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