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