all repos — honk @ 26a49078ad8985b14dae2e080ef6e71757cfa615

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	Avatar     string `json:",omitempty"`
 58	Banner     string `json:",omitempty"`
 59	MapLink    string `json:",omitempty"`
 60	Reaction   string `json:",omitempty"`
 61	MeCount    int64
 62	ChatCount  int64
 63}
 64
 65type KeyInfo struct {
 66	keyname string
 67	seckey  httpsig.PrivateKey
 68}
 69
 70const serverUID int64 = -2
 71const readyLuserOne int64 = 1
 72
 73type Honk struct {
 74	ID       int64
 75	UserID   int64
 76	Username string
 77	What     string
 78	Honker   string
 79	Handle   string
 80	Handles  string
 81	Oonker   string
 82	Oondle   string
 83	XID      string
 84	RID      string
 85	Date     time.Time
 86	URL      string
 87	Noise    string
 88	Precis   string
 89	Format   string
 90	Convoy   string
 91	Audience []string
 92	Public   bool
 93	Whofore  int64
 94	Replies  []*Honk
 95	Flags    int64
 96	HTPrecis template.HTML
 97	HTML     template.HTML
 98	Style    string
 99	Open     string
100	Donks    []*Donk
101	Onts     []string
102	Place    *Place
103	Time     *Time
104	Mentions []Mention
105	Badonks  []Badonk
106	Wonkles  string
107	Guesses  template.HTML
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
182func (honk *Honk) IsWonked() bool {
183	return honk.Flags&flagIsWonked != 0
184}
185
186type Donk struct {
187	FileID   int64
188	XID      string
189	Name     string
190	Desc     string
191	URL      string
192	Media    string
193	Local    bool
194	External bool
195}
196
197type Place struct {
198	Name      string
199	Latitude  float64
200	Longitude float64
201	Url       string
202}
203
204type Duration int64
205
206func (d Duration) String() string {
207	s := time.Duration(d).String()
208	if strings.HasSuffix(s, "m0s") {
209		s = s[:len(s)-2]
210	}
211	if strings.HasSuffix(s, "h0m") {
212		s = s[:len(s)-2]
213	}
214	return s
215}
216
217func parseDuration(s string) time.Duration {
218	didx := strings.IndexByte(s, 'd')
219	if didx != -1 {
220		days, _ := strconv.ParseInt(s[:didx], 10, 0)
221		dur, _ := time.ParseDuration(s[didx:])
222		return dur + 24*time.Hour*time.Duration(days)
223	}
224	dur, _ := time.ParseDuration(s)
225	return dur
226}
227
228type Time struct {
229	StartTime time.Time
230	EndTime   time.Time
231	Duration  Duration
232}
233
234type Honker struct {
235	ID     int64
236	UserID int64
237	Name   string
238	XID    string
239	Handle string
240	Flavor string
241	Combos []string
242	Meta   HonkerMeta
243}
244
245type HonkerMeta struct {
246	Notes string
247}
248
249type SomeThing struct {
250	What  int
251	XID   string
252	Owner string
253	Name  string
254}
255
256const (
257	SomeNothing int = iota
258	SomeActor
259	SomeCollection
260)
261
262var serverName string
263var serverPrefix string
264var masqName string
265var dataDir = "."
266var viewDir = "."
267var iconName = "icon.png"
268var serverMsg template.HTML
269var aboutMsg template.HTML
270var loginMsg template.HTML
271
272func ElaborateUnitTests() {
273}
274
275func unplugserver(hostname string) {
276	db := opendatabase()
277	xid := fmt.Sprintf("%%https://%s/%%", hostname)
278	db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
279	db.Exec("delete from doovers where rcpt like ?", xid)
280}
281
282func reexecArgs(cmd string) []string {
283	args := []string{"-datadir", dataDir}
284	args = append(args, log.Args()...)
285	args = append(args, cmd)
286	return args
287}
288
289var elog, ilog, dlog *golog.Logger
290
291func main() {
292	flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
293	flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
294	flag.Parse()
295
296	log.Init(log.Options{Progname: "honk", Facility: syslog.LOG_UUCP})
297	elog = log.E
298	ilog = log.I
299	dlog = log.D
300
301	args := flag.Args()
302	cmd := "run"
303	if len(args) > 0 {
304		cmd = args[0]
305	}
306	switch cmd {
307	case "init":
308		initdb()
309	case "upgrade":
310		upgradedb()
311	case "version":
312		fmt.Println(softwareVersion)
313		os.Exit(0)
314	}
315	db := opendatabase()
316	dbversion := 0
317	getconfig("dbversion", &dbversion)
318	if dbversion != myVersion {
319		elog.Fatal("incorrect database version. run upgrade.")
320	}
321	getconfig("servermsg", &serverMsg)
322	getconfig("aboutmsg", &aboutMsg)
323	getconfig("loginmsg", &loginMsg)
324	getconfig("servername", &serverName)
325	getconfig("masqname", &masqName)
326	if masqName == "" {
327		masqName = serverName
328	}
329	serverPrefix = fmt.Sprintf("https://%s/", serverName)
330	getconfig("usersep", &userSep)
331	getconfig("honksep", &honkSep)
332	getconfig("devel", &develMode)
333	getconfig("fasttimeout", &fastTimeout)
334	getconfig("slowtimeout", &slowTimeout)
335	getconfig("signgets", &signGets)
336	prepareStatements(db)
337	switch cmd {
338	case "admin":
339		adminscreen()
340	case "import":
341		if len(args) != 4 {
342			elog.Fatal("import username mastodon|twitter srcdir")
343		}
344		importMain(args[1], args[2], args[3])
345	case "devel":
346		if len(args) != 2 {
347			elog.Fatal("need an argument: devel (on|off)")
348		}
349		switch args[1] {
350		case "on":
351			setconfig("devel", 1)
352		case "off":
353			setconfig("devel", 0)
354		default:
355			elog.Fatal("argument must be on or off")
356		}
357	case "setconfig":
358		if len(args) != 3 {
359			elog.Fatal("need an argument: setconfig key val")
360		}
361		var val interface{}
362		var err error
363		if val, err = strconv.Atoi(args[2]); err != nil {
364			val = args[2]
365		}
366		setconfig(args[1], val)
367	case "adduser":
368		adduser()
369	case "deluser":
370		if len(args) < 2 {
371			fmt.Printf("usage: honk deluser username\n")
372			return
373		}
374		deluser(args[1])
375	case "chpass":
376		if len(args) < 2 {
377			fmt.Printf("usage: honk chpass username\n")
378			return
379		}
380		chpass(args[1])
381	case "cleanup":
382		arg := "30"
383		if len(args) > 1 {
384			arg = args[1]
385		}
386		cleanupdb(arg)
387	case "unplug":
388		if len(args) < 2 {
389			fmt.Printf("usage: honk unplug servername\n")
390			return
391		}
392		name := args[1]
393		unplugserver(name)
394	case "backup":
395		if len(args) < 2 {
396			fmt.Printf("usage: honk backup dirname\n")
397			return
398		}
399		name := args[1]
400		svalbard(name)
401	case "ping":
402		if len(args) < 3 {
403			fmt.Printf("usage: honk ping (from username) (to username or url)\n")
404			return
405		}
406		name := args[1]
407		targ := args[2]
408		user, err := butwhatabout(name)
409		if err != nil {
410			elog.Printf("unknown user")
411			return
412		}
413		ping(user, targ)
414	case "run":
415		serve()
416	case "backend":
417		backendServer()
418	case "test":
419		ElaborateUnitTests()
420	default:
421		elog.Fatal("unknown command")
422	}
423}