all repos — honk @ 42517537e0ef8eb55eec6e40f2d5a46ffddb89ca

my fork of honk

honk.go (view raw)

  1//
  2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
  3//
  4// Permission to use, copy, modify, and distribute this software for any
  5// purpose with or without fee is hereby granted, provided that the above
  6// copyright notice and this permission notice appear in all copies.
  7//
  8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15
 16package main
 17
 18import (
 19	"flag"
 20	"fmt"
 21	"html/template"
 22	golog "log"
 23	"log/syslog"
 24	notrand "math/rand"
 25	"os"
 26	"strconv"
 27	"strings"
 28	"time"
 29
 30	"humungus.tedunangst.com/r/webs/httpsig"
 31	"humungus.tedunangst.com/r/webs/log"
 32)
 33
 34var softwareVersion = "develop"
 35
 36func init() {
 37	notrand.Seed(time.Now().Unix())
 38}
 39
 40type WhatAbout struct {
 41	ID      int64
 42	Name    string
 43	Display string
 44	About   string
 45	HTAbout template.HTML
 46	Onts    []string
 47	Key     string
 48	URL     string
 49	Options UserOptions
 50	SecKey  httpsig.PrivateKey
 51}
 52
 53type UserOptions struct {
 54	SkinnyCSS    bool   `json:",omitempty"`
 55	OmitImages   bool   `json:",omitempty"`
 56	MentionAll   bool   `json:",omitempty"`
 57	InlineQuotes bool   `json:",omitempty"`
 58	Avatar       string `json:",omitempty"`
 59	Banner       string `json:",omitempty"`
 60	MapLink      string `json:",omitempty"`
 61	Reaction     string `json:",omitempty"`
 62	MeCount      int64
 63	ChatCount    int64
 64}
 65
 66type KeyInfo struct {
 67	keyname string
 68	seckey  httpsig.PrivateKey
 69}
 70
 71const serverUID int64 = -2
 72const readyLuserOne int64 = 1
 73
 74type Honk struct {
 75	ID       int64
 76	UserID   int64
 77	Username string
 78	What     string
 79	Honker   string
 80	Handle   string
 81	Handles  string
 82	Oonker   string
 83	Oondle   string
 84	XID      string
 85	RID      string
 86	Date     time.Time
 87	URL      string
 88	Noise    string
 89	Precis   string
 90	Format   string
 91	Convoy   string
 92	Audience []string
 93	Public   bool
 94	Whofore  int64
 95	Replies  []*Honk
 96	Flags    int64
 97	HTPrecis template.HTML
 98	HTML     template.HTML
 99	Style    string
100	Open     string
101	Donks    []*Donk
102	Onts     []string
103	Place    *Place
104	Time     *Time
105	Mentions []Mention
106	Badonks  []Badonk
107}
108
109type Badonk struct {
110	Who  string
111	What string
112}
113
114type Chonk struct {
115	ID     int64
116	UserID int64
117	XID    string
118	Who    string
119	Target string
120	Date   time.Time
121	Noise  string
122	Format string
123	Donks  []*Donk
124	Handle string
125	HTML   template.HTML
126}
127
128type Chatter struct {
129	Target string
130	Chonks []*Chonk
131}
132
133type Mention struct {
134	Who   string
135	Where string
136}
137
138func (mention *Mention) IsPresent(noise string) bool {
139	nick := strings.TrimLeft(mention.Who, "@")
140	idx := strings.IndexByte(nick, '@')
141	if idx != -1 {
142		nick = nick[:idx]
143	}
144	return strings.Contains(noise, ">@"+nick) || strings.Contains(noise, "@<span>"+nick)
145}
146
147type OldRevision struct {
148	Precis string
149	Noise  string
150}
151
152const (
153	flagIsAcked    = 1
154	flagIsBonked   = 2
155	flagIsSaved    = 4
156	flagIsUntagged = 8
157	flagIsReacted  = 16
158	flagIsWonked   = 32
159)
160
161func (honk *Honk) IsAcked() bool {
162	return honk.Flags&flagIsAcked != 0
163}
164
165func (honk *Honk) IsBonked() bool {
166	return honk.Flags&flagIsBonked != 0
167}
168
169func (honk *Honk) IsSaved() bool {
170	return honk.Flags&flagIsSaved != 0
171}
172
173func (honk *Honk) IsUntagged() bool {
174	return honk.Flags&flagIsUntagged != 0
175}
176
177func (honk *Honk) IsReacted() bool {
178	return honk.Flags&flagIsReacted != 0
179}
180
181type Donk struct {
182	FileID   int64
183	XID      string
184	Name     string
185	Desc     string
186	URL      string
187	Media    string
188	Local    bool
189	External bool
190}
191
192type Place struct {
193	Name      string
194	Latitude  float64
195	Longitude float64
196	Url       string
197}
198
199type Duration int64
200
201func (d Duration) String() string {
202	s := time.Duration(d).String()
203	if strings.HasSuffix(s, "m0s") {
204		s = s[:len(s)-2]
205	}
206	if strings.HasSuffix(s, "h0m") {
207		s = s[:len(s)-2]
208	}
209	return s
210}
211
212func parseDuration(s string) time.Duration {
213	didx := strings.IndexByte(s, 'd')
214	if didx != -1 {
215		days, _ := strconv.ParseInt(s[:didx], 10, 0)
216		dur, _ := time.ParseDuration(s[didx:])
217		return dur + 24*time.Hour*time.Duration(days)
218	}
219	dur, _ := time.ParseDuration(s)
220	return dur
221}
222
223type Time struct {
224	StartTime time.Time
225	EndTime   time.Time
226	Duration  Duration
227}
228
229type Honker struct {
230	ID     int64
231	UserID int64
232	Name   string
233	XID    string
234	Handle string
235	Flavor string
236	Combos []string
237	Meta   HonkerMeta
238}
239
240type HonkerMeta struct {
241	Notes string
242}
243
244type SomeThing struct {
245	What  int
246	XID   string
247	Owner string
248	Name  string
249}
250
251const (
252	SomeNothing int = iota
253	SomeActor
254	SomeCollection
255)
256
257var serverName string
258var serverPrefix string
259var masqName string
260var dataDir = "."
261var viewDir = "."
262var iconName = "icon.png"
263var serverMsg template.HTML
264var aboutMsg template.HTML
265var loginMsg template.HTML
266
267func ElaborateUnitTests() {
268}
269
270func unplugserver(hostname string) {
271	db := opendatabase()
272	xid := fmt.Sprintf("%%https://%s/%%", hostname)
273	db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
274	db.Exec("delete from doovers where rcpt like ?", xid)
275}
276
277func reexecArgs(cmd string) []string {
278	args := []string{"-datadir", dataDir}
279	args = append(args, log.Args()...)
280	args = append(args, cmd)
281	return args
282}
283
284var elog, ilog, dlog *golog.Logger
285
286func main() {
287	flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
288	flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
289	flag.Parse()
290
291	log.Init(log.Options{Progname: "honk", Facility: syslog.LOG_UUCP})
292	elog = log.E
293	ilog = log.I
294	dlog = log.D
295
296	args := flag.Args()
297	cmd := "run"
298	if len(args) > 0 {
299		cmd = args[0]
300	}
301	switch cmd {
302	case "init":
303		initdb()
304	case "upgrade":
305		upgradedb()
306	case "version":
307		fmt.Println(softwareVersion)
308		os.Exit(0)
309	}
310	db := opendatabase()
311	dbversion := 0
312	getconfig("dbversion", &dbversion)
313	if dbversion != myVersion {
314		elog.Fatal("incorrect database version. run upgrade.")
315	}
316	getconfig("servermsg", &serverMsg)
317	getconfig("aboutmsg", &aboutMsg)
318	getconfig("loginmsg", &loginMsg)
319	getconfig("servername", &serverName)
320	getconfig("masqname", &masqName)
321	if masqName == "" {
322		masqName = serverName
323	}
324	serverPrefix = fmt.Sprintf("https://%s/", serverName)
325	getconfig("usersep", &userSep)
326	getconfig("honksep", &honkSep)
327	getconfig("devel", &develMode)
328	getconfig("fasttimeout", &fastTimeout)
329	getconfig("slowtimeout", &slowTimeout)
330	getconfig("signgets", &signGets)
331	prepareStatements(db)
332	switch cmd {
333	case "admin":
334		adminscreen()
335	case "import":
336		if len(args) != 4 {
337			elog.Fatal("import username mastodon|twitter srcdir")
338		}
339		importMain(args[1], args[2], args[3])
340	case "devel":
341		if len(args) != 2 {
342			elog.Fatal("need an argument: devel (on|off)")
343		}
344		switch args[1] {
345		case "on":
346			setconfig("devel", 1)
347		case "off":
348			setconfig("devel", 0)
349		default:
350			elog.Fatal("argument must be on or off")
351		}
352	case "setconfig":
353		if len(args) != 3 {
354			elog.Fatal("need an argument: setconfig key val")
355		}
356		var val interface{}
357		var err error
358		if val, err = strconv.Atoi(args[2]); err != nil {
359			val = args[2]
360		}
361		setconfig(args[1], val)
362	case "adduser":
363		adduser()
364	case "deluser":
365		if len(args) < 2 {
366			fmt.Printf("usage: honk deluser username\n")
367			return
368		}
369		deluser(args[1])
370	case "chpass":
371		if len(args) < 2 {
372			fmt.Printf("usage: honk chpass username\n")
373			return
374		}
375		chpass(args[1])
376	case "follow":
377		if len(args) < 3 {
378			fmt.Printf("usage: honk follow username url\n")
379			return
380		}
381		user, err := butwhatabout(args[1])
382		if err != nil {
383			fmt.Printf("user not found\n")
384			return
385		}
386		var meta HonkerMeta
387		mj, _ := jsonify(&meta)
388		honkerid, err := savehonker(user, args[2], "", "presub", "", mj)
389		if err != nil {
390			fmt.Printf("had some trouble with that: %s\n", err)
391			return
392		}
393		followyou(user, honkerid, true)
394	case "unfollow":
395		if len(args) < 3 {
396			fmt.Printf("usage: honk unfollow username url\n")
397			return
398		}
399		user, err := butwhatabout(args[1])
400		if err != nil {
401			fmt.Printf("user not found\n")
402			return
403		}
404		row := db.QueryRow("select honkerid from honkers where xid = ? and userid = ? and flavor in ('sub')", args[2], user.ID)
405		var honkerid int64
406		err = row.Scan(&honkerid)
407		if err != nil {
408			fmt.Printf("sorry couldn't find them\n")
409			return
410		}
411		unfollowyou(user, honkerid, true)
412	case "cleanup":
413		arg := "30"
414		if len(args) > 1 {
415			arg = args[1]
416		}
417		cleanupdb(arg)
418	case "unplug":
419		if len(args) < 2 {
420			fmt.Printf("usage: honk unplug servername\n")
421			return
422		}
423		name := args[1]
424		unplugserver(name)
425	case "backup":
426		if len(args) < 2 {
427			fmt.Printf("usage: honk backup dirname\n")
428			return
429		}
430		name := args[1]
431		svalbard(name)
432	case "ping":
433		if len(args) < 3 {
434			fmt.Printf("usage: honk ping (from username) (to username or url)\n")
435			return
436		}
437		name := args[1]
438		targ := args[2]
439		user, err := butwhatabout(name)
440		if err != nil {
441			elog.Printf("unknown user")
442			return
443		}
444		ping(user, targ)
445	case "run":
446		serve()
447	case "backend":
448		backendServer()
449	case "test":
450		ElaborateUnitTests()
451	default:
452		elog.Fatal("unknown command")
453	}
454}