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