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 "crypto/rsa"
20 "flag"
21 "fmt"
22 "html/template"
23 "log"
24 notrand "math/rand"
25 "os"
26 "strconv"
27 "strings"
28 "time"
29)
30
31var softwareVersion = "develop"
32
33func init() {
34 notrand.Seed(time.Now().Unix())
35}
36
37type WhatAbout struct {
38 ID int64
39 Name string
40 Display string
41 About string
42 Key string
43 URL string
44 Options UserOptions
45 SecKey *rsa.PrivateKey
46}
47
48type UserOptions struct {
49 SkinnyCSS bool `json:",omitempty"`
50 OmitImages bool `json:",omitempty"`
51 Avatar string `json:",omitempty"`
52 MapLink string `json:",omitempty"`
53}
54
55type KeyInfo struct {
56 keyname string
57 seckey *rsa.PrivateKey
58}
59
60const serverUID int64 = -2
61
62type Honk struct {
63 ID int64
64 UserID int64
65 Username string
66 What string
67 Honker string
68 Handle string
69 Oonker string
70 Oondle string
71 XID string
72 RID string
73 Date time.Time
74 URL string
75 Noise string
76 Precis string
77 Format string
78 Convoy string
79 Audience []string
80 Public bool
81 Whofore int64
82 Replies []*Honk
83 Flags int64
84 HTPrecis template.HTML
85 HTML template.HTML
86 Style string
87 Open string
88 Donks []*Donk
89 Onts []string
90 Place *Place
91 Time *Time
92 Mentions []Mention
93}
94
95type Mention struct {
96 Who string
97 Where string
98}
99
100type OldRevision struct {
101 Precis string
102 Noise string
103}
104
105const (
106 flagIsAcked = 1
107 flagIsBonked = 2
108 flagIsSaved = 4
109 flagIsUntagged = 8
110)
111
112func (honk *Honk) IsAcked() bool {
113 return honk.Flags&flagIsAcked != 0
114}
115
116func (honk *Honk) IsBonked() bool {
117 return honk.Flags&flagIsBonked != 0
118}
119
120func (honk *Honk) IsSaved() bool {
121 return honk.Flags&flagIsSaved != 0
122}
123
124func (honk *Honk) IsUntagged() bool {
125 return honk.Flags&flagIsUntagged != 0
126}
127
128type Donk struct {
129 FileID int64
130 XID string
131 Name string
132 Desc string
133 URL string
134 Media string
135 Local bool
136}
137
138type Place struct {
139 Name string
140 Latitude float64
141 Longitude float64
142 Url string
143}
144
145type Duration int64
146
147func (d Duration) String() string {
148 s := time.Duration(d).String()
149 if strings.HasSuffix(s, "m0s") {
150 s = s[:len(s)-2]
151 }
152 if strings.HasSuffix(s, "h0m") {
153 s = s[:len(s)-2]
154 }
155 return s
156}
157
158func parseDuration(s string) time.Duration {
159 didx := strings.IndexByte(s, 'd')
160 if didx != -1 {
161 days, _ := strconv.ParseInt(s[:didx], 10, 0)
162 dur, _ := time.ParseDuration(s[didx:])
163 return dur + 24*time.Hour*time.Duration(days)
164 }
165 dur, _ := time.ParseDuration(s)
166 return dur
167}
168
169type Time struct {
170 StartTime time.Time
171 EndTime time.Time
172 Duration Duration
173}
174
175type Honker struct {
176 ID int64
177 UserID int64
178 Name string
179 XID string
180 Handle string
181 Flavor string
182 Combos []string
183 Meta HonkerMeta
184}
185
186type HonkerMeta struct {
187 Notes string
188}
189
190type SomeThing struct {
191 What int
192 XID string
193 Owner string
194 Name string
195}
196
197const (
198 SomeNothing int = iota
199 SomeActor
200 SomeCollection
201)
202
203var serverName string
204var dataDir = "."
205var viewDir = "."
206var iconName = "icon.png"
207var serverMsg template.HTML
208var aboutMsg template.HTML
209var loginMsg template.HTML
210
211func ElaborateUnitTests() {
212}
213
214func unplugserver(hostname string) {
215 db := opendatabase()
216 xid := fmt.Sprintf("%%https://%s/%%", hostname)
217 db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
218 db.Exec("delete from doovers where rcpt like ?", xid)
219}
220
221func main() {
222 flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
223 flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
224 flag.Parse()
225 args := flag.Args()
226 cmd := "run"
227 if len(args) > 0 {
228 cmd = args[0]
229 }
230 switch cmd {
231 case "init":
232 initdb()
233 case "upgrade":
234 upgradedb()
235 case "version":
236 fmt.Println(softwareVersion)
237 os.Exit(0)
238 }
239 db := opendatabase()
240 dbversion := 0
241 getconfig("dbversion", &dbversion)
242 if dbversion != myVersion {
243 log.Fatal("incorrect database version. run upgrade.")
244 }
245 getconfig("servermsg", &serverMsg)
246 getconfig("aboutmsg", &aboutMsg)
247 getconfig("loginmsg", &loginMsg)
248 getconfig("servername", &serverName)
249 getconfig("usersep", &userSep)
250 getconfig("honksep", &honkSep)
251 prepareStatements(db)
252 switch cmd {
253 case "admin":
254 adminscreen()
255 case "import":
256 if len(args) != 4 {
257 log.Fatal("import username mastodon|twitter srcdir")
258 }
259 importMain(args[1], args[2], args[3])
260 case "debug":
261 if len(args) != 2 {
262 log.Fatal("need an argument: debug (on|off)")
263 }
264 switch args[1] {
265 case "on":
266 setconfig("debug", 1)
267 case "off":
268 setconfig("debug", 0)
269 default:
270 log.Fatal("argument must be on or off")
271 }
272 case "adduser":
273 adduser()
274 case "deluser":
275 if len(args) < 2 {
276 fmt.Printf("usage: honk deluser username\n")
277 return
278 }
279 deluser(args[1])
280 case "chpass":
281 chpass()
282 case "cleanup":
283 arg := "30"
284 if len(args) > 1 {
285 arg = args[1]
286 }
287 cleanupdb(arg)
288 case "unplug":
289 if len(args) < 2 {
290 fmt.Printf("usage: honk unplug servername\n")
291 return
292 }
293 name := args[1]
294 unplugserver(name)
295 case "ping":
296 if len(args) < 3 {
297 fmt.Printf("usage: honk ping from to\n")
298 return
299 }
300 name := args[1]
301 targ := args[2]
302 user, err := butwhatabout(name)
303 if err != nil {
304 log.Printf("unknown user")
305 return
306 }
307 ping(user, targ)
308 case "run":
309 serve()
310 case "backend":
311 backendServer()
312 case "test":
313 ElaborateUnitTests()
314 default:
315 log.Fatal("unknown command")
316 }
317}