all repos — honk @ 673de2a22d5667a912ce80e8dde39e400bbc8184

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