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