all repos — honk @ d77c085920669b0e62d451ee0c2df2fcd586ace4

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