all repos — honk @ 95a9708de43dfd6cf3f303512d360d8fb1a2a116

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
147func OntIsPresent(ont, noise string) bool {
148	ont = strings.ToLower(ont[1:] + "<")
149	idx := strings.IndexByte(noise, '#')
150	for idx >= 0 {
151		if strings.HasPrefix(noise[idx:], "#<span>") {
152			idx += 5
153		} else {
154			idx += 1
155		}
156		if idx + len(ont) + 1 > len(noise) {
157			return false
158		}
159		test := noise[idx:idx+len(ont)]
160		test = strings.ToLower(test)
161		if test == ont {
162			return true
163		}
164		newidx := strings.IndexByte(noise[idx:], '#')
165		if newidx == -1 {
166			return false
167		}
168		idx += newidx
169	}
170	return false
171}
172
173type OldRevision struct {
174	Precis string
175	Noise  string
176}
177
178const (
179	flagIsAcked    = 1
180	flagIsBonked   = 2
181	flagIsSaved    = 4
182	flagIsUntagged = 8
183	flagIsReacted  = 16
184)
185
186func (honk *Honk) IsAcked() bool {
187	return honk.Flags&flagIsAcked != 0
188}
189
190func (honk *Honk) IsBonked() bool {
191	return honk.Flags&flagIsBonked != 0
192}
193
194func (honk *Honk) IsSaved() bool {
195	return honk.Flags&flagIsSaved != 0
196}
197
198func (honk *Honk) IsUntagged() bool {
199	return honk.Flags&flagIsUntagged != 0
200}
201
202func (honk *Honk) IsReacted() bool {
203	return honk.Flags&flagIsReacted != 0
204}
205
206type Donk struct {
207	FileID   int64
208	XID      string
209	Name     string
210	Desc     string
211	URL      string
212	Media    string
213	Local    bool
214	External bool
215}
216
217type Place struct {
218	Name      string
219	Latitude  float64
220	Longitude float64
221	Url       string
222}
223
224type Duration int64
225
226func (d Duration) String() string {
227	s := time.Duration(d).String()
228	if strings.HasSuffix(s, "m0s") {
229		s = s[:len(s)-2]
230	}
231	if strings.HasSuffix(s, "h0m") {
232		s = s[:len(s)-2]
233	}
234	return s
235}
236
237func parseDuration(s string) time.Duration {
238	didx := strings.IndexByte(s, 'd')
239	if didx != -1 {
240		days, _ := strconv.ParseInt(s[:didx], 10, 0)
241		dur, _ := time.ParseDuration(s[didx:])
242		return dur + 24*time.Hour*time.Duration(days)
243	}
244	dur, _ := time.ParseDuration(s)
245	return dur
246}
247
248type Time struct {
249	StartTime time.Time
250	EndTime   time.Time
251	Duration  Duration
252}
253
254type Honker struct {
255	ID     int64
256	UserID int64
257	Name   string
258	XID    string
259	Handle string
260	Flavor string
261	Combos []string
262	Meta   HonkerMeta
263}
264
265type HonkerMeta struct {
266	Notes string
267}
268
269type SomeThing struct {
270	What  int
271	XID   string
272	Owner string
273	Name  string
274}
275
276const (
277	SomeNothing int = iota
278	SomeActor
279	SomeCollection
280)
281
282var serverName string
283var serverPrefix string
284var masqName string
285var dataDir = "."
286var viewDir = "."
287var iconName = "icon.png"
288var serverMsg template.HTML
289var aboutMsg template.HTML
290var loginMsg template.HTML
291
292func ElaborateUnitTests() {
293}
294
295func unplugserver(hostname string) {
296	db := opendatabase()
297	xid := fmt.Sprintf("%%https://%s/%%", hostname)
298	db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
299	db.Exec("delete from doovers where rcpt like ?", xid)
300}
301
302func reexecArgs(cmd string) []string {
303	args := []string{"-datadir", dataDir}
304	args = append(args, log.Args()...)
305	args = append(args, cmd)
306	return args
307}
308
309var elog, ilog, dlog *golog.Logger
310
311func main() {
312	flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
313	flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
314	flag.Parse()
315
316	log.Init(log.Options{Progname: "honk", Facility: syslog.LOG_UUCP})
317	elog = log.E
318	ilog = log.I
319	dlog = log.D
320
321	args := flag.Args()
322	cmd := "run"
323	if len(args) > 0 {
324		cmd = args[0]
325	}
326	switch cmd {
327	case "init":
328		initdb()
329	case "upgrade":
330		upgradedb()
331	case "version":
332		fmt.Println(softwareVersion)
333		os.Exit(0)
334	}
335	db := opendatabase()
336	dbversion := 0
337	getconfig("dbversion", &dbversion)
338	if dbversion != myVersion {
339		elog.Fatal("incorrect database version. run upgrade.")
340	}
341	getconfig("servermsg", &serverMsg)
342	getconfig("aboutmsg", &aboutMsg)
343	getconfig("loginmsg", &loginMsg)
344	getconfig("servername", &serverName)
345	getconfig("masqname", &masqName)
346	if masqName == "" {
347		masqName = serverName
348	}
349	serverPrefix = fmt.Sprintf("https://%s/", serverName)
350	getconfig("usersep", &userSep)
351	getconfig("honksep", &honkSep)
352	getconfig("devel", &develMode)
353	getconfig("fasttimeout", &fastTimeout)
354	getconfig("slowtimeout", &slowTimeout)
355	getconfig("signgets", &signGets)
356	prepareStatements(db)
357	switch cmd {
358	case "admin":
359		adminscreen()
360	case "import":
361		if len(args) != 4 {
362			elog.Fatal("import username mastodon|twitter srcdir")
363		}
364		importMain(args[1], args[2], args[3])
365	case "devel":
366		if len(args) != 2 {
367			elog.Fatal("need an argument: devel (on|off)")
368		}
369		switch args[1] {
370		case "on":
371			setconfig("devel", 1)
372		case "off":
373			setconfig("devel", 0)
374		default:
375			elog.Fatal("argument must be on or off")
376		}
377	case "setconfig":
378		if len(args) != 3 {
379			elog.Fatal("need an argument: setconfig key val")
380		}
381		var val interface{}
382		var err error
383		if val, err = strconv.Atoi(args[2]); err != nil {
384			val = args[2]
385		}
386		setconfig(args[1], val)
387	case "adduser":
388		adduser()
389	case "deluser":
390		if len(args) < 2 {
391			fmt.Printf("usage: honk deluser username\n")
392			return
393		}
394		deluser(args[1])
395	case "chpass":
396		if len(args) < 2 {
397			fmt.Printf("usage: honk chpass username\n")
398			return
399		}
400		chpass(args[1])
401	case "follow":
402		if len(args) < 3 {
403			fmt.Printf("usage: honk follow username url\n")
404			return
405		}
406		user, err := butwhatabout(args[1])
407		if err != nil {
408			fmt.Printf("user not found\n")
409			return
410		}
411		var meta HonkerMeta
412		mj, _ := jsonify(&meta)
413		honkerid, err := savehonker(user, args[2], "", "presub", "", mj)
414		if err != nil {
415			fmt.Printf("had some trouble with that: %s\n", err)
416			return
417		}
418		followyou(user, honkerid, true)
419	case "unfollow":
420		if len(args) < 3 {
421			fmt.Printf("usage: honk unfollow username url\n")
422			return
423		}
424		user, err := butwhatabout(args[1])
425		if err != nil {
426			fmt.Printf("user not found\n")
427			return
428		}
429		row := db.QueryRow("select honkerid from honkers where xid = ? and userid = ? and flavor in ('sub')", args[2], user.ID)
430		var honkerid int64
431		err = row.Scan(&honkerid)
432		if err != nil {
433			fmt.Printf("sorry couldn't find them\n")
434			return
435		}
436		unfollowyou(user, honkerid, true)
437	case "cleanup":
438		arg := "30"
439		if len(args) > 1 {
440			arg = args[1]
441		}
442		cleanupdb(arg)
443	case "unplug":
444		if len(args) < 2 {
445			fmt.Printf("usage: honk unplug servername\n")
446			return
447		}
448		name := args[1]
449		unplugserver(name)
450	case "backup":
451		if len(args) < 2 {
452			fmt.Printf("usage: honk backup dirname\n")
453			return
454		}
455		name := args[1]
456		svalbard(name)
457	case "ping":
458		if len(args) < 3 {
459			fmt.Printf("usage: honk ping (from username) (to username or url)\n")
460			return
461		}
462		name := args[1]
463		targ := args[2]
464		user, err := butwhatabout(name)
465		if err != nil {
466			elog.Printf("unknown user")
467			return
468		}
469		ping(user, targ)
470	case "run":
471		serve()
472	case "backend":
473		backendServer()
474	case "test":
475		ElaborateUnitTests()
476	default:
477		elog.Fatal("unknown command")
478	}
479}