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