all repos — honk @ 8062001fdc374d1a90298b60b933f85d4349ec34

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