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