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 getconfig("debug", &debugMode)
264 prepareStatements(db)
265 switch cmd {
266 case "admin":
267 adminscreen()
268 case "import":
269 if len(args) != 4 {
270 log.Fatal("import username mastodon|twitter srcdir")
271 }
272 importMain(args[1], args[2], args[3])
273 case "debug":
274 if len(args) != 2 {
275 log.Fatal("need an argument: debug (on|off)")
276 }
277 switch args[1] {
278 case "on":
279 setconfig("debug", 1)
280 case "off":
281 setconfig("debug", 0)
282 default:
283 log.Fatal("argument must be on or off")
284 }
285 case "adduser":
286 adduser()
287 case "deluser":
288 if len(args) < 2 {
289 fmt.Printf("usage: honk deluser username\n")
290 return
291 }
292 deluser(args[1])
293 case "chpass":
294 chpass()
295 case "cleanup":
296 arg := "30"
297 if len(args) > 1 {
298 arg = args[1]
299 }
300 cleanupdb(arg)
301 case "unplug":
302 if len(args) < 2 {
303 fmt.Printf("usage: honk unplug servername\n")
304 return
305 }
306 name := args[1]
307 unplugserver(name)
308 case "ping":
309 if len(args) < 3 {
310 fmt.Printf("usage: honk ping from to\n")
311 return
312 }
313 name := args[1]
314 targ := args[2]
315 user, err := butwhatabout(name)
316 if err != nil {
317 log.Printf("unknown user")
318 return
319 }
320 ping(user, targ)
321 case "run":
322 serve()
323 case "backend":
324 backendServer()
325 case "test":
326 ElaborateUnitTests()
327 default:
328 log.Fatal("unknown command")
329 }
330}