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