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