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 dataDir = "."
212var viewDir = "."
213var iconName = "icon.png"
214var serverMsg template.HTML
215var aboutMsg template.HTML
216var loginMsg template.HTML
217
218func ElaborateUnitTests() {
219}
220
221func unplugserver(hostname string) {
222 db := opendatabase()
223 xid := fmt.Sprintf("%%https://%s/%%", hostname)
224 db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
225 db.Exec("delete from doovers where rcpt like ?", xid)
226}
227
228func main() {
229 flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
230 flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
231 flag.Parse()
232 args := flag.Args()
233 cmd := "run"
234 if len(args) > 0 {
235 cmd = args[0]
236 }
237 switch cmd {
238 case "init":
239 initdb()
240 case "upgrade":
241 upgradedb()
242 case "version":
243 fmt.Println(softwareVersion)
244 os.Exit(0)
245 }
246 db := opendatabase()
247 dbversion := 0
248 getconfig("dbversion", &dbversion)
249 if dbversion != myVersion {
250 log.Fatal("incorrect database version. run upgrade.")
251 }
252 getconfig("servermsg", &serverMsg)
253 getconfig("aboutmsg", &aboutMsg)
254 getconfig("loginmsg", &loginMsg)
255 getconfig("servername", &serverName)
256 getconfig("usersep", &userSep)
257 getconfig("honksep", &honkSep)
258 prepareStatements(db)
259 switch cmd {
260 case "admin":
261 adminscreen()
262 case "import":
263 if len(args) != 4 {
264 log.Fatal("import username mastodon|twitter srcdir")
265 }
266 importMain(args[1], args[2], args[3])
267 case "debug":
268 if len(args) != 2 {
269 log.Fatal("need an argument: debug (on|off)")
270 }
271 switch args[1] {
272 case "on":
273 setconfig("debug", 1)
274 case "off":
275 setconfig("debug", 0)
276 default:
277 log.Fatal("argument must be on or off")
278 }
279 case "adduser":
280 adduser()
281 case "deluser":
282 if len(args) < 2 {
283 fmt.Printf("usage: honk deluser username\n")
284 return
285 }
286 deluser(args[1])
287 case "chpass":
288 chpass()
289 case "cleanup":
290 arg := "30"
291 if len(args) > 1 {
292 arg = args[1]
293 }
294 cleanupdb(arg)
295 case "unplug":
296 if len(args) < 2 {
297 fmt.Printf("usage: honk unplug servername\n")
298 return
299 }
300 name := args[1]
301 unplugserver(name)
302 case "ping":
303 if len(args) < 3 {
304 fmt.Printf("usage: honk ping from to\n")
305 return
306 }
307 name := args[1]
308 targ := args[2]
309 user, err := butwhatabout(name)
310 if err != nil {
311 log.Printf("unknown user")
312 return
313 }
314 ping(user, targ)
315 case "run":
316 serve()
317 case "backend":
318 backendServer()
319 case "test":
320 ElaborateUnitTests()
321 default:
322 log.Fatal("unknown command")
323 }
324}