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