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