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