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