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