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 prepareStatements(db)
326 switch cmd {
327 case "admin":
328 adminscreen()
329 case "import":
330 if len(args) != 4 {
331 elog.Fatal("import username mastodon|twitter srcdir")
332 }
333 importMain(args[1], args[2], args[3])
334 case "devel":
335 if len(args) != 2 {
336 elog.Fatal("need an argument: devel (on|off)")
337 }
338 switch args[1] {
339 case "on":
340 setconfig("devel", 1)
341 case "off":
342 setconfig("devel", 0)
343 default:
344 elog.Fatal("argument must be on or off")
345 }
346 case "adduser":
347 adduser()
348 case "deluser":
349 if len(args) < 2 {
350 fmt.Printf("usage: honk deluser username\n")
351 return
352 }
353 deluser(args[1])
354 case "chpass":
355 chpass()
356 case "cleanup":
357 arg := "30"
358 if len(args) > 1 {
359 arg = args[1]
360 }
361 cleanupdb(arg)
362 case "unplug":
363 if len(args) < 2 {
364 fmt.Printf("usage: honk unplug servername\n")
365 return
366 }
367 name := args[1]
368 unplugserver(name)
369 case "backup":
370 if len(args) < 2 {
371 fmt.Printf("usage: honk backup dirname\n")
372 return
373 }
374 name := args[1]
375 svalbard(name)
376 case "ping":
377 if len(args) < 3 {
378 fmt.Printf("usage: honk ping (from username) (to username or url)\n")
379 return
380 }
381 name := args[1]
382 targ := args[2]
383 user, err := butwhatabout(name)
384 if err != nil {
385 elog.Printf("unknown user")
386 return
387 }
388 ping(user, targ)
389 case "run":
390 serve()
391 case "backend":
392 backendServer()
393 case "test":
394 ElaborateUnitTests()
395 default:
396 elog.Fatal("unknown command")
397 }
398}