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