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 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)
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)
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 honks := gethonksbyuser(name)
429 u := login.GetUserInfo(r)
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 {
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 for _, hh := range honks {
481 if hh.XID != h.XID {
482 hh.Privacy = "limited"
483 }
484 }
485 u := login.GetUserInfo(r)
486 honkpage(w, r, u, nil, honks, "one honk maybe more")
487}
488
489func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
490 honks []*Honk, infomsg string) {
491 reverbolate(honks)
492 templinfo := getInfo(r)
493 if u != nil {
494 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
495 }
496 if u == nil {
497 w.Header().Set("Cache-Control", "max-age=60")
498 }
499 if user != nil {
500 filt := htfilter.New()
501 templinfo["Name"] = user.Name
502 whatabout := user.About
503 whatabout = obfusbreak(user.About)
504 templinfo["WhatAbout"], _ = filt.String(whatabout)
505 }
506 templinfo["Honks"] = honks
507 templinfo["ServerMessage"] = infomsg
508 err := readviews.Execute(w, "honkpage.html", templinfo)
509 if err != nil {
510 log.Print(err)
511 }
512}
513
514func saveuser(w http.ResponseWriter, r *http.Request) {
515 whatabout := r.FormValue("whatabout")
516 u := login.GetUserInfo(r)
517 db := opendatabase()
518 _, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
519 if err != nil {
520 log.Printf("error bouting what: %s", err)
521 }
522
523 http.Redirect(w, r, "/account", http.StatusSeeOther)
524}
525
526func gethonkers(userid int64) []*Honker {
527 rows, err := stmtHonkers.Query(userid)
528 if err != nil {
529 log.Printf("error querying honkers: %s", err)
530 return nil
531 }
532 defer rows.Close()
533 var honkers []*Honker
534 for rows.Next() {
535 var f Honker
536 var combos string
537 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
538 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
539 if err != nil {
540 log.Printf("error scanning honker: %s", err)
541 return nil
542 }
543 honkers = append(honkers, &f)
544 }
545 return honkers
546}
547
548func getdubs(userid int64) []*Honker {
549 rows, err := stmtDubbers.Query(userid)
550 if err != nil {
551 log.Printf("error querying dubs: %s", err)
552 return nil
553 }
554 defer rows.Close()
555 var honkers []*Honker
556 for rows.Next() {
557 var f Honker
558 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
559 if err != nil {
560 log.Printf("error scanning honker: %s", err)
561 return nil
562 }
563 honkers = append(honkers, &f)
564 }
565 return honkers
566}
567
568func allusers() []login.UserInfo {
569 var users []login.UserInfo
570 rows, _ := opendatabase().Query("select userid, username from users")
571 defer rows.Close()
572 for rows.Next() {
573 var u login.UserInfo
574 rows.Scan(&u.UserID, &u.Username)
575 users = append(users, u)
576 }
577 return users
578}
579
580func getxonk(userid int64, xid string) *Honk {
581 h := new(Honk)
582 var dt, aud string
583 row := stmtOneXonk.QueryRow(userid, xid)
584 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
585 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore)
586 if err != nil {
587 if err != sql.ErrNoRows {
588 log.Printf("error scanning xonk: %s", err)
589 }
590 return nil
591 }
592 h.Date, _ = time.Parse(dbtimeformat, dt)
593 h.Audience = strings.Split(aud, " ")
594 return h
595}
596
597func getpublichonks() []*Honk {
598 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
599 rows, err := stmtPublicHonks.Query(dt)
600 return getsomehonks(rows, err)
601}
602func gethonksbyuser(name string) []*Honk {
603 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
604 rows, err := stmtUserHonks.Query(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.Privacy = "limited"
654 for _, a := range h.Audience {
655 if a == thewholeworld {
656 h.Privacy = ""
657 break
658 }
659 }
660 honks = append(honks, &h)
661 }
662 rows.Close()
663 donksforhonks(honks)
664 return honks
665}
666
667func donksforhonks(honks []*Honk) {
668 db := opendatabase()
669 var ids []string
670 hmap := make(map[int64]*Honk)
671 for _, h := range honks {
672 ids = append(ids, fmt.Sprintf("%d", h.ID))
673 hmap[h.ID] = h
674 }
675 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, ","))
676 rows, err := db.Query(q)
677 if err != nil {
678 log.Printf("error querying donks: %s", err)
679 return
680 }
681 defer rows.Close()
682 for rows.Next() {
683 var hid int64
684 var d Donk
685 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media, &d.Local)
686 if err != nil {
687 log.Printf("error scanning donk: %s", err)
688 continue
689 }
690 h := hmap[hid]
691 h.Donks = append(h.Donks, &d)
692 }
693}
694
695func savebonk(w http.ResponseWriter, r *http.Request) {
696 xid := r.FormValue("xid")
697 userinfo := login.GetUserInfo(r)
698 user, _ := butwhatabout(userinfo.Username)
699
700 log.Printf("bonking %s", xid)
701
702 xonk := getxonk(userinfo.UserID, xid)
703 if xonk == nil {
704 return
705 }
706 donksforhonks([]*Honk{xonk})
707
708 dt := time.Now().UTC()
709 bonk := Honk{
710 UserID: userinfo.UserID,
711 Username: userinfo.Username,
712 What: "bonk",
713 Honker: user.URL,
714 XID: xonk.XID,
715 Date: dt,
716 Donks: xonk.Donks,
717 Audience: []string{thewholeworld},
718 }
719
720 oonker := xonk.Oonker
721 if oonker == "" {
722 oonker = xonk.Honker
723 }
724 aud := strings.Join(bonk.Audience, " ")
725 whofore := 2
726 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
727 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
728 xonk.Precis, oonker)
729 if err != nil {
730 log.Printf("error saving bonk: %s", err)
731 return
732 }
733 bonk.ID, _ = res.LastInsertId()
734 for _, d := range bonk.Donks {
735 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
736 if err != nil {
737 log.Printf("err saving donk: %s", err)
738 return
739 }
740 }
741
742 go honkworldwide(user, &bonk)
743}
744
745func zonkit(w http.ResponseWriter, r *http.Request) {
746 wherefore := r.FormValue("wherefore")
747 var what string
748 switch wherefore {
749 case "this honk":
750 what = r.FormValue("honk")
751 wherefore = "zonk"
752 case "this honker":
753 what = r.FormValue("honker")
754 wherefore = "zonker"
755 case "this convoy":
756 what = r.FormValue("convoy")
757 wherefore = "zonvoy"
758 }
759 if what == "" {
760 return
761 }
762
763 log.Printf("zonking %s %s", wherefore, what)
764 userinfo := login.GetUserInfo(r)
765 if wherefore == "zonk" {
766 xonk := getxonk(userinfo.UserID, what)
767 if xonk != nil {
768 stmtZonkDonks.Exec(xonk.ID)
769 stmtZonkIt.Exec(userinfo.UserID, what)
770 if xonk.Whofore == 2 {
771 zonk := Honk{
772 What: "zonk",
773 XID: xonk.XID,
774 Date: time.Now().UTC(),
775 Audience: oneofakind(xonk.Audience),
776 }
777
778 user, _ := butwhatabout(userinfo.Username)
779 log.Printf("announcing deleted honk: %s", what)
780 go honkworldwide(user, &zonk)
781 }
782 }
783 } else {
784 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
785 if err != nil {
786 log.Printf("error saving zonker: %s", err)
787 return
788 }
789 }
790}
791
792func savehonk(w http.ResponseWriter, r *http.Request) {
793 rid := r.FormValue("rid")
794 noise := r.FormValue("noise")
795
796 userinfo := login.GetUserInfo(r)
797 user, _ := butwhatabout(userinfo.Username)
798
799 dt := time.Now().UTC()
800 xid := fmt.Sprintf("https://%s/u/%s/h/%s", serverName, userinfo.Username, xfiltrate())
801 what := "honk"
802 if rid != "" {
803 what = "tonk"
804 }
805 honk := Honk{
806 UserID: userinfo.UserID,
807 Username: userinfo.Username,
808 What: "honk",
809 Honker: user.URL,
810 XID: xid,
811 Date: dt,
812 }
813 if strings.HasPrefix(noise, "DZ:") {
814 idx := strings.Index(noise, "\n")
815 if idx == -1 {
816 honk.Precis = noise
817 noise = ""
818 } else {
819 honk.Precis = noise[:idx]
820 noise = noise[idx+1:]
821 }
822 }
823 noise = strings.TrimSpace(noise)
824 honk.Precis = strings.TrimSpace(honk.Precis)
825
826 if noise != "" && noise[0] == '@' {
827 honk.Audience = append(grapevine(noise), thewholeworld)
828 } else {
829 honk.Audience = prepend(thewholeworld, grapevine(noise))
830 }
831 var convoy string
832 if rid != "" {
833 xonk := getxonk(userinfo.UserID, rid)
834 if xonk != nil {
835 honk.Audience = append(honk.Audience, xonk.Audience...)
836 convoy = xonk.Convoy
837 } else {
838 xonkaud, c := whosthere(rid)
839 honk.Audience = append(honk.Audience, xonkaud...)
840 convoy = c
841 }
842 honk.RID = rid
843 }
844 if convoy == "" {
845 convoy = "data:,electrichonkytonk-" + xfiltrate()
846 }
847 butnottooloud(honk.Audience)
848 honk.Audience = oneofakind(honk.Audience)
849 noise = obfusbreak(noise)
850 honk.Noise = noise
851 honk.Convoy = convoy
852
853 file, filehdr, err := r.FormFile("donk")
854 if err == nil {
855 var buf bytes.Buffer
856 io.Copy(&buf, file)
857 file.Close()
858 data := buf.Bytes()
859 xid := xfiltrate()
860 var media, name string
861 img, err := image.Vacuum(&buf)
862 if err == nil {
863 data = img.Data
864 format := img.Format
865 media = "image/" + format
866 if format == "jpeg" {
867 format = "jpg"
868 }
869 name = xid + "." + format
870 xid = name
871 } else {
872 maxsize := 100000
873 if len(data) > maxsize {
874 log.Printf("bad image: %s too much text: %d", err, len(data))
875 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
876 return
877 }
878 for i := 0; i < len(data); i++ {
879 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
880 log.Printf("bad image: %s not text: %d", err, data[i])
881 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
882 return
883 }
884 }
885 media = "text/plain"
886 name = filehdr.Filename
887 if name == "" {
888 name = xid + ".txt"
889 }
890 xid += ".txt"
891 }
892 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
893 res, err := stmtSaveFile.Exec(xid, name, url, media, 1, data)
894 if err != nil {
895 log.Printf("unable to save image: %s", err)
896 return
897 }
898 var d Donk
899 d.FileID, _ = res.LastInsertId()
900 d.XID = name
901 d.Name = name
902 d.Media = media
903 d.URL = url
904 honk.Donks = append(honk.Donks, &d)
905 }
906 herd := herdofemus(honk.Noise)
907 for _, e := range herd {
908 donk := savedonk(e.ID, e.Name, "image/png")
909 if donk != nil {
910 donk.Name = e.Name
911 honk.Donks = append(honk.Donks, donk)
912 }
913 }
914
915 aud := strings.Join(honk.Audience, " ")
916 whofore := 2
917 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
918 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
919 if err != nil {
920 log.Printf("error saving honk: %s", err)
921 return
922 }
923 honk.ID, _ = res.LastInsertId()
924 for _, d := range honk.Donks {
925 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
926 if err != nil {
927 log.Printf("err saving donk: %s", err)
928 return
929 }
930 }
931
932 go honkworldwide(user, &honk)
933
934 http.Redirect(w, r, "/", http.StatusSeeOther)
935}
936
937func showhonkers(w http.ResponseWriter, r *http.Request) {
938 userinfo := login.GetUserInfo(r)
939 templinfo := getInfo(r)
940 templinfo["Honkers"] = gethonkers(userinfo.UserID)
941 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
942 err := readviews.Execute(w, "honkers.html", templinfo)
943 if err != nil {
944 log.Print(err)
945 }
946}
947
948func showcombos(w http.ResponseWriter, r *http.Request) {
949 userinfo := login.GetUserInfo(r)
950 templinfo := getInfo(r)
951 honkers := gethonkers(userinfo.UserID)
952 var combos []string
953 for _, h := range honkers {
954 combos = append(combos, h.Combos...)
955 }
956 for i, c := range combos {
957 if c == "-" {
958 combos[i] = ""
959 }
960 }
961 combos = oneofakind(combos)
962 sort.Strings(combos)
963 templinfo["Combos"] = combos
964 err := readviews.Execute(w, "combos.html", templinfo)
965 if err != nil {
966 log.Print(err)
967 }
968}
969
970func savehonker(w http.ResponseWriter, r *http.Request) {
971 u := login.GetUserInfo(r)
972 name := r.FormValue("name")
973 url := r.FormValue("url")
974 peep := r.FormValue("peep")
975 combos := r.FormValue("combos")
976 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
977
978 if honkerid > 0 {
979 goodbye := r.FormValue("goodbye")
980 if goodbye == "F" {
981 db := opendatabase()
982 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
983 honkerid, u.UserID)
984 var xid string
985 err := row.Scan(&xid)
986 if err != nil {
987 log.Printf("can't get honker xid: %s", err)
988 return
989 }
990 log.Printf("unsubscribing from %s", xid)
991 user, _ := butwhatabout(u.Username)
992 err = itakeitallback(user, xid)
993 if err != nil {
994 log.Printf("can't take it back: %s", err)
995 } else {
996 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
997 if err != nil {
998 log.Printf("error updating honker: %s", err)
999 return
1000 }
1001 }
1002
1003 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1004 return
1005 }
1006 combos = " " + strings.TrimSpace(combos) + " "
1007 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1008 if err != nil {
1009 log.Printf("update honker err: %s", err)
1010 return
1011 }
1012 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1013 }
1014
1015 flavor := "presub"
1016 if peep == "peep" {
1017 flavor = "peep"
1018 }
1019 if url == "" {
1020 return
1021 }
1022 if url[0] == '@' {
1023 url = gofish(url)
1024 }
1025 if url == "" {
1026 return
1027 }
1028 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1029 if err != nil {
1030 log.Print(err)
1031 return
1032 }
1033 if flavor == "presub" {
1034 user, _ := butwhatabout(u.Username)
1035 go subsub(user, url)
1036 }
1037 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1038}
1039
1040type Zonker struct {
1041 ID int64
1042 Name string
1043 Wherefore string
1044}
1045
1046func killzone(w http.ResponseWriter, r *http.Request) {
1047 db := opendatabase()
1048 userinfo := login.GetUserInfo(r)
1049 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1050 if err != nil {
1051 log.Printf("err: %s", err)
1052 return
1053 }
1054 var zonkers []Zonker
1055 for rows.Next() {
1056 var z Zonker
1057 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1058 zonkers = append(zonkers, z)
1059 }
1060 templinfo := getInfo(r)
1061 templinfo["Zonkers"] = zonkers
1062 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1063 err = readviews.Execute(w, "zonkers.html", templinfo)
1064 if err != nil {
1065 log.Print(err)
1066 }
1067}
1068
1069func killitwithfire(w http.ResponseWriter, r *http.Request) {
1070 userinfo := login.GetUserInfo(r)
1071 itsok := r.FormValue("itsok")
1072 if itsok == "iforgiveyou" {
1073 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1074 db := opendatabase()
1075 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1076 userinfo.UserID, zonkerid)
1077 bitethethumbs()
1078 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1079 return
1080 }
1081 wherefore := r.FormValue("wherefore")
1082 name := r.FormValue("name")
1083 if name == "" {
1084 return
1085 }
1086 switch wherefore {
1087 case "zonker":
1088 case "zurl":
1089 case "zonvoy":
1090 default:
1091 return
1092 }
1093 db := opendatabase()
1094 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1095 userinfo.UserID, name, wherefore)
1096 if wherefore == "zonker" || wherefore == "zurl" {
1097 bitethethumbs()
1098 }
1099
1100 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1101}
1102
1103func accountpage(w http.ResponseWriter, r *http.Request) {
1104 u := login.GetUserInfo(r)
1105 user, _ := butwhatabout(u.Username)
1106 templinfo := getInfo(r)
1107 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1108 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1109 templinfo["WhatAbout"] = user.About
1110 err := readviews.Execute(w, "account.html", templinfo)
1111 if err != nil {
1112 log.Print(err)
1113 }
1114}
1115
1116func dochpass(w http.ResponseWriter, r *http.Request) {
1117 err := login.ChangePassword(w, r)
1118 if err != nil {
1119 log.Printf("error changing password: %s", err)
1120 }
1121 http.Redirect(w, r, "/account", http.StatusSeeOther)
1122}
1123
1124func fingerlicker(w http.ResponseWriter, r *http.Request) {
1125 orig := r.FormValue("resource")
1126
1127 log.Printf("finger lick: %s", orig)
1128
1129 if strings.HasPrefix(orig, "acct:") {
1130 orig = orig[5:]
1131 }
1132
1133 name := orig
1134 idx := strings.LastIndexByte(name, '/')
1135 if idx != -1 {
1136 name = name[idx+1:]
1137 if "https://"+serverName+"/u/"+name != orig {
1138 log.Printf("foreign request rejected")
1139 name = ""
1140 }
1141 } else {
1142 idx = strings.IndexByte(name, '@')
1143 if idx != -1 {
1144 name = name[:idx]
1145 if name+"@"+serverName != orig {
1146 log.Printf("foreign request rejected")
1147 name = ""
1148 }
1149 }
1150 }
1151 user, err := butwhatabout(name)
1152 if err != nil {
1153 http.NotFound(w, r)
1154 return
1155 }
1156
1157 j := NewJunk()
1158 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1159 j["aliases"] = []string{user.URL}
1160 var links []map[string]interface{}
1161 l := NewJunk()
1162 l["rel"] = "self"
1163 l["type"] = `application/activity+json`
1164 l["href"] = user.URL
1165 links = append(links, l)
1166 j["links"] = links
1167
1168 w.Header().Set("Cache-Control", "max-age=3600")
1169 w.Header().Set("Content-Type", "application/jrd+json")
1170 WriteJunk(w, j)
1171}
1172
1173func somedays() string {
1174 secs := 432000 + notrand.Int63n(432000)
1175 return fmt.Sprintf("%d", secs)
1176}
1177
1178func avatate(w http.ResponseWriter, r *http.Request) {
1179 n := r.FormValue("a")
1180 a := avatar(n)
1181 w.Header().Set("Cache-Control", "max-age="+somedays())
1182 w.Write(a)
1183}
1184
1185func servecss(w http.ResponseWriter, r *http.Request) {
1186 w.Header().Set("Cache-Control", "max-age=7776000")
1187 http.ServeFile(w, r, "views"+r.URL.Path)
1188}
1189func servehtml(w http.ResponseWriter, r *http.Request) {
1190 templinfo := getInfo(r)
1191 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1192 if err != nil {
1193 log.Print(err)
1194 }
1195}
1196func serveemu(w http.ResponseWriter, r *http.Request) {
1197 xid := mux.Vars(r)["xid"]
1198 w.Header().Set("Cache-Control", "max-age="+somedays())
1199 http.ServeFile(w, r, "emus/"+xid)
1200}
1201
1202func servefile(w http.ResponseWriter, r *http.Request) {
1203 xid := mux.Vars(r)["xid"]
1204 row := stmtFileData.QueryRow(xid)
1205 var media string
1206 var data []byte
1207 err := row.Scan(&media, &data)
1208 if err != nil {
1209 log.Printf("error loading file: %s", err)
1210 http.NotFound(w, r)
1211 return
1212 }
1213 w.Header().Set("Content-Type", media)
1214 w.Header().Set("X-Content-Type-Options", "nosniff")
1215 w.Header().Set("Cache-Control", "max-age="+somedays())
1216 w.Write(data)
1217}
1218
1219func serve() {
1220 db := opendatabase()
1221 login.Init(db)
1222
1223 listener, err := openListener()
1224 if err != nil {
1225 log.Fatal(err)
1226 }
1227 go redeliverator()
1228
1229 debug := false
1230 getconfig("debug", &debug)
1231 readviews = templates.Load(debug,
1232 "views/honkpage.html",
1233 "views/honkers.html",
1234 "views/zonkers.html",
1235 "views/combos.html",
1236 "views/honkform.html",
1237 "views/honk.html",
1238 "views/account.html",
1239 "views/login.html",
1240 "views/header.html",
1241 )
1242 if !debug {
1243 s := "views/style.css"
1244 savedstyleparams[s] = getstyleparam(s)
1245 s = "views/local.css"
1246 savedstyleparams[s] = getstyleparam(s)
1247 }
1248
1249 bitethethumbs()
1250
1251 mux := mux.NewRouter()
1252 mux.Use(login.Checker)
1253
1254 posters := mux.Methods("POST").Subrouter()
1255 getters := mux.Methods("GET").Subrouter()
1256
1257 getters.HandleFunc("/", homepage)
1258 getters.HandleFunc("/rss", showrss)
1259 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1260 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1261 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1262 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1263 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1264 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1265 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1266 getters.HandleFunc("/a", avatate)
1267 getters.HandleFunc("/t", showconvoy)
1268 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1269 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1270 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1271
1272 getters.HandleFunc("/style.css", servecss)
1273 getters.HandleFunc("/local.css", servecss)
1274 getters.HandleFunc("/login", servehtml)
1275 posters.HandleFunc("/dologin", login.LoginFunc)
1276 getters.HandleFunc("/logout", login.LogoutFunc)
1277
1278 loggedin := mux.NewRoute().Subrouter()
1279 loggedin.Use(login.Required)
1280 loggedin.HandleFunc("/account", accountpage)
1281 loggedin.HandleFunc("/chpass", dochpass)
1282 loggedin.HandleFunc("/atme", homepage)
1283 loggedin.HandleFunc("/killzone", killzone)
1284 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1285 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1286 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1287 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1288 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1289 loggedin.HandleFunc("/honkers", showhonkers)
1290 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1291 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1292 loggedin.HandleFunc("/c", showcombos)
1293 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1294
1295 err = http.Serve(listener, mux)
1296 if err != nil {
1297 log.Fatal(err)
1298 }
1299}
1300
1301func cleanupdb() {
1302 db := opendatabase()
1303 rows, _ := db.Query("select userid, name from zonkers where wherefore = 'zonvoy'")
1304 deadthreads := make(map[int64][]string)
1305 for rows.Next() {
1306 var userid int64
1307 var name string
1308 rows.Scan(&userid, &name)
1309 deadthreads[userid] = append(deadthreads[userid], name)
1310 }
1311 rows.Close()
1312 for userid, threads := range deadthreads {
1313 for _, t := range threads {
1314 doordie(db, "delete from donks where honkid in (select honkid from honks where userid = ? and convoy = ?)", userid, t)
1315 doordie(db, "delete from honks where userid = ? and convoy = ?", userid, t)
1316 }
1317 }
1318 expdate := time.Now().UTC().Add(-30 * 24 * time.Hour).Format(dbtimeformat)
1319 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)", expdate)
1320 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1321}
1322
1323var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1324var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1325var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1326var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1327var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1328var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1329var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1330var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1331
1332func preparetodie(db *sql.DB, s string) *sql.Stmt {
1333 stmt, err := db.Prepare(s)
1334 if err != nil {
1335 log.Fatalf("error %s: %s", err, s)
1336 }
1337 return stmt
1338}
1339
1340func prepareStatements(db *sql.DB) {
1341 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")
1342 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1343 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1344 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1345 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1346 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1347
1348 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 "
1349 limit := " order by honkid desc limit 250"
1350 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1351 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1352 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1353 stmtUserHonks = preparetodie(db, selecthonks+"where whofore = 2 and username = ? and dt > ?"+limit)
1354 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)
1355 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1356 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1357 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1358 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or whofore = 2) and convoy = ?"+limit)
1359
1360 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1361 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1362 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1363 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1364 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1365 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1366 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1367 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1368 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1369 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1370 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1371 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1372 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1373 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1374 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1375 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1376 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1377 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1378}
1379
1380func ElaborateUnitTests() {
1381}
1382
1383func main() {
1384 cmd := "run"
1385 if len(os.Args) > 1 {
1386 cmd = os.Args[1]
1387 }
1388 switch cmd {
1389 case "init":
1390 initdb()
1391 case "upgrade":
1392 upgradedb()
1393 }
1394 db := opendatabase()
1395 dbversion := 0
1396 getconfig("dbversion", &dbversion)
1397 if dbversion != myVersion {
1398 log.Fatal("incorrect database version. run upgrade.")
1399 }
1400 getconfig("servername", &serverName)
1401 prepareStatements(db)
1402 switch cmd {
1403 case "adduser":
1404 adduser()
1405 case "cleanup":
1406 cleanupdb()
1407 case "ping":
1408 if len(os.Args) < 4 {
1409 fmt.Printf("usage: honk ping from to\n")
1410 return
1411 }
1412 name := os.Args[2]
1413 targ := os.Args[3]
1414 user, err := butwhatabout(name)
1415 if err != nil {
1416 log.Printf("unknown user")
1417 return
1418 }
1419 ping(user, targ)
1420 case "peep":
1421 peeppeep()
1422 case "run":
1423 serve()
1424 case "test":
1425 ElaborateUnitTests()
1426 default:
1427 log.Fatal("unknown command")
1428 }
1429}