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