all repos — honk @ b498518b13d3fa6840662e0bf1ff5ba6bba3270c

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	return strings.Contains(noise, ">@"+nick) || strings.Contains(noise, "@<span>"+nick)
145}
146
147type OldRevision struct {
148	Precis string
149	Noise  string
150}
151
152const (
153	flagIsAcked    = 1
154	flagIsBonked   = 2
155	flagIsSaved    = 4
156	flagIsUntagged = 8
157	flagIsReacted  = 16
158	flagIsWonked   = 32
159)
160
161func (honk *Honk) IsAcked() bool {
162	return honk.Flags&flagIsAcked != 0
163}
164
165func (honk *Honk) IsBonked() bool {
166	return honk.Flags&flagIsBonked != 0
167}
168
169func (honk *Honk) IsSaved() bool {
170	return honk.Flags&flagIsSaved != 0
171}
172
173func (honk *Honk) IsUntagged() bool {
174	return honk.Flags&flagIsUntagged != 0
175}
176
177func (honk *Honk) IsReacted() bool {
178	return honk.Flags&flagIsReacted != 0
179}
180
181func (honk *Honk) IsWonked() bool {
182	return honk.Flags&flagIsWonked != 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		chpass()
376	case "cleanup":
377		arg := "30"
378		if len(args) > 1 {
379			arg = args[1]
380		}
381		cleanupdb(arg)
382	case "unplug":
383		if len(args) < 2 {
384			fmt.Printf("usage: honk unplug servername\n")
385			return
386		}
387		name := args[1]
388		unplugserver(name)
389	case "backup":
390		if len(args) < 2 {
391			fmt.Printf("usage: honk backup dirname\n")
392			return
393		}
394		name := args[1]
395		svalbard(name)
396	case "ping":
397		if len(args) < 3 {
398			fmt.Printf("usage: honk ping (from username) (to username or url)\n")
399			return
400		}
401		name := args[1]
402		targ := args[2]
403		user, err := butwhatabout(name)
404		if err != nil {
405			elog.Printf("unknown user")
406			return
407		}
408		ping(user, targ)
409	case "run":
410		serve()
411	case "backend":
412		backendServer()
413	case "test":
414		ElaborateUnitTests()
415	default:
416		elog.Fatal("unknown command")
417	}
418}