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