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