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