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