all repos — honk @ 015a89464da28eccac72c4a3aac6874680a4e738

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