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 "bytes"
20 "database/sql"
21 "fmt"
22 "html"
23 "html/template"
24 "io"
25 "log"
26 notrand "math/rand"
27 "net/http"
28 "os"
29 "sort"
30 "strconv"
31 "strings"
32 "time"
33
34 "github.com/gorilla/mux"
35 "humungus.tedunangst.com/r/webs/htfilter"
36 "humungus.tedunangst.com/r/webs/image"
37 "humungus.tedunangst.com/r/webs/login"
38 "humungus.tedunangst.com/r/webs/rss"
39 "humungus.tedunangst.com/r/webs/templates"
40)
41
42type WhatAbout struct {
43 ID int64
44 Name string
45 Display string
46 About string
47 Key string
48 URL string
49}
50
51type Honk struct {
52 ID int64
53 UserID int64
54 Username string
55 What string
56 Honker string
57 Oonker string
58 XID string
59 RID string
60 Date time.Time
61 URL string
62 Noise string
63 Precis string
64 Convoy string
65 Audience []string
66 Privacy string
67 Whofore int64
68 HTML template.HTML
69 Donks []*Donk
70}
71
72type Donk struct {
73 FileID int64
74 XID string
75 Name string
76 URL string
77 Media string
78 Content []byte
79}
80
81type Honker struct {
82 ID int64
83 UserID int64
84 Name string
85 XID string
86 Flavor string
87 Combos []string
88}
89
90var serverName string
91var iconName = "icon.png"
92
93var readviews *templates.Template
94
95func getInfo(r *http.Request) map[string]interface{} {
96 templinfo := make(map[string]interface{})
97 templinfo["StyleParam"] = getstyleparam("views/style.css")
98 templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
99 templinfo["ServerName"] = serverName
100 templinfo["IconName"] = iconName
101 templinfo["UserInfo"] = login.GetUserInfo(r)
102 return templinfo
103}
104
105func homepage(w http.ResponseWriter, r *http.Request) {
106 templinfo := getInfo(r)
107 u := login.GetUserInfo(r)
108 var honks []*Honk
109 if u != nil {
110 if r.URL.Path == "/atme" {
111 honks = gethonksforme(u.UserID)
112 } else {
113 honks = gethonksforuser(u.UserID)
114 }
115 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
116 } else {
117 honks = getpublichonks()
118 }
119
120 var modtime time.Time
121 if len(honks) > 0 {
122 modtime = honks[0].Date
123 }
124 debug := false
125 getconfig("debug", &debug)
126 imh := r.Header.Get("If-Modified-Since")
127 if !debug && imh != "" && !modtime.IsZero() {
128 ifmod, err := time.Parse(http.TimeFormat, imh)
129 if err == nil && !modtime.After(ifmod) {
130 w.WriteHeader(http.StatusNotModified)
131 return
132 }
133 }
134 reverbolate(honks)
135
136 msg := "Things happen."
137 getconfig("servermsg", &msg)
138 templinfo["Honks"] = honks
139 templinfo["ShowRSS"] = true
140 templinfo["ServerMessage"] = msg
141 if u == nil {
142 w.Header().Set("Cache-Control", "max-age=60")
143 } else {
144 w.Header().Set("Cache-Control", "max-age=0")
145 }
146 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
147 err := readviews.Execute(w, "honkpage.html", templinfo)
148 if err != nil {
149 log.Print(err)
150 }
151}
152
153func showrss(w http.ResponseWriter, r *http.Request) {
154 name := mux.Vars(r)["name"]
155
156 var honks []*Honk
157 if name != "" {
158 honks = gethonksbyuser(name)
159 } else {
160 honks = getpublichonks()
161 }
162 reverbolate(honks)
163
164 home := fmt.Sprintf("https://%s/", serverName)
165 base := home
166 if name != "" {
167 home += "u/" + name
168 name += " "
169 }
170 feed := rss.Feed{
171 Title: name + "honk",
172 Link: home,
173 Description: name + "honk rss",
174 Image: &rss.Image{
175 URL: base + "icon.png",
176 Title: name + "honk rss",
177 Link: home,
178 },
179 }
180 var modtime time.Time
181 for _, honk := range honks {
182 desc := string(honk.HTML)
183 for _, d := range honk.Donks {
184 desc += fmt.Sprintf(`<p><a href="%sd/%s">Attachment: %s</a>`,
185 base, d.XID, html.EscapeString(d.Name))
186 }
187
188 feed.Items = append(feed.Items, &rss.Item{
189 Title: fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
190 Description: rss.CData{desc},
191 Link: honk.URL,
192 PubDate: honk.Date.Format(time.RFC1123),
193 Guid: &rss.Guid{IsPermaLink: true, Value: honk.URL},
194 })
195 if honk.Date.After(modtime) {
196 modtime = honk.Date
197 }
198 }
199 w.Header().Set("Cache-Control", "max-age=300")
200 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
201
202 err := feed.Write(w)
203 if err != nil {
204 log.Printf("error writing rss: %s", err)
205 }
206}
207
208func butwhatabout(name string) (*WhatAbout, error) {
209 row := stmtWhatAbout.QueryRow(name)
210 var user WhatAbout
211 err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key)
212 user.URL = fmt.Sprintf("https://%s/u/%s", serverName, user.Name)
213 return &user, err
214}
215
216func crappola(j map[string]interface{}) bool {
217 t, _ := jsongetstring(j, "type")
218 a, _ := jsongetstring(j, "actor")
219 o, _ := jsongetstring(j, "object")
220 if t == "Delete" && a == o {
221 log.Printf("crappola from %s", a)
222 return true
223 }
224 return false
225}
226
227func ping(user *WhatAbout, who string) {
228 box, err := getboxes(who)
229 if err != nil {
230 log.Printf("no inbox for ping: %s", err)
231 return
232 }
233 j := NewJunk()
234 j["@context"] = itiswhatitis
235 j["type"] = "Ping"
236 j["id"] = user.URL + "/ping/" + xfiltrate()
237 j["actor"] = user.URL
238 j["to"] = who
239 keyname, key := ziggy(user.Name)
240 err = PostJunk(keyname, key, box.In, j)
241 if err != nil {
242 log.Printf("can't send ping: %s", err)
243 return
244 }
245 log.Printf("sent ping to %s: %s", who, j["id"])
246}
247
248func pong(user *WhatAbout, who string, obj string) {
249 box, err := getboxes(who)
250 if err != nil {
251 log.Printf("no inbox for pong %s : %s", who, err)
252 return
253 }
254 j := NewJunk()
255 j["@context"] = itiswhatitis
256 j["type"] = "Pong"
257 j["id"] = user.URL + "/pong/" + xfiltrate()
258 j["actor"] = user.URL
259 j["to"] = who
260 j["object"] = obj
261 keyname, key := ziggy(user.Name)
262 err = PostJunk(keyname, key, box.In, j)
263 if err != nil {
264 log.Printf("can't send pong: %s", err)
265 return
266 }
267}
268
269func inbox(w http.ResponseWriter, r *http.Request) {
270 name := mux.Vars(r)["name"]
271 user, err := butwhatabout(name)
272 if err != nil {
273 http.NotFound(w, r)
274 return
275 }
276 var buf bytes.Buffer
277 io.Copy(&buf, r.Body)
278 payload := buf.Bytes()
279 j, err := ReadJunk(bytes.NewReader(payload))
280 if err != nil {
281 log.Printf("bad payload: %s", err)
282 io.WriteString(os.Stdout, "bad payload\n")
283 os.Stdout.Write(payload)
284 io.WriteString(os.Stdout, "\n")
285 return
286 }
287 if crappola(j) {
288 return
289 }
290 keyname, err := zag(r, payload)
291 if err != nil {
292 log.Printf("inbox message failed signature: %s", err)
293 if keyname != "" {
294 keyname, err = makeitworksomehowwithoutregardforkeycontinuity(keyname, r, payload)
295 }
296 if err != nil {
297 return
298 }
299 }
300 what, _ := jsongetstring(j, "type")
301 if what == "Like" {
302 return
303 }
304 who, _ := jsongetstring(j, "actor")
305 origin := keymatch(keyname, who)
306 if origin == "" {
307 log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
308 return
309 }
310 objid, _ := jsongetstring(j, "id")
311 if thoudostbitethythumb(user.ID, []string{who}, objid) {
312 log.Printf("ignoring thumb sucker %s", who)
313 return
314 }
315 switch what {
316 case "Ping":
317 obj, _ := jsongetstring(j, "id")
318 log.Printf("ping from %s: %s", who, obj)
319 pong(user, who, obj)
320 case "Pong":
321 obj, _ := jsongetstring(j, "object")
322 log.Printf("pong from %s: %s", who, obj)
323 case "Follow":
324 obj, _ := jsongetstring(j, "object")
325 if obj == user.URL {
326 log.Printf("updating honker follow: %s", who)
327 rubadubdub(user, j)
328 } else {
329 log.Printf("can't follow %s", obj)
330 }
331 case "Accept":
332 log.Printf("updating honker accept: %s", who)
333 _, err = stmtUpdateFlavor.Exec("sub", user.ID, who, "presub")
334 if err != nil {
335 log.Printf("error updating honker: %s", err)
336 return
337 }
338 case "Undo":
339 obj, ok := jsongetmap(j, "object")
340 if !ok {
341 log.Printf("unknown undo no object")
342 } else {
343 what, _ := jsongetstring(obj, "type")
344 switch what {
345 case "Follow":
346 log.Printf("updating honker undo: %s", who)
347 _, err = stmtUpdateFlavor.Exec("undub", user.ID, who, "dub")
348 if err != nil {
349 log.Printf("error updating honker: %s", err)
350 return
351 }
352 case "Like":
353 case "Announce":
354 default:
355 log.Printf("unknown undo: %s", what)
356 }
357 }
358 default:
359 go consumeactivity(user, j, origin)
360 }
361}
362
363func outbox(w http.ResponseWriter, r *http.Request) {
364 name := mux.Vars(r)["name"]
365 user, err := butwhatabout(name)
366 if err != nil {
367 http.NotFound(w, r)
368 return
369 }
370 honks := gethonksbyuser(name)
371
372 var jonks []map[string]interface{}
373 for _, h := range honks {
374 j, _ := jonkjonk(user, h)
375 jonks = append(jonks, j)
376 }
377
378 j := NewJunk()
379 j["@context"] = itiswhatitis
380 j["id"] = user.URL + "/outbox"
381 j["type"] = "OrderedCollection"
382 j["totalItems"] = len(jonks)
383 j["orderedItems"] = jonks
384
385 w.Header().Set("Cache-Control", "max-age=60")
386 w.Header().Set("Content-Type", theonetruename)
387 WriteJunk(w, j)
388}
389
390func emptiness(w http.ResponseWriter, r *http.Request) {
391 name := mux.Vars(r)["name"]
392 user, err := butwhatabout(name)
393 if err != nil {
394 http.NotFound(w, r)
395 return
396 }
397 colname := "/followers"
398 if strings.HasSuffix(r.URL.Path, "/following") {
399 colname = "/following"
400 }
401 j := NewJunk()
402 j["@context"] = itiswhatitis
403 j["id"] = user.URL + colname
404 j["type"] = "OrderedCollection"
405 j["totalItems"] = 0
406 j["orderedItems"] = []interface{}{}
407
408 w.Header().Set("Cache-Control", "max-age=60")
409 w.Header().Set("Content-Type", theonetruename)
410 WriteJunk(w, j)
411}
412
413func showuser(w http.ResponseWriter, r *http.Request) {
414 name := mux.Vars(r)["name"]
415 user, err := butwhatabout(name)
416 if err != nil {
417 http.NotFound(w, r)
418 return
419 }
420 if friendorfoe(r.Header.Get("Accept")) {
421 j := asjonker(user)
422 w.Header().Set("Cache-Control", "max-age=600")
423 w.Header().Set("Content-Type", theonetruename)
424 WriteJunk(w, j)
425 return
426 }
427 honks := gethonksbyuser(name)
428 u := login.GetUserInfo(r)
429 honkpage(w, r, u, user, honks, "")
430}
431
432func showhonker(w http.ResponseWriter, r *http.Request) {
433 name := mux.Vars(r)["name"]
434 u := login.GetUserInfo(r)
435 honks := gethonksbyhonker(u.UserID, name)
436 honkpage(w, r, u, nil, honks, "honks by honker: "+name)
437}
438
439func showcombo(w http.ResponseWriter, r *http.Request) {
440 name := mux.Vars(r)["name"]
441 u := login.GetUserInfo(r)
442 honks := gethonksbycombo(u.UserID, name)
443 honkpage(w, r, u, nil, honks, "honks by combo: "+name)
444}
445func showconvoy(w http.ResponseWriter, r *http.Request) {
446 c := r.FormValue("c")
447 var userid int64 = -1
448 u := login.GetUserInfo(r)
449 if u != nil {
450 userid = u.UserID
451 }
452 honks := gethonksbyconvoy(userid, c)
453 honkpage(w, r, u, nil, honks, "honks in convoy: "+c)
454}
455
456func showhonk(w http.ResponseWriter, r *http.Request) {
457 name := mux.Vars(r)["name"]
458 user, err := butwhatabout(name)
459 if err != nil {
460 http.NotFound(w, r)
461 return
462 }
463 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
464 h := getxonk(user.ID, xid)
465 if h == nil {
466 http.NotFound(w, r)
467 return
468 }
469 if friendorfoe(r.Header.Get("Accept")) {
470 donksforhonks([]*Honk{h})
471 _, j := jonkjonk(user, h)
472 j["@context"] = itiswhatitis
473 w.Header().Set("Cache-Control", "max-age=3600")
474 w.Header().Set("Content-Type", theonetruename)
475 WriteJunk(w, j)
476 return
477 }
478 honks := gethonksbyconvoy(-1, h.Convoy)
479 for _, hh := range honks {
480 if hh.XID != h.XID {
481 hh.Privacy = "limited"
482 }
483 }
484 u := login.GetUserInfo(r)
485 honkpage(w, r, u, nil, honks, "one honk maybe more")
486}
487
488func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
489 honks []*Honk, infomsg string) {
490 reverbolate(honks)
491 templinfo := getInfo(r)
492 if u != nil {
493 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
494 }
495 if u == nil {
496 w.Header().Set("Cache-Control", "max-age=60")
497 }
498 if user != nil {
499 filt := htfilter.New()
500 templinfo["Name"] = user.Name
501 whatabout := user.About
502 whatabout = obfusbreak(user.About)
503 templinfo["WhatAbout"], _ = filt.String(whatabout)
504 }
505 templinfo["Honks"] = honks
506 templinfo["ServerMessage"] = infomsg
507 err := readviews.Execute(w, "honkpage.html", templinfo)
508 if err != nil {
509 log.Print(err)
510 }
511}
512
513func saveuser(w http.ResponseWriter, r *http.Request) {
514 whatabout := r.FormValue("whatabout")
515 u := login.GetUserInfo(r)
516 db := opendatabase()
517 _, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
518 if err != nil {
519 log.Printf("error bouting what: %s", err)
520 }
521
522 http.Redirect(w, r, "/account", http.StatusSeeOther)
523}
524
525func gethonkers(userid int64) []*Honker {
526 rows, err := stmtHonkers.Query(userid)
527 if err != nil {
528 log.Printf("error querying honkers: %s", err)
529 return nil
530 }
531 defer rows.Close()
532 var honkers []*Honker
533 for rows.Next() {
534 var f Honker
535 var combos string
536 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
537 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
538 if err != nil {
539 log.Printf("error scanning honker: %s", err)
540 return nil
541 }
542 honkers = append(honkers, &f)
543 }
544 return honkers
545}
546
547func getdubs(userid int64) []*Honker {
548 rows, err := stmtDubbers.Query(userid)
549 if err != nil {
550 log.Printf("error querying dubs: %s", err)
551 return nil
552 }
553 defer rows.Close()
554 var honkers []*Honker
555 for rows.Next() {
556 var f Honker
557 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
558 if err != nil {
559 log.Printf("error scanning honker: %s", err)
560 return nil
561 }
562 honkers = append(honkers, &f)
563 }
564 return honkers
565}
566
567func allusers() []login.UserInfo {
568 var users []login.UserInfo
569 rows, _ := opendatabase().Query("select userid, username from users")
570 defer rows.Close()
571 for rows.Next() {
572 var u login.UserInfo
573 rows.Scan(&u.UserID, &u.Username)
574 users = append(users, u)
575 }
576 return users
577}
578
579func getxonk(userid int64, xid string) *Honk {
580 h := new(Honk)
581 var dt, aud string
582 row := stmtOneXonk.QueryRow(userid, xid)
583 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
584 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore)
585 if err != nil {
586 if err != sql.ErrNoRows {
587 log.Printf("error scanning xonk: %s", err)
588 }
589 return nil
590 }
591 h.Date, _ = time.Parse(dbtimeformat, dt)
592 h.Audience = strings.Split(aud, " ")
593 return h
594}
595
596func getpublichonks() []*Honk {
597 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
598 rows, err := stmtPublicHonks.Query(dt)
599 return getsomehonks(rows, err)
600}
601func gethonksbyuser(name string) []*Honk {
602 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
603 rows, err := stmtUserHonks.Query(name, dt)
604 return getsomehonks(rows, err)
605}
606func gethonksforuser(userid int64) []*Honk {
607 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
608 rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
609 return getsomehonks(rows, err)
610}
611func gethonksforme(userid int64) []*Honk {
612 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
613 rows, err := stmtHonksForMe.Query(userid, dt, userid)
614 return getsomehonks(rows, err)
615}
616func gethonksbyhonker(userid int64, honker string) []*Honk {
617 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
618 return getsomehonks(rows, err)
619}
620func gethonksbycombo(userid int64, combo string) []*Honk {
621 combo = "% " + combo + " %"
622 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
623 return getsomehonks(rows, err)
624}
625func gethonksbyconvoy(userid int64, convoy string) []*Honk {
626 rows, err := stmtHonksByConvoy.Query(userid, convoy)
627 honks := getsomehonks(rows, err)
628 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
629 honks[i], honks[j] = honks[j], honks[i]
630 }
631 return honks
632}
633
634func getsomehonks(rows *sql.Rows, err error) []*Honk {
635 if err != nil {
636 log.Printf("error querying honks: %s", err)
637 return nil
638 }
639 defer rows.Close()
640 var honks []*Honk
641 for rows.Next() {
642 var h Honk
643 var dt, aud string
644 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker,
645 &h.XID, &h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore)
646 if err != nil {
647 log.Printf("error scanning honks: %s", err)
648 return nil
649 }
650 h.Date, _ = time.Parse(dbtimeformat, dt)
651 h.Audience = strings.Split(aud, " ")
652 h.Privacy = "limited"
653 for _, a := range h.Audience {
654 if a == thewholeworld {
655 h.Privacy = ""
656 break
657 }
658 }
659 honks = append(honks, &h)
660 }
661 rows.Close()
662 donksforhonks(honks)
663 return honks
664}
665
666func donksforhonks(honks []*Honk) {
667 db := opendatabase()
668 var ids []string
669 hmap := make(map[int64]*Honk)
670 for _, h := range honks {
671 ids = append(ids, fmt.Sprintf("%d", h.ID))
672 hmap[h.ID] = h
673 }
674 q := fmt.Sprintf("select honkid, donks.fileid, xid, name, url, media from donks join files on donks.fileid = files.fileid where honkid in (%s)", strings.Join(ids, ","))
675 rows, err := db.Query(q)
676 if err != nil {
677 log.Printf("error querying donks: %s", err)
678 return
679 }
680 defer rows.Close()
681 for rows.Next() {
682 var hid int64
683 var d Donk
684 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media)
685 if err != nil {
686 log.Printf("error scanning donk: %s", err)
687 continue
688 }
689 h := hmap[hid]
690 h.Donks = append(h.Donks, &d)
691 }
692}
693
694func savebonk(w http.ResponseWriter, r *http.Request) {
695 xid := r.FormValue("xid")
696 userinfo := login.GetUserInfo(r)
697 user, _ := butwhatabout(userinfo.Username)
698
699 log.Printf("bonking %s", xid)
700
701 xonk := getxonk(userinfo.UserID, xid)
702 if xonk == nil {
703 return
704 }
705 donksforhonks([]*Honk{xonk})
706
707 dt := time.Now().UTC()
708 bonk := Honk{
709 UserID: userinfo.UserID,
710 Username: userinfo.Username,
711 What: "bonk",
712 Honker: user.URL,
713 XID: xonk.XID,
714 Date: dt,
715 Donks: xonk.Donks,
716 Audience: []string{thewholeworld},
717 }
718
719 oonker := xonk.Oonker
720 if oonker == "" {
721 oonker = xonk.Honker
722 }
723 aud := strings.Join(bonk.Audience, " ")
724 whofore := 2
725 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
726 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
727 xonk.Precis, oonker)
728 if err != nil {
729 log.Printf("error saving bonk: %s", err)
730 return
731 }
732 bonk.ID, _ = res.LastInsertId()
733 for _, d := range bonk.Donks {
734 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
735 if err != nil {
736 log.Printf("err saving donk: %s", err)
737 return
738 }
739 }
740
741 go honkworldwide(user, &bonk)
742}
743
744func zonkit(w http.ResponseWriter, r *http.Request) {
745 wherefore := r.FormValue("wherefore")
746 var what string
747 switch wherefore {
748 case "this honk":
749 what = r.FormValue("honk")
750 wherefore = "zonk"
751 case "this honker":
752 what = r.FormValue("honker")
753 wherefore = "zonker"
754 case "this convoy":
755 what = r.FormValue("convoy")
756 wherefore = "zonvoy"
757 }
758 if what == "" {
759 return
760 }
761
762 log.Printf("zonking %s %s", wherefore, what)
763 userinfo := login.GetUserInfo(r)
764 if wherefore == "zonk" {
765 xonk := getxonk(userinfo.UserID, what)
766 if xonk != nil {
767 stmtZonkDonks.Exec(xonk.ID)
768 stmtZonkIt.Exec(userinfo.UserID, what)
769 if xonk.Whofore == 2 {
770 zonk := Honk{
771 What: "zonk",
772 XID: xonk.XID,
773 Date: time.Now().UTC(),
774 Audience: oneofakind(xonk.Audience),
775 }
776
777 user, _ := butwhatabout(userinfo.Username)
778 log.Printf("announcing deleted honk: %s", what)
779 go honkworldwide(user, &zonk)
780 }
781 }
782 } else {
783 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
784 if err != nil {
785 log.Printf("error saving zonker: %s", err)
786 return
787 }
788 }
789}
790
791func savehonk(w http.ResponseWriter, r *http.Request) {
792 rid := r.FormValue("rid")
793 noise := r.FormValue("noise")
794
795 userinfo := login.GetUserInfo(r)
796 user, _ := butwhatabout(userinfo.Username)
797
798 dt := time.Now().UTC()
799 xid := fmt.Sprintf("https://%s/u/%s/h/%s", serverName, userinfo.Username, xfiltrate())
800 what := "honk"
801 if rid != "" {
802 what = "tonk"
803 }
804 honk := Honk{
805 UserID: userinfo.UserID,
806 Username: userinfo.Username,
807 What: "honk",
808 Honker: user.URL,
809 XID: xid,
810 Date: dt,
811 }
812 if strings.HasPrefix(noise, "DZ:") {
813 idx := strings.Index(noise, "\n")
814 if idx == -1 {
815 honk.Precis = noise
816 noise = ""
817 } else {
818 honk.Precis = noise[:idx]
819 noise = noise[idx+1:]
820 }
821 }
822 noise = strings.TrimSpace(noise)
823 honk.Precis = strings.TrimSpace(honk.Precis)
824
825 if noise != "" && noise[0] == '@' {
826 honk.Audience = append(grapevine(noise), thewholeworld)
827 } else {
828 honk.Audience = prepend(thewholeworld, grapevine(noise))
829 }
830 var convoy string
831 if rid != "" {
832 xonk := getxonk(userinfo.UserID, rid)
833 if xonk != nil {
834 honk.Audience = append(honk.Audience, xonk.Audience...)
835 convoy = xonk.Convoy
836 } else {
837 xonkaud, c := whosthere(rid)
838 honk.Audience = append(honk.Audience, xonkaud...)
839 convoy = c
840 }
841 honk.RID = rid
842 }
843 if convoy == "" {
844 convoy = "data:,electrichonkytonk-" + xfiltrate()
845 }
846 butnottooloud(honk.Audience)
847 honk.Audience = oneofakind(honk.Audience)
848 noise = obfusbreak(noise)
849 honk.Noise = noise
850 honk.Convoy = convoy
851
852 file, filehdr, err := r.FormFile("donk")
853 if err == nil {
854 var buf bytes.Buffer
855 io.Copy(&buf, file)
856 file.Close()
857 data := buf.Bytes()
858 xid := xfiltrate()
859 var media, name string
860 img, err := image.Vacuum(&buf)
861 if err == nil {
862 data = img.Data
863 format := img.Format
864 media = "image/" + format
865 if format == "jpeg" {
866 format = "jpg"
867 }
868 name = xid + "." + format
869 xid = name
870 } else {
871 maxsize := 100000
872 if len(data) > maxsize {
873 log.Printf("bad image: %s too much text: %d", err, len(data))
874 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
875 return
876 }
877 for i := 0; i < len(data); i++ {
878 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
879 log.Printf("bad image: %s not text: %d", err, data[i])
880 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
881 return
882 }
883 }
884 media = "text/plain"
885 name = filehdr.Filename
886 if name == "" {
887 name = xid + ".txt"
888 }
889 xid += ".txt"
890 }
891 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
892 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
893 if err != nil {
894 log.Printf("unable to save image: %s", err)
895 return
896 }
897 var d Donk
898 d.FileID, _ = res.LastInsertId()
899 d.XID = name
900 d.Name = name
901 d.Media = media
902 d.URL = url
903 honk.Donks = append(honk.Donks, &d)
904 }
905 herd := herdofemus(honk.Noise)
906 for _, e := range herd {
907 donk := savedonk(e.ID, e.Name, "image/png")
908 if donk != nil {
909 donk.Name = e.Name
910 honk.Donks = append(honk.Donks, donk)
911 }
912 }
913
914 aud := strings.Join(honk.Audience, " ")
915 whofore := 2
916 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
917 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
918 if err != nil {
919 log.Printf("error saving honk: %s", err)
920 return
921 }
922 honk.ID, _ = res.LastInsertId()
923 for _, d := range honk.Donks {
924 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
925 if err != nil {
926 log.Printf("err saving donk: %s", err)
927 return
928 }
929 }
930
931 go honkworldwide(user, &honk)
932
933 http.Redirect(w, r, "/", http.StatusSeeOther)
934}
935
936func showhonkers(w http.ResponseWriter, r *http.Request) {
937 userinfo := login.GetUserInfo(r)
938 templinfo := getInfo(r)
939 templinfo["Honkers"] = gethonkers(userinfo.UserID)
940 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
941 err := readviews.Execute(w, "honkers.html", templinfo)
942 if err != nil {
943 log.Print(err)
944 }
945}
946
947func showcombos(w http.ResponseWriter, r *http.Request) {
948 userinfo := login.GetUserInfo(r)
949 templinfo := getInfo(r)
950 honkers := gethonkers(userinfo.UserID)
951 var combos []string
952 for _, h := range honkers {
953 combos = append(combos, h.Combos...)
954 }
955 for i, c := range combos {
956 if c == "-" {
957 combos[i] = ""
958 }
959 }
960 combos = oneofakind(combos)
961 sort.Strings(combos)
962 templinfo["Combos"] = combos
963 err := readviews.Execute(w, "combos.html", templinfo)
964 if err != nil {
965 log.Print(err)
966 }
967}
968
969func savehonker(w http.ResponseWriter, r *http.Request) {
970 u := login.GetUserInfo(r)
971 name := r.FormValue("name")
972 url := r.FormValue("url")
973 peep := r.FormValue("peep")
974 combos := r.FormValue("combos")
975 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
976
977 if honkerid > 0 {
978 goodbye := r.FormValue("goodbye")
979 if goodbye == "goodbye" {
980 db := opendatabase()
981 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
982 honkerid, u.UserID)
983 var xid string
984 err := row.Scan(&xid)
985 if err != nil {
986 log.Printf("can't get honker xid: %s", err)
987 return
988 }
989 log.Printf("unsubscribing from %s", xid)
990 user, _ := butwhatabout(u.Username)
991 err = itakeitallback(user, xid)
992 if err != nil {
993 log.Printf("can't take it back: %s", err)
994 } else {
995 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
996 if err != nil {
997 log.Printf("error updating honker: %s", err)
998 return
999 }
1000 }
1001
1002 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1003 return
1004 }
1005 combos = " " + strings.TrimSpace(combos) + " "
1006 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1007 if err != nil {
1008 log.Printf("update honker err: %s", err)
1009 return
1010 }
1011 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1012 }
1013
1014 flavor := "presub"
1015 if peep == "peep" {
1016 flavor = "peep"
1017 }
1018 if url == "" {
1019 return
1020 }
1021 if url[0] == '@' {
1022 url = gofish(url)
1023 }
1024 if url == "" {
1025 return
1026 }
1027 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1028 if err != nil {
1029 log.Print(err)
1030 return
1031 }
1032 if flavor == "presub" {
1033 user, _ := butwhatabout(u.Username)
1034 go subsub(user, url)
1035 }
1036 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1037}
1038
1039type Zonker struct {
1040 ID int64
1041 Name string
1042 Wherefore string
1043}
1044
1045func killzone(w http.ResponseWriter, r *http.Request) {
1046 db := opendatabase()
1047 userinfo := login.GetUserInfo(r)
1048 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1049 if err != nil {
1050 log.Printf("err: %s", err)
1051 return
1052 }
1053 var zonkers []Zonker
1054 for rows.Next() {
1055 var z Zonker
1056 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1057 zonkers = append(zonkers, z)
1058 }
1059 templinfo := getInfo(r)
1060 templinfo["Zonkers"] = zonkers
1061 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1062 err = readviews.Execute(w, "zonkers.html", templinfo)
1063 if err != nil {
1064 log.Print(err)
1065 }
1066}
1067
1068func killitwithfire(w http.ResponseWriter, r *http.Request) {
1069 userinfo := login.GetUserInfo(r)
1070 itsok := r.FormValue("itsok")
1071 if itsok == "iforgiveyou" {
1072 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1073 db := opendatabase()
1074 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1075 userinfo.UserID, zonkerid)
1076 bitethethumbs()
1077 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1078 return
1079 }
1080 wherefore := r.FormValue("wherefore")
1081 name := r.FormValue("name")
1082 if name == "" {
1083 return
1084 }
1085 switch wherefore {
1086 case "zonker":
1087 case "zurl":
1088 case "zonvoy":
1089 default:
1090 return
1091 }
1092 db := opendatabase()
1093 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1094 userinfo.UserID, name, wherefore)
1095 if wherefore == "zonker" || wherefore == "zurl" {
1096 bitethethumbs()
1097 }
1098
1099 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1100}
1101
1102func accountpage(w http.ResponseWriter, r *http.Request) {
1103 u := login.GetUserInfo(r)
1104 user, _ := butwhatabout(u.Username)
1105 templinfo := getInfo(r)
1106 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1107 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1108 templinfo["WhatAbout"] = user.About
1109 err := readviews.Execute(w, "account.html", templinfo)
1110 if err != nil {
1111 log.Print(err)
1112 }
1113}
1114
1115func dochpass(w http.ResponseWriter, r *http.Request) {
1116 err := login.ChangePassword(w, r)
1117 if err != nil {
1118 log.Printf("error changing password: %s", err)
1119 }
1120 http.Redirect(w, r, "/account", http.StatusSeeOther)
1121}
1122
1123func fingerlicker(w http.ResponseWriter, r *http.Request) {
1124 orig := r.FormValue("resource")
1125
1126 log.Printf("finger lick: %s", orig)
1127
1128 if strings.HasPrefix(orig, "acct:") {
1129 orig = orig[5:]
1130 }
1131
1132 name := orig
1133 idx := strings.LastIndexByte(name, '/')
1134 if idx != -1 {
1135 name = name[idx+1:]
1136 if "https://"+serverName+"/u/"+name != orig {
1137 log.Printf("foreign request rejected")
1138 name = ""
1139 }
1140 } else {
1141 idx = strings.IndexByte(name, '@')
1142 if idx != -1 {
1143 name = name[:idx]
1144 if name+"@"+serverName != orig {
1145 log.Printf("foreign request rejected")
1146 name = ""
1147 }
1148 }
1149 }
1150 user, err := butwhatabout(name)
1151 if err != nil {
1152 http.NotFound(w, r)
1153 return
1154 }
1155
1156 j := NewJunk()
1157 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1158 j["aliases"] = []string{user.URL}
1159 var links []map[string]interface{}
1160 l := NewJunk()
1161 l["rel"] = "self"
1162 l["type"] = `application/activity+json`
1163 l["href"] = user.URL
1164 links = append(links, l)
1165 j["links"] = links
1166
1167 w.Header().Set("Cache-Control", "max-age=3600")
1168 w.Header().Set("Content-Type", "application/jrd+json")
1169 WriteJunk(w, j)
1170}
1171
1172func somedays() string {
1173 secs := 432000 + notrand.Int63n(432000)
1174 return fmt.Sprintf("%d", secs)
1175}
1176
1177func avatate(w http.ResponseWriter, r *http.Request) {
1178 n := r.FormValue("a")
1179 a := avatar(n)
1180 w.Header().Set("Cache-Control", "max-age="+somedays())
1181 w.Write(a)
1182}
1183
1184func servecss(w http.ResponseWriter, r *http.Request) {
1185 w.Header().Set("Cache-Control", "max-age=7776000")
1186 http.ServeFile(w, r, "views"+r.URL.Path)
1187}
1188func servehtml(w http.ResponseWriter, r *http.Request) {
1189 templinfo := getInfo(r)
1190 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1191 if err != nil {
1192 log.Print(err)
1193 }
1194}
1195func serveemu(w http.ResponseWriter, r *http.Request) {
1196 xid := mux.Vars(r)["xid"]
1197 w.Header().Set("Cache-Control", "max-age="+somedays())
1198 http.ServeFile(w, r, "emus/"+xid)
1199}
1200
1201func servefile(w http.ResponseWriter, r *http.Request) {
1202 xid := mux.Vars(r)["xid"]
1203 row := stmtFileData.QueryRow(xid)
1204 var media string
1205 var data []byte
1206 err := row.Scan(&media, &data)
1207 if err != nil {
1208 log.Printf("error loading file: %s", err)
1209 http.NotFound(w, r)
1210 return
1211 }
1212 w.Header().Set("Content-Type", media)
1213 w.Header().Set("X-Content-Type-Options", "nosniff")
1214 w.Header().Set("Cache-Control", "max-age="+somedays())
1215 w.Write(data)
1216}
1217
1218func serve() {
1219 db := opendatabase()
1220 login.Init(db)
1221
1222 listener, err := openListener()
1223 if err != nil {
1224 log.Fatal(err)
1225 }
1226 go redeliverator()
1227
1228 debug := false
1229 getconfig("debug", &debug)
1230 readviews = templates.Load(debug,
1231 "views/honkpage.html",
1232 "views/honkers.html",
1233 "views/zonkers.html",
1234 "views/combos.html",
1235 "views/honkform.html",
1236 "views/honk.html",
1237 "views/account.html",
1238 "views/login.html",
1239 "views/header.html",
1240 )
1241 if !debug {
1242 s := "views/style.css"
1243 savedstyleparams[s] = getstyleparam(s)
1244 s = "views/local.css"
1245 savedstyleparams[s] = getstyleparam(s)
1246 }
1247
1248 bitethethumbs()
1249
1250 mux := mux.NewRouter()
1251 mux.Use(login.Checker)
1252
1253 posters := mux.Methods("POST").Subrouter()
1254 getters := mux.Methods("GET").Subrouter()
1255
1256 getters.HandleFunc("/", homepage)
1257 getters.HandleFunc("/rss", showrss)
1258 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1259 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1260 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1261 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1262 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1263 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1264 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1265 getters.HandleFunc("/a", avatate)
1266 getters.HandleFunc("/t", showconvoy)
1267 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1268 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1269 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1270
1271 getters.HandleFunc("/style.css", servecss)
1272 getters.HandleFunc("/local.css", servecss)
1273 getters.HandleFunc("/login", servehtml)
1274 posters.HandleFunc("/dologin", login.LoginFunc)
1275 getters.HandleFunc("/logout", login.LogoutFunc)
1276
1277 loggedin := mux.NewRoute().Subrouter()
1278 loggedin.Use(login.Required)
1279 loggedin.HandleFunc("/account", accountpage)
1280 loggedin.HandleFunc("/chpass", dochpass)
1281 loggedin.HandleFunc("/atme", homepage)
1282 loggedin.HandleFunc("/killzone", killzone)
1283 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1284 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1285 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1286 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1287 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1288 loggedin.HandleFunc("/honkers", showhonkers)
1289 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1290 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1291 loggedin.HandleFunc("/c", showcombos)
1292 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1293
1294 err = http.Serve(listener, mux)
1295 if err != nil {
1296 log.Fatal(err)
1297 }
1298}
1299
1300func cleanupdb() {
1301 db := opendatabase()
1302 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1303}
1304
1305var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1306var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1307var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1308var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1309var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1310var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1311var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1312var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1313
1314func preparetodie(db *sql.DB, s string) *sql.Stmt {
1315 stmt, err := db.Prepare(s)
1316 if err != nil {
1317 log.Fatalf("error %s: %s", err, s)
1318 }
1319 return stmt
1320}
1321
1322func prepareStatements(db *sql.DB) {
1323 stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor, combos from honkers where userid = ? and (flavor = 'sub' or flavor = 'peep' or flavor = 'unsub') order by name")
1324 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1325 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1326 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1327 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1328 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1329
1330 selecthonks := "select honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, convoy, whofore from honks join users on honks.userid = users.userid "
1331 limit := " order by honkid desc limit 250"
1332 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1333 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1334 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1335 stmtUserHonks = preparetodie(db, selecthonks+"where whofore = 2 and username = ? and dt > ?"+limit)
1336 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and honker in (select xid from honkers where userid = ? and flavor = 'sub' and combos not like '% - %')"+butnotthose+limit)
1337 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1338 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1339 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1340 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or whofore = 2) and convoy = ?"+limit)
1341
1342 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1343 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1344 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1345 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1346 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1347 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1348 stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1349 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1350 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1351 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1352 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1353 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1354 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1355 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1356 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1357 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1358 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1359 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1360}
1361
1362func ElaborateUnitTests() {
1363}
1364
1365func main() {
1366 cmd := "run"
1367 if len(os.Args) > 1 {
1368 cmd = os.Args[1]
1369 }
1370 switch cmd {
1371 case "init":
1372 initdb()
1373 case "upgrade":
1374 upgradedb()
1375 }
1376 db := opendatabase()
1377 dbversion := 0
1378 getconfig("dbversion", &dbversion)
1379 if dbversion != myVersion {
1380 log.Fatal("incorrect database version. run upgrade.")
1381 }
1382 getconfig("servername", &serverName)
1383 prepareStatements(db)
1384 switch cmd {
1385 case "adduser":
1386 adduser()
1387 case "cleanup":
1388 cleanupdb()
1389 case "ping":
1390 if len(os.Args) < 4 {
1391 fmt.Printf("usage: honk ping from to\n")
1392 return
1393 }
1394 name := os.Args[2]
1395 targ := os.Args[3]
1396 user, err := butwhatabout(name)
1397 if err != nil {
1398 log.Printf("unknown user")
1399 return
1400 }
1401 ping(user, targ)
1402 case "peep":
1403 peeppeep()
1404 case "run":
1405 serve()
1406 case "test":
1407 ElaborateUnitTests()
1408 default:
1409 log.Fatal("unknown command")
1410 }
1411}