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 flagIsWonked = 32
163)
164
165func (honk *Honk) IsAcked() bool {
166 return honk.Flags&flagIsAcked != 0
167}
168
169func (honk *Honk) IsBonked() bool {
170 return honk.Flags&flagIsBonked != 0
171}
172
173func (honk *Honk) IsSaved() bool {
174 return honk.Flags&flagIsSaved != 0
175}
176
177func (honk *Honk) IsUntagged() bool {
178 return honk.Flags&flagIsUntagged != 0
179}
180
181func (honk *Honk) IsReacted() bool {
182 return honk.Flags&flagIsReacted != 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 if len(args) < 2 {
376 fmt.Printf("usage: honk chpass username\n")
377 return
378 }
379 chpass(args[1])
380 case "follow":
381 if len(args) < 3 {
382 fmt.Printf("usage: honk follow username url\n")
383 return
384 }
385 user, err := butwhatabout(args[1])
386 if err != nil {
387 fmt.Printf("user not found\n")
388 return
389 }
390 var meta HonkerMeta
391 mj, _ := jsonify(&meta)
392 honkerid, err := savehonker(user, args[2], "", "presub", "", mj)
393 if err != nil {
394 fmt.Printf("had some trouble with that: %s\n", err)
395 return
396 }
397 followyou(user, honkerid, true)
398 case "unfollow":
399 if len(args) < 3 {
400 fmt.Printf("usage: honk unfollow username url\n")
401 return
402 }
403 user, err := butwhatabout(args[1])
404 if err != nil {
405 fmt.Printf("user not found\n")
406 return
407 }
408 row := db.QueryRow("select honkerid from honkers where xid = ? and userid = ? and flavor in ('sub')", args[2], user.ID)
409 var honkerid int64
410 err = row.Scan(&honkerid)
411 if err != nil {
412 fmt.Printf("sorry couldn't find them\n")
413 return
414 }
415 unfollowyou(user, honkerid, true)
416 case "cleanup":
417 arg := "30"
418 if len(args) > 1 {
419 arg = args[1]
420 }
421 cleanupdb(arg)
422 case "unplug":
423 if len(args) < 2 {
424 fmt.Printf("usage: honk unplug servername\n")
425 return
426 }
427 name := args[1]
428 unplugserver(name)
429 case "backup":
430 if len(args) < 2 {
431 fmt.Printf("usage: honk backup dirname\n")
432 return
433 }
434 name := args[1]
435 svalbard(name)
436 case "ping":
437 if len(args) < 3 {
438 fmt.Printf("usage: honk ping (from username) (to username or url)\n")
439 return
440 }
441 name := args[1]
442 targ := args[2]
443 user, err := butwhatabout(name)
444 if err != nil {
445 elog.Printf("unknown user")
446 return
447 }
448 ping(user, targ)
449 case "run":
450 serve()
451 case "backend":
452 backendServer()
453 case "test":
454 ElaborateUnitTests()
455 default:
456 elog.Fatal("unknown command")
457 }
458}