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