all repos — honk @ 74409de06d55508eaf85daefb3312dc260055579

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	Avahex        bool   `json:",omitempty"`
 59	Avatar        string `json:",omitempty"`
 60	CustomDisplay string `json:",omitempty"`
 61	Banner        string `json:",omitempty"`
 62	MapLink       string `json:",omitempty"`
 63	Reaction      string `json:",omitempty"`
 64	MeCount       int64
 65	ChatCount     int64
 66}
 67
 68type KeyInfo struct {
 69	keyname string
 70	seckey  httpsig.PrivateKey
 71}
 72
 73const serverUID int64 = -2
 74const readyLuserOne int64 = 1
 75
 76type Honk struct {
 77	ID          int64
 78	UserID      int64
 79	Username    string
 80	DisplayName string
 81	What        string
 82	Honker      string
 83	Handle      string
 84	Handles     string
 85	Oonker      string
 86	Oondle      string
 87	XID         string
 88	RID         string
 89	Date        time.Time
 90	DatePretty  string
 91	URL         string
 92	Noise       string
 93	Precis      string
 94	Format      string
 95	Convoy      string
 96	Audience    []string
 97	Public      bool
 98	Whofore     int64
 99	Replies     []*Honk
100	Flags       int64
101	HTPrecis    template.HTML
102	HTML        template.HTML
103	Style       string
104	Open        string
105	Donks       []*Donk
106	Onts        []string
107	Place       *Place
108	Time        *Time
109	Mentions    []Mention
110	Badonks     []Badonk
111}
112
113type Badonk struct {
114	Who  string
115	What string
116}
117
118type Chonk struct {
119	ID     int64
120	UserID int64
121	XID    string
122	Who    string
123	Target string
124	Date   time.Time
125	Noise  string
126	Format string
127	Donks  []*Donk
128	Handle string
129	HTML   template.HTML
130}
131
132type Chatter struct {
133	Target string
134	Chonks []*Chonk
135}
136
137type Mention struct {
138	Who   string
139	Where string
140}
141
142func (mention *Mention) IsPresent(noise string) bool {
143	nick := strings.TrimLeft(mention.Who, "@")
144	idx := strings.IndexByte(nick, '@')
145	if idx != -1 {
146		nick = nick[:idx]
147	}
148	return strings.Contains(noise, ">@"+nick) || strings.Contains(noise, "@<span>"+nick)
149}
150
151type OldRevision struct {
152	Precis string
153	Noise  string
154}
155
156const (
157	flagIsAcked    = 1
158	flagIsBonked   = 2
159	flagIsSaved    = 4
160	flagIsUntagged = 8
161	flagIsReacted  = 16
162)
163
164func (honk *Honk) IsAcked() bool {
165	return honk.Flags&flagIsAcked != 0
166}
167
168func (honk *Honk) IsBonked() bool {
169	return honk.Flags&flagIsBonked != 0
170}
171
172func (honk *Honk) IsSaved() bool {
173	return honk.Flags&flagIsSaved != 0
174}
175
176func (honk *Honk) IsUntagged() bool {
177	return honk.Flags&flagIsUntagged != 0
178}
179
180func (honk *Honk) IsReacted() bool {
181	return honk.Flags&flagIsReacted != 0
182}
183
184type Donk struct {
185	FileID   int64
186	XID      string
187	Name     string
188	Desc     string
189	URL      string
190	Media    string
191	Local    bool
192	External bool
193}
194
195type Place struct {
196	Name      string
197	Latitude  float64
198	Longitude float64
199	Url       string
200}
201
202type Duration int64
203
204func (d Duration) String() string {
205	s := time.Duration(d).String()
206	if strings.HasSuffix(s, "m0s") {
207		s = s[:len(s)-2]
208	}
209	if strings.HasSuffix(s, "h0m") {
210		s = s[:len(s)-2]
211	}
212	return s
213}
214
215func parseDuration(s string) time.Duration {
216	didx := strings.IndexByte(s, 'd')
217	if didx != -1 {
218		days, _ := strconv.ParseInt(s[:didx], 10, 0)
219		dur, _ := time.ParseDuration(s[didx:])
220		return dur + 24*time.Hour*time.Duration(days)
221	}
222	dur, _ := time.ParseDuration(s)
223	return dur
224}
225
226type Time struct {
227	StartTime time.Time
228	EndTime   time.Time
229	Duration  Duration
230}
231
232type Honker struct {
233	ID     int64
234	UserID int64
235	Name   string
236	XID    string
237	Handle string
238	Flavor string
239	Combos []string
240	Meta   HonkerMeta
241}
242
243type HonkerMeta struct {
244	Notes string
245}
246
247type SomeThing struct {
248	What  int
249	XID   string
250	Owner string
251	Name  string
252}
253
254const (
255	SomeNothing int = iota
256	SomeActor
257	SomeCollection
258)
259
260var serverName string
261var serverPrefix string
262var masqName string
263var dataDir = "."
264var viewDir = "."
265var iconName = "icon.png"
266var serverMsg template.HTML
267var aboutMsg template.HTML
268var loginMsg template.HTML
269
270func ElaborateUnitTests() {
271}
272
273func unplugserver(hostname string) {
274	db := opendatabase()
275	xid := fmt.Sprintf("%%https://%s/%%", hostname)
276	db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
277	db.Exec("delete from doovers where rcpt like ?", xid)
278}
279
280func reexecArgs(cmd string) []string {
281	args := []string{"-datadir", dataDir}
282	args = append(args, log.Args()...)
283	args = append(args, cmd)
284	return args
285}
286
287var elog, ilog, dlog *golog.Logger
288
289func main() {
290	flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
291	flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
292	flag.Parse()
293
294	log.Init(log.Options{Progname: "honk", Facility: syslog.LOG_UUCP})
295	elog = log.E
296	ilog = log.I
297	dlog = log.D
298
299	args := flag.Args()
300	cmd := "run"
301	if len(args) > 0 {
302		cmd = args[0]
303	}
304	switch cmd {
305	case "init":
306		initdb()
307	case "upgrade":
308		upgradedb()
309	case "version":
310		fmt.Println(softwareVersion)
311		os.Exit(0)
312	}
313	db := opendatabase()
314	dbversion := 0
315	getconfig("dbversion", &dbversion)
316	if dbversion != myVersion {
317		elog.Fatal("incorrect database version. run upgrade.")
318	}
319	getconfig("servermsg", &serverMsg)
320	getconfig("aboutmsg", &aboutMsg)
321	getconfig("loginmsg", &loginMsg)
322	getconfig("servername", &serverName)
323	getconfig("masqname", &masqName)
324	if masqName == "" {
325		masqName = serverName
326	}
327	serverPrefix = fmt.Sprintf("https://%s/", serverName)
328	getconfig("usersep", &userSep)
329	getconfig("honksep", &honkSep)
330	getconfig("devel", &develMode)
331	getconfig("fasttimeout", &fastTimeout)
332	getconfig("slowtimeout", &slowTimeout)
333	getconfig("signgets", &signGets)
334	prepareStatements(db)
335	switch cmd {
336	case "admin":
337		adminscreen()
338	case "import":
339		if len(args) != 4 {
340			elog.Fatal("import username mastodon|twitter srcdir")
341		}
342		importMain(args[1], args[2], args[3])
343	case "devel":
344		if len(args) != 2 {
345			elog.Fatal("need an argument: devel (on|off)")
346		}
347		switch args[1] {
348		case "on":
349			setconfig("devel", 1)
350		case "off":
351			setconfig("devel", 0)
352		default:
353			elog.Fatal("argument must be on or off")
354		}
355	case "setconfig":
356		if len(args) != 3 {
357			elog.Fatal("need an argument: setconfig key val")
358		}
359		var val interface{}
360		var err error
361		if val, err = strconv.Atoi(args[2]); err != nil {
362			val = args[2]
363		}
364		setconfig(args[1], val)
365	case "adduser":
366		adduser()
367	case "deluser":
368		if len(args) < 2 {
369			fmt.Printf("usage: honk deluser username\n")
370			return
371		}
372		deluser(args[1])
373	case "chpass":
374		if len(args) < 2 {
375			fmt.Printf("usage: honk chpass username\n")
376			return
377		}
378		chpass(args[1])
379	case "follow":
380		if len(args) < 3 {
381			fmt.Printf("usage: honk follow username url\n")
382			return
383		}
384		user, err := butwhatabout(args[1])
385		if err != nil {
386			fmt.Printf("user not found\n")
387			return
388		}
389		var meta HonkerMeta
390		mj, _ := jsonify(&meta)
391		honkerid, err := savehonker(user, args[2], "", "presub", "", mj)
392		if err != nil {
393			fmt.Printf("had some trouble with that: %s\n", err)
394			return
395		}
396		followyou(user, honkerid, true)
397	case "unfollow":
398		if len(args) < 3 {
399			fmt.Printf("usage: honk unfollow username url\n")
400			return
401		}
402		user, err := butwhatabout(args[1])
403		if err != nil {
404			fmt.Printf("user not found\n")
405			return
406		}
407		row := db.QueryRow("select honkerid from honkers where xid = ? and userid = ? and flavor in ('sub')", args[2], user.ID)
408		var honkerid int64
409		err = row.Scan(&honkerid)
410		if err != nil {
411			fmt.Printf("sorry couldn't find them\n")
412			return
413		}
414		unfollowyou(user, honkerid, true)
415	case "cleanup":
416		arg := "30"
417		if len(args) > 1 {
418			arg = args[1]
419		}
420		cleanupdb(arg)
421	case "unplug":
422		if len(args) < 2 {
423			fmt.Printf("usage: honk unplug servername\n")
424			return
425		}
426		name := args[1]
427		unplugserver(name)
428	case "backup":
429		if len(args) < 2 {
430			fmt.Printf("usage: honk backup dirname\n")
431			return
432		}
433		name := args[1]
434		svalbard(name)
435	case "ping":
436		if len(args) < 3 {
437			fmt.Printf("usage: honk ping (from username) (to username or url)\n")
438			return
439		}
440		name := args[1]
441		targ := args[2]
442		user, err := butwhatabout(name)
443		if err != nil {
444			elog.Printf("unknown user")
445			return
446		}
447		ping(user, targ)
448	case "run":
449		serve()
450	case "backend":
451		backendServer()
452	case "test":
453		ElaborateUnitTests()
454	default:
455		elog.Fatal("unknown command")
456	}
457}