all repos — honk @ e1b0d210e1ae7536028a70c92b2d69390cd27a60

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	What     string
 79	Honker   string
 80	Handle   string
 81	Handles  string
 82	Oonker   string
 83	Oondle   string
 84	XID      string
 85	RID      string
 86	Date     time.Time
 87	URL      string
 88	Noise    string
 89	Precis   string
 90	Format   string
 91	Convoy   string
 92	Audience []string
 93	Public   bool
 94	Whofore  int64
 95	Replies  []*Honk
 96	Flags    int64
 97	HTPrecis template.HTML
 98	HTML     template.HTML
 99	Style    string
100	Open     string
101	Donks    []*Donk
102	Onts     []string
103	Place    *Place
104	Time     *Time
105	Mentions []Mention
106	Badonks  []Badonk
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	return strings.Contains(noise, ">@"+nick) || strings.Contains(noise, "@<span>"+nick)
145}
146
147func OntIsPresent(ont, noise string) bool {
148	ont = ont[1:]
149	return strings.Contains(noise, ">#"+ont) || strings.Contains(noise, "#<span>"+ont)
150}
151
152type OldRevision struct {
153	Precis string
154	Noise  string
155}
156
157const (
158	flagIsAcked    = 1
159	flagIsBonked   = 2
160	flagIsSaved    = 4
161	flagIsUntagged = 8
162	flagIsReacted  = 16
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}