all repos — honk @ c973e951ec23543966278c98ab77892435fcce08

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