all repos — honk @ 1e8975efecd83676e93cd1faa8217049ab80c370

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