all repos — honk @ 7ed3b24f1a2c7c1d9fed8486ebe3cdb7bf3edb41

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	"log"
 23	notrand "math/rand"
 24	"os"
 25	"strconv"
 26	"strings"
 27	"time"
 28
 29	"humungus.tedunangst.com/r/webs/httpsig"
 30)
 31
 32var softwareVersion = "develop"
 33
 34func init() {
 35	notrand.Seed(time.Now().Unix())
 36}
 37
 38type WhatAbout struct {
 39	ID      int64
 40	Name    string
 41	Display string
 42	About   string
 43	Key     string
 44	URL     string
 45	Options UserOptions
 46	SecKey  httpsig.PrivateKey
 47}
 48
 49type UserOptions struct {
 50	SkinnyCSS  bool   `json:",omitempty"`
 51	OmitImages bool   `json:",omitempty"`
 52	Avatar     string `json:",omitempty"`
 53	MapLink    string `json:",omitempty"`
 54	Reaction   string `json:",omitempty"`
 55}
 56
 57type KeyInfo struct {
 58	keyname string
 59	seckey  httpsig.PrivateKey
 60}
 61
 62const serverUID int64 = -2
 63
 64type Honk struct {
 65	ID       int64
 66	UserID   int64
 67	Username string
 68	What     string
 69	Honker   string
 70	Handle   string
 71	Oonker   string
 72	Oondle   string
 73	XID      string
 74	RID      string
 75	Date     time.Time
 76	URL      string
 77	Noise    string
 78	Precis   string
 79	Format   string
 80	Convoy   string
 81	Audience []string
 82	Public   bool
 83	Whofore  int64
 84	Replies  []*Honk
 85	Flags    int64
 86	HTPrecis template.HTML
 87	HTML     template.HTML
 88	Style    string
 89	Open     string
 90	Donks    []*Donk
 91	Onts     []string
 92	Place    *Place
 93	Time     *Time
 94	Mentions []Mention
 95}
 96
 97type Mention struct {
 98	Who   string
 99	Where string
100}
101
102type OldRevision struct {
103	Precis string
104	Noise  string
105}
106
107const (
108	flagIsAcked    = 1
109	flagIsBonked   = 2
110	flagIsSaved    = 4
111	flagIsUntagged = 8
112	flagIsReacted  = 16
113)
114
115func (honk *Honk) IsAcked() bool {
116	return honk.Flags&flagIsAcked != 0
117}
118
119func (honk *Honk) IsBonked() bool {
120	return honk.Flags&flagIsBonked != 0
121}
122
123func (honk *Honk) IsSaved() bool {
124	return honk.Flags&flagIsSaved != 0
125}
126
127func (honk *Honk) IsUntagged() bool {
128	return honk.Flags&flagIsUntagged != 0
129}
130
131func (honk *Honk) IsReacted() bool {
132	return honk.Flags&flagIsReacted != 0
133}
134
135type Donk struct {
136	FileID int64
137	XID    string
138	Name   string
139	Desc   string
140	URL    string
141	Media  string
142	Local  bool
143}
144
145type Place struct {
146	Name      string
147	Latitude  float64
148	Longitude float64
149	Url       string
150}
151
152type Duration int64
153
154func (d Duration) String() string {
155	s := time.Duration(d).String()
156	if strings.HasSuffix(s, "m0s") {
157		s = s[:len(s)-2]
158	}
159	if strings.HasSuffix(s, "h0m") {
160		s = s[:len(s)-2]
161	}
162	return s
163}
164
165func parseDuration(s string) time.Duration {
166	didx := strings.IndexByte(s, 'd')
167	if didx != -1 {
168		days, _ := strconv.ParseInt(s[:didx], 10, 0)
169		dur, _ := time.ParseDuration(s[didx:])
170		return dur + 24*time.Hour*time.Duration(days)
171	}
172	dur, _ := time.ParseDuration(s)
173	return dur
174}
175
176type Time struct {
177	StartTime time.Time
178	EndTime   time.Time
179	Duration  Duration
180}
181
182type Honker struct {
183	ID     int64
184	UserID int64
185	Name   string
186	XID    string
187	Handle string
188	Flavor string
189	Combos []string
190	Meta   HonkerMeta
191}
192
193type HonkerMeta struct {
194	Notes string
195}
196
197type SomeThing struct {
198	What  int
199	XID   string
200	Owner string
201	Name  string
202}
203
204const (
205	SomeNothing int = iota
206	SomeActor
207	SomeCollection
208)
209
210var serverName string
211var masqName string
212var dataDir = "."
213var viewDir = "."
214var iconName = "icon.png"
215var serverMsg template.HTML
216var aboutMsg template.HTML
217var loginMsg template.HTML
218
219func ElaborateUnitTests() {
220}
221
222func unplugserver(hostname string) {
223	db := opendatabase()
224	xid := fmt.Sprintf("%%https://%s/%%", hostname)
225	db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
226	db.Exec("delete from doovers where rcpt like ?", xid)
227}
228
229func main() {
230	flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
231	flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
232	flag.Parse()
233	args := flag.Args()
234	cmd := "run"
235	if len(args) > 0 {
236		cmd = args[0]
237	}
238	switch cmd {
239	case "init":
240		initdb()
241	case "upgrade":
242		upgradedb()
243	case "version":
244		fmt.Println(softwareVersion)
245		os.Exit(0)
246	}
247	db := opendatabase()
248	dbversion := 0
249	getconfig("dbversion", &dbversion)
250	if dbversion != myVersion {
251		log.Fatal("incorrect database version. run upgrade.")
252	}
253	getconfig("servermsg", &serverMsg)
254	getconfig("aboutmsg", &aboutMsg)
255	getconfig("loginmsg", &loginMsg)
256	getconfig("servername", &serverName)
257	getconfig("masqname", &masqName)
258	if masqName == "" {
259		masqName = serverName
260	}
261	getconfig("usersep", &userSep)
262	getconfig("honksep", &honkSep)
263	prepareStatements(db)
264	switch cmd {
265	case "admin":
266		adminscreen()
267	case "import":
268		if len(args) != 4 {
269			log.Fatal("import username mastodon|twitter srcdir")
270		}
271		importMain(args[1], args[2], args[3])
272	case "debug":
273		if len(args) != 2 {
274			log.Fatal("need an argument: debug (on|off)")
275		}
276		switch args[1] {
277		case "on":
278			setconfig("debug", 1)
279		case "off":
280			setconfig("debug", 0)
281		default:
282			log.Fatal("argument must be on or off")
283		}
284	case "adduser":
285		adduser()
286	case "deluser":
287		if len(args) < 2 {
288			fmt.Printf("usage: honk deluser username\n")
289			return
290		}
291		deluser(args[1])
292	case "chpass":
293		chpass()
294	case "cleanup":
295		arg := "30"
296		if len(args) > 1 {
297			arg = args[1]
298		}
299		cleanupdb(arg)
300	case "unplug":
301		if len(args) < 2 {
302			fmt.Printf("usage: honk unplug servername\n")
303			return
304		}
305		name := args[1]
306		unplugserver(name)
307	case "ping":
308		if len(args) < 3 {
309			fmt.Printf("usage: honk ping from to\n")
310			return
311		}
312		name := args[1]
313		targ := args[2]
314		user, err := butwhatabout(name)
315		if err != nil {
316			log.Printf("unknown user")
317			return
318		}
319		ping(user, targ)
320	case "run":
321		serve()
322	case "backend":
323		backendServer()
324	case "test":
325		ElaborateUnitTests()
326	default:
327		log.Fatal("unknown command")
328	}
329}