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