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 main() {
263 flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
264 flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
265 flag.Parse()
266
267 if alllogname != "stderr" {
268 elogname = alllogname
269 ilogname = alllogname
270 dlogname = alllogname
271 }
272 initLogging(elogname, ilogname, dlogname)
273
274 args := flag.Args()
275 cmd := "run"
276 if len(args) > 0 {
277 cmd = args[0]
278 }
279 switch cmd {
280 case "init":
281 initdb()
282 case "upgrade":
283 upgradedb()
284 case "version":
285 fmt.Println(softwareVersion)
286 os.Exit(0)
287 }
288 db := opendatabase()
289 dbversion := 0
290 getconfig("dbversion", &dbversion)
291 if dbversion != myVersion {
292 elog.Fatal("incorrect database version. run upgrade.")
293 }
294 getconfig("servermsg", &serverMsg)
295 getconfig("aboutmsg", &aboutMsg)
296 getconfig("loginmsg", &loginMsg)
297 getconfig("servername", &serverName)
298 getconfig("masqname", &masqName)
299 if masqName == "" {
300 masqName = serverName
301 }
302 serverPrefix = fmt.Sprintf("https://%s/", serverName)
303 getconfig("usersep", &userSep)
304 getconfig("honksep", &honkSep)
305 getconfig("debug", &debugMode)
306 prepareStatements(db)
307 switch cmd {
308 case "admin":
309 adminscreen()
310 case "import":
311 if len(args) != 4 {
312 elog.Fatal("import username mastodon|twitter srcdir")
313 }
314 importMain(args[1], args[2], args[3])
315 case "debug":
316 if len(args) != 2 {
317 elog.Fatal("need an argument: debug (on|off)")
318 }
319 switch args[1] {
320 case "on":
321 setconfig("debug", 1)
322 case "off":
323 setconfig("debug", 0)
324 default:
325 elog.Fatal("argument must be on or off")
326 }
327 case "adduser":
328 adduser()
329 case "deluser":
330 if len(args) < 2 {
331 fmt.Printf("usage: honk deluser username\n")
332 return
333 }
334 deluser(args[1])
335 case "chpass":
336 chpass()
337 case "cleanup":
338 arg := "30"
339 if len(args) > 1 {
340 arg = args[1]
341 }
342 cleanupdb(arg)
343 case "unplug":
344 if len(args) < 2 {
345 fmt.Printf("usage: honk unplug servername\n")
346 return
347 }
348 name := args[1]
349 unplugserver(name)
350 case "backup":
351 if len(args) < 2 {
352 fmt.Printf("usage: honk backup dirname\n")
353 return
354 }
355 name := args[1]
356 svalbard(name)
357 case "ping":
358 if len(args) < 3 {
359 fmt.Printf("usage: honk ping (from username) (to username or url)\n")
360 return
361 }
362 name := args[1]
363 targ := args[2]
364 user, err := butwhatabout(name)
365 if err != nil {
366 elog.Printf("unknown user")
367 return
368 }
369 ping(user, targ)
370 case "run":
371 serve()
372 case "backend":
373 backendServer()
374 case "test":
375 ElaborateUnitTests()
376 default:
377 elog.Fatal("unknown command")
378 }
379}