all repos — honk @ 79d3e5e0dba858ca533d38976328017a00dcfc57

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	Avahex     bool   `json:",omitempty"`
 57	MentionAll bool   `json:",omitempty"`
 58	Avatar     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
 71
 72type Honk struct {
 73	ID       int64
 74	UserID   int64
 75	Username string
 76	What     string
 77	Honker   string
 78	Handle   string
 79	Handles  string
 80	Oonker   string
 81	Oondle   string
 82	XID      string
 83	RID      string
 84	Date     time.Time
 85	URL      string
 86	Noise    string
 87	Precis   string
 88	Format   string
 89	Convoy   string
 90	Audience []string
 91	Public   bool
 92	Whofore  int64
 93	Replies  []*Honk
 94	Flags    int64
 95	HTPrecis template.HTML
 96	HTML     template.HTML
 97	Style    string
 98	Open     string
 99	Donks    []*Donk
100	Onts     []string
101	Place    *Place
102	Time     *Time
103	Mentions []Mention
104	Badonks  []Badonk
105	Wonkles  string
106	Guesses  template.HTML
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	nick += "<"
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		chpass()
377	case "cleanup":
378		arg := "30"
379		if len(args) > 1 {
380			arg = args[1]
381		}
382		cleanupdb(arg)
383	case "unplug":
384		if len(args) < 2 {
385			fmt.Printf("usage: honk unplug servername\n")
386			return
387		}
388		name := args[1]
389		unplugserver(name)
390	case "backup":
391		if len(args) < 2 {
392			fmt.Printf("usage: honk backup dirname\n")
393			return
394		}
395		name := args[1]
396		svalbard(name)
397	case "ping":
398		if len(args) < 3 {
399			fmt.Printf("usage: honk ping (from username) (to username or url)\n")
400			return
401		}
402		name := args[1]
403		targ := args[2]
404		user, err := butwhatabout(name)
405		if err != nil {
406			elog.Printf("unknown user")
407			return
408		}
409		ping(user, targ)
410	case "run":
411		serve()
412	case "backend":
413		backendServer()
414	case "test":
415		ElaborateUnitTests()
416	default:
417		elog.Fatal("unknown command")
418	}
419}