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 External bool
167}
168
169type Place struct {
170 Name string
171 Latitude float64
172 Longitude float64
173 Url string
174}
175
176type Duration int64
177
178func (d Duration) String() string {
179 s := time.Duration(d).String()
180 if strings.HasSuffix(s, "m0s") {
181 s = s[:len(s)-2]
182 }
183 if strings.HasSuffix(s, "h0m") {
184 s = s[:len(s)-2]
185 }
186 return s
187}
188
189func parseDuration(s string) time.Duration {
190 didx := strings.IndexByte(s, 'd')
191 if didx != -1 {
192 days, _ := strconv.ParseInt(s[:didx], 10, 0)
193 dur, _ := time.ParseDuration(s[didx:])
194 return dur + 24*time.Hour*time.Duration(days)
195 }
196 dur, _ := time.ParseDuration(s)
197 return dur
198}
199
200type Time struct {
201 StartTime time.Time
202 EndTime time.Time
203 Duration Duration
204}
205
206type Honker struct {
207 ID int64
208 UserID int64
209 Name string
210 XID string
211 Handle string
212 Flavor string
213 Combos []string
214 Meta HonkerMeta
215}
216
217type HonkerMeta struct {
218 Notes string
219}
220
221type SomeThing struct {
222 What int
223 XID string
224 Owner string
225 Name string
226}
227
228const (
229 SomeNothing int = iota
230 SomeActor
231 SomeCollection
232)
233
234var serverName string
235var serverPrefix string
236var masqName string
237var dataDir = "."
238var viewDir = "."
239var iconName = "icon.png"
240var serverMsg template.HTML
241var aboutMsg template.HTML
242var loginMsg template.HTML
243
244func ElaborateUnitTests() {
245}
246
247func unplugserver(hostname string) {
248 db := opendatabase()
249 xid := fmt.Sprintf("%%https://%s/%%", hostname)
250 db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
251 db.Exec("delete from doovers where rcpt like ?", xid)
252}
253
254func main() {
255 flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
256 flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
257 flag.Parse()
258 args := flag.Args()
259 cmd := "run"
260 if len(args) > 0 {
261 cmd = args[0]
262 }
263 switch cmd {
264 case "init":
265 initdb()
266 case "upgrade":
267 upgradedb()
268 case "version":
269 fmt.Println(softwareVersion)
270 os.Exit(0)
271 }
272 db := opendatabase()
273 dbversion := 0
274 getconfig("dbversion", &dbversion)
275 if dbversion != myVersion {
276 log.Fatal("incorrect database version. run upgrade.")
277 }
278 getconfig("servermsg", &serverMsg)
279 getconfig("aboutmsg", &aboutMsg)
280 getconfig("loginmsg", &loginMsg)
281 getconfig("servername", &serverName)
282 getconfig("masqname", &masqName)
283 if masqName == "" {
284 masqName = serverName
285 }
286 serverPrefix = fmt.Sprintf("https://%s/", serverName)
287 getconfig("usersep", &userSep)
288 getconfig("honksep", &honkSep)
289 getconfig("debug", &debugMode)
290 prepareStatements(db)
291 switch cmd {
292 case "admin":
293 adminscreen()
294 case "import":
295 if len(args) != 4 {
296 log.Fatal("import username mastodon|twitter srcdir")
297 }
298 importMain(args[1], args[2], args[3])
299 case "debug":
300 if len(args) != 2 {
301 log.Fatal("need an argument: debug (on|off)")
302 }
303 switch args[1] {
304 case "on":
305 setconfig("debug", 1)
306 case "off":
307 setconfig("debug", 0)
308 default:
309 log.Fatal("argument must be on or off")
310 }
311 case "adduser":
312 adduser()
313 case "deluser":
314 if len(args) < 2 {
315 fmt.Printf("usage: honk deluser username\n")
316 return
317 }
318 deluser(args[1])
319 case "chpass":
320 chpass()
321 case "cleanup":
322 arg := "30"
323 if len(args) > 1 {
324 arg = args[1]
325 }
326 cleanupdb(arg)
327 case "unplug":
328 if len(args) < 2 {
329 fmt.Printf("usage: honk unplug servername\n")
330 return
331 }
332 name := args[1]
333 unplugserver(name)
334 case "backup":
335 if len(args) < 2 {
336 fmt.Printf("usage: honk backup dirname\n")
337 return
338 }
339 name := args[1]
340 svalbard(name)
341 case "ping":
342 if len(args) < 3 {
343 fmt.Printf("usage: honk ping (from username) (to username or url)\n")
344 return
345 }
346 name := args[1]
347 targ := args[2]
348 user, err := butwhatabout(name)
349 if err != nil {
350 log.Printf("unknown user")
351 return
352 }
353 ping(user, targ)
354 case "run":
355 serve()
356 case "backend":
357 backendServer()
358 case "test":
359 ElaborateUnitTests()
360 default:
361 log.Fatal("unknown command")
362 }
363}