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 "net/url"
29 "os"
30 "sort"
31 "strconv"
32 "strings"
33 "time"
34
35 "github.com/gorilla/mux"
36 "humungus.tedunangst.com/r/webs/htfilter"
37 "humungus.tedunangst.com/r/webs/httpsig"
38 "humungus.tedunangst.com/r/webs/image"
39 "humungus.tedunangst.com/r/webs/junk"
40 "humungus.tedunangst.com/r/webs/login"
41 "humungus.tedunangst.com/r/webs/rss"
42 "humungus.tedunangst.com/r/webs/templates"
43)
44
45type WhatAbout struct {
46 ID int64
47 Name string
48 Display string
49 About string
50 Key string
51 URL string
52 SkinnyCSS bool
53}
54
55type Honk struct {
56 ID int64
57 UserID int64
58 Username string
59 What string
60 Honker string
61 Handle string
62 Oonker string
63 Oondle string
64 XID string
65 RID string
66 Date time.Time
67 URL string
68 Noise string
69 Precis string
70 Convoy string
71 Audience []string
72 Public bool
73 Whofore int64
74 Replies []*Honk
75 Flags int64
76 HTML template.HTML
77 Style string
78 Open string
79 Donks []*Donk
80 Onts []string
81}
82
83const (
84 flagIsAcked = 1
85 flagIsBonked = 2
86)
87
88func (honk *Honk) IsAcked() bool {
89 return honk.Flags&flagIsAcked != 0
90}
91
92func (honk *Honk) IsBonked() bool {
93 return honk.Flags&flagIsBonked != 0
94}
95
96type Donk struct {
97 FileID int64
98 XID string
99 Name string
100 URL string
101 Media string
102 Local bool
103 Content []byte
104}
105
106type Honker struct {
107 ID int64
108 UserID int64
109 Name string
110 XID string
111 Handle string
112 Flavor string
113 Combos []string
114}
115
116var serverName string
117var iconName = "icon.png"
118var serverMsg = "Things happen."
119
120var userSep = "u"
121var honkSep = "h"
122
123var readviews *templates.Template
124
125func getuserstyle(u *login.UserInfo) template.CSS {
126 if u == nil {
127 return ""
128 }
129 user, _ := butwhatabout(u.Username)
130 if user.SkinnyCSS {
131 return "main { max-width: 700px; }"
132 }
133 return ""
134}
135
136func getInfo(r *http.Request) map[string]interface{} {
137 u := login.GetUserInfo(r)
138 templinfo := make(map[string]interface{})
139 templinfo["StyleParam"] = getstyleparam("views/style.css")
140 templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
141 templinfo["UserStyle"] = getuserstyle(u)
142 templinfo["ServerName"] = serverName
143 templinfo["IconName"] = iconName
144 templinfo["UserInfo"] = u
145 templinfo["UserSep"] = userSep
146 return templinfo
147}
148
149var donotfedafterdark = make(map[string]bool)
150
151func stealthed(r *http.Request) bool {
152 addr := r.Header.Get("X-Forwarded-For")
153 fake := donotfedafterdark[addr]
154 if fake {
155 log.Printf("faking 404 for %s", addr)
156 }
157 return fake
158}
159
160func homepage(w http.ResponseWriter, r *http.Request) {
161 templinfo := getInfo(r)
162 u := login.GetUserInfo(r)
163 var honks []*Honk
164 var userid int64 = -1
165 if r.URL.Path == "/front" || u == nil {
166 honks = getpublichonks()
167 } else {
168 userid = u.UserID
169 if r.URL.Path == "/atme" {
170 honks = gethonksforme(userid)
171 } else {
172 honks = gethonksforuser(userid)
173 honks = osmosis(honks, userid)
174 if len(honks) > 0 {
175 templinfo["TopXID"] = honks[0].XID
176 }
177 }
178 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
179 }
180
181 tname := "honkpage.html"
182 if topxid := r.FormValue("topxid"); topxid != "" {
183 for i, h := range honks {
184 if h.XID == topxid {
185 honks = honks[0:i]
186 break
187 }
188 }
189 log.Printf("topxid %d frags", len(honks))
190 tname = "honkfrags.html"
191 }
192
193 reverbolate(userid, honks)
194
195 templinfo["Honks"] = honks
196 templinfo["ShowRSS"] = true
197 templinfo["ServerMessage"] = serverMsg
198 if u == nil {
199 w.Header().Set("Cache-Control", "max-age=60")
200 } else {
201 w.Header().Set("Cache-Control", "max-age=0")
202 }
203 w.Header().Set("Content-Type", "text/html; charset=utf-8")
204
205 err := readviews.Execute(w, tname, templinfo)
206 if err != nil {
207 log.Print(err)
208 }
209}
210
211func showfunzone(w http.ResponseWriter, r *http.Request) {
212 var emunames, memenames []string
213 dir, err := os.Open("emus")
214 if err == nil {
215 emunames, _ = dir.Readdirnames(0)
216 dir.Close()
217 }
218 for i, e := range emunames {
219 if len(e) > 4 {
220 emunames[i] = e[:len(e)-4]
221 }
222 }
223 dir, err = os.Open("memes")
224 if err == nil {
225 memenames, _ = dir.Readdirnames(0)
226 dir.Close()
227 }
228 templinfo := getInfo(r)
229 templinfo["Emus"] = emunames
230 templinfo["Memes"] = memenames
231 err = readviews.Execute(w, "funzone.html", templinfo)
232 if err != nil {
233 log.Print(err)
234 }
235
236}
237
238func showrss(w http.ResponseWriter, r *http.Request) {
239 name := mux.Vars(r)["name"]
240
241 var honks []*Honk
242 if name != "" {
243 honks = gethonksbyuser(name, false)
244 } else {
245 honks = getpublichonks()
246 }
247 reverbolate(-1, honks)
248
249 home := fmt.Sprintf("https://%s/", serverName)
250 base := home
251 if name != "" {
252 home += "u/" + name
253 name += " "
254 }
255 feed := rss.Feed{
256 Title: name + "honk",
257 Link: home,
258 Description: name + "honk rss",
259 Image: &rss.Image{
260 URL: base + "icon.png",
261 Title: name + "honk rss",
262 Link: home,
263 },
264 }
265 var modtime time.Time
266 for _, honk := range honks {
267 if !firstclass(honk) {
268 continue
269 }
270 desc := string(honk.HTML)
271 for _, d := range honk.Donks {
272 desc += fmt.Sprintf(`<p><a href="%s">Attachment: %s</a>`,
273 d.URL, html.EscapeString(d.Name))
274 }
275
276 feed.Items = append(feed.Items, &rss.Item{
277 Title: fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
278 Description: rss.CData{desc},
279 Link: honk.URL,
280 PubDate: honk.Date.Format(time.RFC1123),
281 Guid: &rss.Guid{IsPermaLink: true, Value: honk.URL},
282 })
283 if honk.Date.After(modtime) {
284 modtime = honk.Date
285 }
286 }
287 w.Header().Set("Cache-Control", "max-age=300")
288 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
289
290 err := feed.Write(w)
291 if err != nil {
292 log.Printf("error writing rss: %s", err)
293 }
294}
295
296func butwhatabout(name string) (*WhatAbout, error) {
297 row := stmtWhatAbout.QueryRow(name)
298 var user WhatAbout
299 var options string
300 err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key, &options)
301 user.URL = fmt.Sprintf("https://%s/%s/%s", serverName, userSep, user.Name)
302 user.SkinnyCSS = strings.Contains(options, " skinny ")
303 return &user, err
304}
305
306func crappola(j junk.Junk) bool {
307 t, _ := j.GetString("type")
308 a, _ := j.GetString("actor")
309 o, _ := j.GetString("object")
310 if t == "Delete" && a == o {
311 log.Printf("crappola from %s", a)
312 return true
313 }
314 return false
315}
316
317func ping(user *WhatAbout, who string) {
318 box, err := getboxes(who)
319 if err != nil {
320 log.Printf("no inbox for ping: %s", err)
321 return
322 }
323 j := junk.New()
324 j["@context"] = itiswhatitis
325 j["type"] = "Ping"
326 j["id"] = user.URL + "/ping/" + xfiltrate()
327 j["actor"] = user.URL
328 j["to"] = who
329 keyname, key := ziggy(user.Name)
330 err = PostJunk(keyname, key, box.In, j)
331 if err != nil {
332 log.Printf("can't send ping: %s", err)
333 return
334 }
335 log.Printf("sent ping to %s: %s", who, j["id"])
336}
337
338func pong(user *WhatAbout, who string, obj string) {
339 box, err := getboxes(who)
340 if err != nil {
341 log.Printf("no inbox for pong %s : %s", who, err)
342 return
343 }
344 j := junk.New()
345 j["@context"] = itiswhatitis
346 j["type"] = "Pong"
347 j["id"] = user.URL + "/pong/" + xfiltrate()
348 j["actor"] = user.URL
349 j["to"] = who
350 j["object"] = obj
351 keyname, key := ziggy(user.Name)
352 err = PostJunk(keyname, key, box.In, j)
353 if err != nil {
354 log.Printf("can't send pong: %s", err)
355 return
356 }
357}
358
359func inbox(w http.ResponseWriter, r *http.Request) {
360 name := mux.Vars(r)["name"]
361 user, err := butwhatabout(name)
362 if err != nil {
363 http.NotFound(w, r)
364 return
365 }
366 var buf bytes.Buffer
367 io.Copy(&buf, r.Body)
368 payload := buf.Bytes()
369 j, err := junk.Read(bytes.NewReader(payload))
370 if err != nil {
371 log.Printf("bad payload: %s", err)
372 io.WriteString(os.Stdout, "bad payload\n")
373 os.Stdout.Write(payload)
374 io.WriteString(os.Stdout, "\n")
375 return
376 }
377 if crappola(j) {
378 return
379 }
380 keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
381 if err != nil {
382 log.Printf("inbox message failed signature: %s", err)
383 if keyname != "" {
384 keyname, err = makeitworksomehowwithoutregardforkeycontinuity(keyname, r, payload)
385 if err != nil {
386 log.Printf("still failed: %s", err)
387 }
388 }
389 if err != nil {
390 return
391 }
392 }
393 what, _ := j.GetString("type")
394 if what == "Like" {
395 return
396 }
397 who, _ := j.GetString("actor")
398 origin := keymatch(keyname, who)
399 if origin == "" {
400 log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
401 return
402 }
403 objid, _ := j.GetString("id")
404 if thoudostbitethythumb(user.ID, []string{who}, objid) {
405 log.Printf("ignoring thumb sucker %s", who)
406 return
407 }
408 switch what {
409 case "Ping":
410 obj, _ := j.GetString("id")
411 log.Printf("ping from %s: %s", who, obj)
412 pong(user, who, obj)
413 case "Pong":
414 obj, _ := j.GetString("object")
415 log.Printf("pong from %s: %s", who, obj)
416 case "Follow":
417 obj, _ := j.GetString("object")
418 if obj == user.URL {
419 log.Printf("updating honker follow: %s", who)
420 stmtSaveDub.Exec(user.ID, who, who, "dub")
421 go rubadubdub(user, j)
422 } else {
423 log.Printf("can't follow %s", obj)
424 }
425 case "Accept":
426 log.Printf("updating honker accept: %s", who)
427 _, err = stmtUpdateFlavor.Exec("sub", user.ID, who, "presub")
428 if err != nil {
429 log.Printf("error updating honker: %s", err)
430 return
431 }
432 case "Update":
433 obj, ok := j.GetMap("object")
434 if ok {
435 what, _ := obj.GetString("type")
436 switch what {
437 case "Person":
438 return
439 case "Question":
440 return
441 }
442 }
443 log.Printf("unknown Update activity")
444 fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
445 j.Write(fd)
446 io.WriteString(fd, "\n")
447 fd.Close()
448
449 case "Undo":
450 obj, ok := j.GetMap("object")
451 if !ok {
452 log.Printf("unknown undo no object")
453 } else {
454 what, _ := obj.GetString("type")
455 switch what {
456 case "Follow":
457 log.Printf("updating honker undo: %s", who)
458 _, err = stmtUpdateFlavor.Exec("undub", user.ID, who, "dub")
459 if err != nil {
460 log.Printf("error updating honker: %s", err)
461 return
462 }
463 case "Announce":
464 fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
465 j.Write(fd)
466 io.WriteString(fd, "\n")
467 fd.Close()
468 log.Printf("an announcement has been undone")
469 xid, _ := obj.GetString("object")
470 log.Printf("undo announce: %s", xid)
471 case "Like":
472 default:
473 log.Printf("unknown undo: %s", what)
474 }
475 }
476 default:
477 go consumeactivity(user, j, origin)
478 }
479}
480
481func ximport(w http.ResponseWriter, r *http.Request) {
482 xid := r.FormValue("xid")
483 p := investigate(xid)
484 if p != nil {
485 xid = p.XID
486 }
487 j, err := GetJunk(xid)
488 if err != nil {
489 http.Error(w, "error getting external object", http.StatusInternalServerError)
490 log.Printf("error getting external object: %s", err)
491 return
492 }
493 log.Printf("importing %s", xid)
494 u := login.GetUserInfo(r)
495 user, _ := butwhatabout(u.Username)
496
497 what, _ := j.GetString("type")
498 if isactor(what) {
499 outbox, _ := j.GetString("outbox")
500 gimmexonks(user, outbox)
501 http.Redirect(w, r, "/h?xid="+url.QueryEscape(xid), http.StatusSeeOther)
502 return
503 }
504 xonk := xonkxonk(user, j, originate(xid))
505 convoy := ""
506 if xonk != nil {
507 convoy = xonk.Convoy
508 savexonk(user, xonk)
509 }
510 http.Redirect(w, r, "/t?c="+url.QueryEscape(convoy), http.StatusSeeOther)
511}
512
513func xzone(w http.ResponseWriter, r *http.Request) {
514 u := login.GetUserInfo(r)
515 rows, err := stmtRecentHonkers.Query(u.UserID, u.UserID)
516 if err != nil {
517 log.Printf("query err: %s", err)
518 return
519 }
520 defer rows.Close()
521 var honkers []Honker
522 for rows.Next() {
523 var xid string
524 rows.Scan(&xid)
525 honkers = append(honkers, Honker{XID: xid})
526 }
527 rows.Close()
528 for i, _ := range honkers {
529 _, honkers[i].Handle = handles(honkers[i].XID)
530 }
531 templinfo := getInfo(r)
532 templinfo["XCSRF"] = login.GetCSRF("ximport", r)
533 templinfo["Honkers"] = honkers
534 err = readviews.Execute(w, "xzone.html", templinfo)
535 if err != nil {
536 log.Print(err)
537 }
538}
539
540func outbox(w http.ResponseWriter, r *http.Request) {
541 name := mux.Vars(r)["name"]
542 user, err := butwhatabout(name)
543 if err != nil {
544 http.NotFound(w, r)
545 return
546 }
547 if stealthed(r) {
548 http.NotFound(w, r)
549 return
550 }
551
552 honks := gethonksbyuser(name, false)
553
554 var jonks []junk.Junk
555 for _, h := range honks {
556 j, _ := jonkjonk(user, h)
557 jonks = append(jonks, j)
558 }
559
560 j := junk.New()
561 j["@context"] = itiswhatitis
562 j["id"] = user.URL + "/outbox"
563 j["type"] = "OrderedCollection"
564 j["totalItems"] = len(jonks)
565 j["orderedItems"] = jonks
566
567 w.Header().Set("Content-Type", theonetruename)
568 j.Write(w)
569}
570
571func emptiness(w http.ResponseWriter, r *http.Request) {
572 name := mux.Vars(r)["name"]
573 user, err := butwhatabout(name)
574 if err != nil {
575 http.NotFound(w, r)
576 return
577 }
578 colname := "/followers"
579 if strings.HasSuffix(r.URL.Path, "/following") {
580 colname = "/following"
581 }
582 j := junk.New()
583 j["@context"] = itiswhatitis
584 j["id"] = user.URL + colname
585 j["type"] = "OrderedCollection"
586 j["totalItems"] = 0
587 j["orderedItems"] = []junk.Junk{}
588
589 w.Header().Set("Content-Type", theonetruename)
590 j.Write(w)
591}
592
593func showuser(w http.ResponseWriter, r *http.Request) {
594 name := mux.Vars(r)["name"]
595 user, err := butwhatabout(name)
596 if err != nil {
597 log.Printf("user not found %s: %s", name, err)
598 http.NotFound(w, r)
599 return
600 }
601 if friendorfoe(r.Header.Get("Accept")) {
602 j := asjonker(user)
603 w.Header().Set("Content-Type", theonetruename)
604 j.Write(w)
605 return
606 }
607 u := login.GetUserInfo(r)
608 honks := gethonksbyuser(name, u != nil && u.Username == name)
609 honkpage(w, r, u, user, honks, "")
610}
611
612func showhonker(w http.ResponseWriter, r *http.Request) {
613 u := login.GetUserInfo(r)
614 name := mux.Vars(r)["name"]
615 var honks []*Honk
616 if name == "" {
617 name = r.FormValue("xid")
618 honks = gethonksbyxonker(u.UserID, name)
619 } else {
620 honks = gethonksbyhonker(u.UserID, name)
621 }
622 name = html.EscapeString(name)
623 msg := fmt.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, name, name)
624 honkpage(w, r, u, nil, honks, template.HTML(msg))
625}
626
627func showcombo(w http.ResponseWriter, r *http.Request) {
628 name := mux.Vars(r)["name"]
629 u := login.GetUserInfo(r)
630 honks := gethonksbycombo(u.UserID, name)
631 honks = osmosis(honks, u.UserID)
632 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks by combo: "+name)))
633}
634func showconvoy(w http.ResponseWriter, r *http.Request) {
635 c := r.FormValue("c")
636 u := login.GetUserInfo(r)
637 honks := gethonksbyconvoy(u.UserID, c)
638 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks in convoy: "+c)))
639}
640func showontology(w http.ResponseWriter, r *http.Request) {
641 name := mux.Vars(r)["name"]
642 u := login.GetUserInfo(r)
643 var userid int64 = -1
644 if u != nil {
645 userid = u.UserID
646 }
647 honks := gethonksbyontology(userid, "#"+name)
648 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks by ontology: "+name)))
649}
650
651func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
652 u := login.GetUserInfo(r)
653 var userid int64 = -1
654 if u != nil {
655 userid = u.UserID
656 }
657 rows, err := stmtSelectOnts.Query(userid)
658 if err != nil {
659 log.Printf("selection error: %s", err)
660 return
661 }
662 var onts [][]string
663 for rows.Next() {
664 var o string
665 err := rows.Scan(&o)
666 if err != nil {
667 log.Printf("error scanning ont: %s", err)
668 continue
669 }
670 onts = append(onts, []string{o, o[1:]})
671 }
672 if u == nil {
673 w.Header().Set("Cache-Control", "max-age=300")
674 }
675 templinfo := getInfo(r)
676 templinfo["Onts"] = onts
677 err = readviews.Execute(w, "onts.html", templinfo)
678 if err != nil {
679 log.Print(err)
680 }
681}
682
683func showhonk(w http.ResponseWriter, r *http.Request) {
684 name := mux.Vars(r)["name"]
685 user, err := butwhatabout(name)
686 if err != nil {
687 http.NotFound(w, r)
688 return
689 }
690 if stealthed(r) {
691 http.NotFound(w, r)
692 return
693 }
694
695 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
696 honk := getxonk(user.ID, xid)
697 if honk == nil {
698 http.NotFound(w, r)
699 return
700 }
701 u := login.GetUserInfo(r)
702 if u != nil && u.UserID != user.ID {
703 u = nil
704 }
705 if !honk.Public {
706 if u == nil {
707 http.NotFound(w, r)
708 return
709
710 }
711 honkpage(w, r, u, nil, []*Honk{honk}, "one honk maybe more")
712 return
713 }
714 rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy)
715 if friendorfoe(r.Header.Get("Accept")) {
716 for _, h := range rawhonks {
717 if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
718 honk.Replies = append(honk.Replies, h)
719 }
720 }
721 donksforhonks([]*Honk{honk})
722 _, j := jonkjonk(user, honk)
723 j["@context"] = itiswhatitis
724 w.Header().Set("Content-Type", theonetruename)
725 j.Write(w)
726 return
727 }
728 var honks []*Honk
729 for _, h := range rawhonks {
730 if h.Public && (h.Whofore == 2 || h.IsAcked()) {
731 honks = append(honks, h)
732 }
733 }
734
735 honkpage(w, r, u, nil, honks, "one honk maybe more")
736}
737
738func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
739 honks []*Honk, infomsg template.HTML) {
740 templinfo := getInfo(r)
741 var userid int64 = -1
742 if u != nil {
743 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
744 userid = u.UserID
745 }
746 if u == nil {
747 w.Header().Set("Cache-Control", "max-age=60")
748 }
749 reverbolate(userid, honks)
750 if user != nil {
751 filt := htfilter.New()
752 templinfo["Name"] = user.Name
753 whatabout := user.About
754 whatabout = obfusbreak(user.About)
755 templinfo["WhatAbout"], _ = filt.String(whatabout)
756 }
757 templinfo["Honks"] = honks
758 templinfo["ServerMessage"] = infomsg
759 err := readviews.Execute(w, "honkpage.html", templinfo)
760 if err != nil {
761 log.Print(err)
762 }
763}
764
765func saveuser(w http.ResponseWriter, r *http.Request) {
766 whatabout := r.FormValue("whatabout")
767 u := login.GetUserInfo(r)
768 db := opendatabase()
769 options := ""
770 if r.FormValue("skinny") == "skinny" {
771 options += " skinny "
772 }
773 _, err := db.Exec("update users set about = ?, options = ? where username = ?", whatabout, options, u.Username)
774 if err != nil {
775 log.Printf("error bouting what: %s", err)
776 }
777
778 http.Redirect(w, r, "/account", http.StatusSeeOther)
779}
780
781func gethonkers(userid int64) []*Honker {
782 rows, err := stmtHonkers.Query(userid)
783 if err != nil {
784 log.Printf("error querying honkers: %s", err)
785 return nil
786 }
787 defer rows.Close()
788 var honkers []*Honker
789 for rows.Next() {
790 var f Honker
791 var combos string
792 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
793 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
794 if err != nil {
795 log.Printf("error scanning honker: %s", err)
796 return nil
797 }
798 honkers = append(honkers, &f)
799 }
800 return honkers
801}
802
803func getdubs(userid int64) []*Honker {
804 rows, err := stmtDubbers.Query(userid)
805 if err != nil {
806 log.Printf("error querying dubs: %s", err)
807 return nil
808 }
809 defer rows.Close()
810 var honkers []*Honker
811 for rows.Next() {
812 var f Honker
813 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
814 if err != nil {
815 log.Printf("error scanning honker: %s", err)
816 return nil
817 }
818 honkers = append(honkers, &f)
819 }
820 return honkers
821}
822
823func allusers() []login.UserInfo {
824 var users []login.UserInfo
825 rows, _ := opendatabase().Query("select userid, username from users")
826 defer rows.Close()
827 for rows.Next() {
828 var u login.UserInfo
829 rows.Scan(&u.UserID, &u.Username)
830 users = append(users, u)
831 }
832 return users
833}
834
835func getxonk(userid int64, xid string) *Honk {
836 h := new(Honk)
837 var dt, aud, onts string
838 row := stmtOneXonk.QueryRow(userid, xid)
839 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
840 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
841 if err != nil {
842 if err != sql.ErrNoRows {
843 log.Printf("error scanning xonk: %s", err)
844 }
845 return nil
846 }
847 h.Date, _ = time.Parse(dbtimeformat, dt)
848 h.Audience = strings.Split(aud, " ")
849 h.Public = !keepitquiet(h.Audience)
850 if len(onts) > 0 {
851 h.Onts = strings.Split(onts, " ")
852 }
853 return h
854}
855
856func getbonk(userid int64, xid string) *Honk {
857 h := new(Honk)
858 var dt, aud, onts string
859 row := stmtOneBonk.QueryRow(userid, xid)
860 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
861 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
862 if err != nil {
863 if err != sql.ErrNoRows {
864 log.Printf("error scanning xonk: %s", err)
865 }
866 return nil
867 }
868 h.Date, _ = time.Parse(dbtimeformat, dt)
869 h.Audience = strings.Split(aud, " ")
870 h.Public = !keepitquiet(h.Audience)
871 if len(onts) > 0 {
872 h.Onts = strings.Split(onts, " ")
873 }
874 return h
875}
876
877func getpublichonks() []*Honk {
878 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
879 rows, err := stmtPublicHonks.Query(dt)
880 return getsomehonks(rows, err)
881}
882func gethonksbyuser(name string, includeprivate bool) []*Honk {
883 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
884 whofore := 2
885 if includeprivate {
886 whofore = 3
887 }
888 rows, err := stmtUserHonks.Query(whofore, name, dt)
889 return getsomehonks(rows, err)
890}
891func gethonksforuser(userid int64) []*Honk {
892 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
893 rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
894 return getsomehonks(rows, err)
895}
896func gethonksforme(userid int64) []*Honk {
897 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
898 rows, err := stmtHonksForMe.Query(userid, dt, userid)
899 return getsomehonks(rows, err)
900}
901func gethonksbyhonker(userid int64, honker string) []*Honk {
902 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
903 return getsomehonks(rows, err)
904}
905func gethonksbyxonker(userid int64, xonker string) []*Honk {
906 rows, err := stmtHonksByXonker.Query(userid, xonker, xonker, userid)
907 return getsomehonks(rows, err)
908}
909func gethonksbycombo(userid int64, combo string) []*Honk {
910 combo = "% " + combo + " %"
911 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
912 return getsomehonks(rows, err)
913}
914func gethonksbyconvoy(userid int64, convoy string) []*Honk {
915 rows, err := stmtHonksByConvoy.Query(userid, userid, convoy)
916 honks := getsomehonks(rows, err)
917 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
918 honks[i], honks[j] = honks[j], honks[i]
919 }
920 return honks
921}
922func gethonksbyontology(userid int64, name string) []*Honk {
923 rows, err := stmtHonksByOntology.Query(name, userid, userid)
924 honks := getsomehonks(rows, err)
925 return honks
926}
927
928func getsomehonks(rows *sql.Rows, err error) []*Honk {
929 if err != nil {
930 log.Printf("error querying honks: %s", err)
931 return nil
932 }
933 defer rows.Close()
934 var honks []*Honk
935 for rows.Next() {
936 var h Honk
937 var dt, aud, onts string
938 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID,
939 &h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
940 if err != nil {
941 log.Printf("error scanning honks: %s", err)
942 return nil
943 }
944 h.Date, _ = time.Parse(dbtimeformat, dt)
945 h.Audience = strings.Split(aud, " ")
946 h.Public = !keepitquiet(h.Audience)
947 if len(onts) > 0 {
948 h.Onts = strings.Split(onts, " ")
949 }
950 honks = append(honks, &h)
951 }
952 rows.Close()
953 donksforhonks(honks)
954 return honks
955}
956
957func donksforhonks(honks []*Honk) {
958 db := opendatabase()
959 var ids []string
960 hmap := make(map[int64]*Honk)
961 for _, h := range honks {
962 ids = append(ids, fmt.Sprintf("%d", h.ID))
963 hmap[h.ID] = h
964 }
965 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, ","))
966 rows, err := db.Query(q)
967 if err != nil {
968 log.Printf("error querying donks: %s", err)
969 return
970 }
971 defer rows.Close()
972 for rows.Next() {
973 var hid int64
974 var d Donk
975 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media, &d.Local)
976 if err != nil {
977 log.Printf("error scanning donk: %s", err)
978 continue
979 }
980 h := hmap[hid]
981 h.Donks = append(h.Donks, &d)
982 }
983}
984
985func savebonk(w http.ResponseWriter, r *http.Request) {
986 xid := r.FormValue("xid")
987 userinfo := login.GetUserInfo(r)
988 user, _ := butwhatabout(userinfo.Username)
989
990 log.Printf("bonking %s", xid)
991
992 xonk := getxonk(userinfo.UserID, xid)
993 if xonk == nil {
994 return
995 }
996 if !xonk.Public {
997 return
998 }
999 donksforhonks([]*Honk{xonk})
1000
1001 _, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
1002 if err != nil {
1003 log.Printf("error acking bonk: %s", err)
1004 }
1005
1006 oonker := xonk.Oonker
1007 if oonker == "" {
1008 oonker = xonk.Honker
1009 }
1010 dt := time.Now().UTC()
1011 bonk := Honk{
1012 UserID: userinfo.UserID,
1013 Username: userinfo.Username,
1014 What: "bonk",
1015 Honker: user.URL,
1016 XID: xonk.XID,
1017 Date: dt,
1018 Donks: xonk.Donks,
1019 Convoy: xonk.Convoy,
1020 Audience: []string{thewholeworld, oonker},
1021 Public: true,
1022 }
1023
1024 aud := strings.Join(bonk.Audience, " ")
1025 whofore := 2
1026 onts := xonk.Onts
1027 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
1028 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
1029 xonk.Precis, oonker, 0, strings.Join(onts, " "))
1030 if err != nil {
1031 log.Printf("error saving bonk: %s", err)
1032 return
1033 }
1034 bonk.ID, _ = res.LastInsertId()
1035 for _, d := range bonk.Donks {
1036 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
1037 if err != nil {
1038 log.Printf("err saving donk: %s", err)
1039 return
1040 }
1041 }
1042 for _, o := range onts {
1043 _, err = stmtSaveOnts.Exec(strings.ToLower(o), bonk.ID)
1044 if err != nil {
1045 log.Printf("error saving ont: %s", err)
1046 }
1047 }
1048
1049 go honkworldwide(user, &bonk)
1050}
1051
1052func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
1053 zonk := Honk{
1054 What: what,
1055 XID: xonk.XID,
1056 Date: time.Now().UTC(),
1057 Audience: oneofakind(xonk.Audience),
1058 }
1059 zonk.Public = !keepitquiet(zonk.Audience)
1060
1061 log.Printf("announcing %sed honk: %s", what, xonk.XID)
1062 go honkworldwide(user, &zonk)
1063}
1064
1065func zonkit(w http.ResponseWriter, r *http.Request) {
1066 wherefore := r.FormValue("wherefore")
1067 what := r.FormValue("what")
1068 userinfo := login.GetUserInfo(r)
1069 user, _ := butwhatabout(userinfo.Username)
1070
1071 if wherefore == "ack" {
1072 xonk := getxonk(userinfo.UserID, what)
1073 if xonk != nil {
1074 _, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1075 if err != nil {
1076 log.Printf("error acking: %s", err)
1077 }
1078 sendzonkofsorts(xonk, user, "ack")
1079 }
1080 return
1081 }
1082
1083 if wherefore == "deack" {
1084 xonk := getxonk(userinfo.UserID, what)
1085 if xonk != nil {
1086 _, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1087 if err != nil {
1088 log.Printf("error deacking: %s", err)
1089 }
1090 sendzonkofsorts(xonk, user, "deack")
1091 }
1092 return
1093 }
1094
1095 if wherefore == "unbonk" {
1096 xonk := getbonk(userinfo.UserID, what)
1097 if xonk != nil {
1098 _, err := stmtZonkDonks.Exec(xonk.ID)
1099 if err != nil {
1100 log.Printf("error zonking: %s", err)
1101 }
1102 _, err = stmtZonkIt.Exec(xonk.ID)
1103 if err != nil {
1104 log.Printf("error zonking: %s", err)
1105 }
1106 xonk = getxonk(userinfo.UserID, what)
1107 _, err = stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1108 if err != nil {
1109 log.Printf("error unbonking: %s", err)
1110 }
1111 sendzonkofsorts(xonk, user, "unbonk")
1112 }
1113 return
1114 }
1115
1116 log.Printf("zonking %s %s", wherefore, what)
1117 if wherefore == "zonk" {
1118 xonk := getxonk(userinfo.UserID, what)
1119 if xonk != nil {
1120 _, err := stmtZonkDonks.Exec(xonk.ID)
1121 if err != nil {
1122 log.Printf("error zonking: %s", err)
1123 }
1124 _, err = stmtZonkIt.Exec(xonk.ID)
1125 if err != nil {
1126 log.Printf("error zonking: %s", err)
1127 }
1128 if xonk.Whofore == 2 || xonk.Whofore == 3 {
1129 sendzonkofsorts(xonk, user, "zonk")
1130 }
1131 }
1132 }
1133 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1134 if err != nil {
1135 log.Printf("error saving zonker: %s", err)
1136 return
1137 }
1138}
1139
1140func savehonk(w http.ResponseWriter, r *http.Request) {
1141 rid := r.FormValue("rid")
1142 noise := r.FormValue("noise")
1143
1144 userinfo := login.GetUserInfo(r)
1145 user, _ := butwhatabout(userinfo.Username)
1146
1147 dt := time.Now().UTC()
1148 xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1149 what := "honk"
1150 if rid != "" {
1151 what = "tonk"
1152 }
1153 honk := Honk{
1154 UserID: userinfo.UserID,
1155 Username: userinfo.Username,
1156 What: "honk",
1157 Honker: user.URL,
1158 XID: xid,
1159 Date: dt,
1160 }
1161 if strings.HasPrefix(noise, "DZ:") {
1162 idx := strings.Index(noise, "\n")
1163 if idx == -1 {
1164 honk.Precis = noise
1165 noise = ""
1166 } else {
1167 honk.Precis = noise[:idx]
1168 noise = noise[idx+1:]
1169 }
1170 }
1171 noise = hooterize(noise)
1172 noise = strings.TrimSpace(noise)
1173 honk.Precis = strings.TrimSpace(honk.Precis)
1174
1175 var convoy string
1176 if rid != "" {
1177 xonk := getxonk(userinfo.UserID, rid)
1178 if xonk != nil {
1179 if xonk.Public {
1180 honk.Audience = append(honk.Audience, xonk.Audience...)
1181 }
1182 convoy = xonk.Convoy
1183 } else {
1184 xonkaud, c := whosthere(rid)
1185 honk.Audience = append(honk.Audience, xonkaud...)
1186 convoy = c
1187 }
1188 for i, a := range honk.Audience {
1189 if a == thewholeworld {
1190 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1191 break
1192 }
1193 }
1194 honk.RID = rid
1195 } else {
1196 honk.Audience = []string{thewholeworld}
1197 }
1198 if noise != "" && noise[0] == '@' {
1199 honk.Audience = append(grapevine(noise), honk.Audience...)
1200 } else {
1201 honk.Audience = append(honk.Audience, grapevine(noise)...)
1202 }
1203 if convoy == "" {
1204 convoy = "data:,electrichonkytonk-" + xfiltrate()
1205 }
1206 butnottooloud(honk.Audience)
1207 honk.Audience = oneofakind(honk.Audience)
1208 if len(honk.Audience) == 0 {
1209 log.Printf("honk to nowhere")
1210 http.Error(w, "honk to nowhere...", http.StatusNotFound)
1211 return
1212 }
1213 honk.Public = !keepitquiet(honk.Audience)
1214 noise = obfusbreak(noise)
1215 honk.Noise = noise
1216 honk.Convoy = convoy
1217
1218 donkxid := r.FormValue("donkxid")
1219 if donkxid == "" {
1220 file, filehdr, err := r.FormFile("donk")
1221 if err == nil {
1222 var buf bytes.Buffer
1223 io.Copy(&buf, file)
1224 file.Close()
1225 data := buf.Bytes()
1226 xid := xfiltrate()
1227 var media, name string
1228 img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
1229 if err == nil {
1230 data = img.Data
1231 format := img.Format
1232 media = "image/" + format
1233 if format == "jpeg" {
1234 format = "jpg"
1235 }
1236 name = xid + "." + format
1237 xid = name
1238 } else {
1239 maxsize := 100000
1240 if len(data) > maxsize {
1241 log.Printf("bad image: %s too much text: %d", err, len(data))
1242 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1243 return
1244 }
1245 for i := 0; i < len(data); i++ {
1246 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1247 log.Printf("bad image: %s not text: %d", err, data[i])
1248 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1249 return
1250 }
1251 }
1252 media = "text/plain"
1253 name = filehdr.Filename
1254 if name == "" {
1255 name = xid + ".txt"
1256 }
1257 xid += ".txt"
1258 }
1259 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1260 res, err := stmtSaveFile.Exec(xid, name, url, media, 1, data)
1261 if err != nil {
1262 log.Printf("unable to save image: %s", err)
1263 return
1264 }
1265 var d Donk
1266 d.FileID, _ = res.LastInsertId()
1267 d.XID = name
1268 d.Name = name
1269 d.Media = media
1270 d.URL = url
1271 d.Local = true
1272 honk.Donks = append(honk.Donks, &d)
1273 donkxid = d.XID
1274 }
1275 } else {
1276 xid := donkxid
1277 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1278 var donk Donk
1279 row := stmtFindFile.QueryRow(url)
1280 err := row.Scan(&donk.FileID)
1281 if err == nil {
1282 donk.XID = xid
1283 donk.Local = true
1284 donk.URL = url
1285 honk.Donks = append(honk.Donks, &donk)
1286 } else {
1287 log.Printf("can't find file: %s", xid)
1288 }
1289 }
1290 herd := herdofemus(honk.Noise)
1291 for _, e := range herd {
1292 donk := savedonk(e.ID, e.Name, "image/png", true)
1293 if donk != nil {
1294 donk.Name = e.Name
1295 honk.Donks = append(honk.Donks, donk)
1296 }
1297 }
1298 memetize(&honk)
1299
1300 aud := strings.Join(honk.Audience, " ")
1301 whofore := 2
1302 if !honk.Public {
1303 whofore = 3
1304 }
1305 if r.FormValue("preview") == "preview" {
1306 honks := []*Honk{&honk}
1307 reverbolate(userinfo.UserID, honks)
1308 templinfo := getInfo(r)
1309 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1310 templinfo["Honks"] = honks
1311 templinfo["InReplyTo"] = r.FormValue("rid")
1312 templinfo["Noise"] = r.FormValue("noise")
1313 templinfo["SavedFile"] = donkxid
1314 templinfo["ServerMessage"] = "honk preview"
1315 err := readviews.Execute(w, "honkpage.html", templinfo)
1316 if err != nil {
1317 log.Print(err)
1318 }
1319 return
1320 }
1321 honk.Onts = oneofakind(ontologies(honk.Noise))
1322 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
1323 dt.Format(dbtimeformat), "", aud, honk.Noise, convoy, whofore, "html",
1324 honk.Precis, honk.Oonker, 0, strings.Join(honk.Onts, " "))
1325 if err != nil {
1326 log.Printf("error saving honk: %s", err)
1327 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1328 return
1329 }
1330 honk.ID, _ = res.LastInsertId()
1331 for _, d := range honk.Donks {
1332 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
1333 if err != nil {
1334 log.Printf("err saving donk: %s", err)
1335 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1336 return
1337 }
1338 }
1339 for _, o := range honk.Onts {
1340 _, err = stmtSaveOnts.Exec(strings.ToLower(o), honk.ID)
1341 if err != nil {
1342 log.Printf("error saving ont: %s", err)
1343 }
1344 }
1345
1346 go honkworldwide(user, &honk)
1347
1348 http.Redirect(w, r, xid, http.StatusSeeOther)
1349}
1350
1351func showhonkers(w http.ResponseWriter, r *http.Request) {
1352 userinfo := login.GetUserInfo(r)
1353 templinfo := getInfo(r)
1354 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1355 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1356 err := readviews.Execute(w, "honkers.html", templinfo)
1357 if err != nil {
1358 log.Print(err)
1359 }
1360}
1361
1362func showcombos(w http.ResponseWriter, r *http.Request) {
1363 userinfo := login.GetUserInfo(r)
1364 templinfo := getInfo(r)
1365 honkers := gethonkers(userinfo.UserID)
1366 var combos []string
1367 for _, h := range honkers {
1368 combos = append(combos, h.Combos...)
1369 }
1370 for i, c := range combos {
1371 if c == "-" {
1372 combos[i] = ""
1373 }
1374 }
1375 combos = oneofakind(combos)
1376 sort.Strings(combos)
1377 templinfo["Combos"] = combos
1378 err := readviews.Execute(w, "combos.html", templinfo)
1379 if err != nil {
1380 log.Print(err)
1381 }
1382}
1383
1384func savehonker(w http.ResponseWriter, r *http.Request) {
1385 u := login.GetUserInfo(r)
1386 name := r.FormValue("name")
1387 url := r.FormValue("url")
1388 peep := r.FormValue("peep")
1389 combos := r.FormValue("combos")
1390 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1391
1392 if honkerid > 0 {
1393 goodbye := r.FormValue("goodbye")
1394 if goodbye == "F" {
1395 db := opendatabase()
1396 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1397 honkerid, u.UserID)
1398 var xid string
1399 err := row.Scan(&xid)
1400 if err != nil {
1401 log.Printf("can't get honker xid: %s", err)
1402 return
1403 }
1404 log.Printf("unsubscribing from %s", xid)
1405 user, _ := butwhatabout(u.Username)
1406 go itakeitallback(user, xid)
1407 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1408 if err != nil {
1409 log.Printf("error updating honker: %s", err)
1410 return
1411 }
1412
1413 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1414 return
1415 }
1416 combos = " " + strings.TrimSpace(combos) + " "
1417 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1418 if err != nil {
1419 log.Printf("update honker err: %s", err)
1420 return
1421 }
1422 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1423 }
1424
1425 flavor := "presub"
1426 if peep == "peep" {
1427 flavor = "peep"
1428 }
1429 p := investigate(url)
1430 if p == nil {
1431 log.Printf("failed to investigate honker")
1432 return
1433 }
1434 url = p.XID
1435 if name == "" {
1436 name = p.Handle
1437 }
1438 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1439 if err != nil {
1440 log.Print(err)
1441 return
1442 }
1443 if flavor == "presub" {
1444 user, _ := butwhatabout(u.Username)
1445 go subsub(user, url)
1446 }
1447 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1448}
1449
1450type Zonker struct {
1451 ID int64
1452 Name string
1453 Wherefore string
1454}
1455
1456func zonkzone(w http.ResponseWriter, r *http.Request) {
1457 userinfo := login.GetUserInfo(r)
1458 rows, err := stmtGetZonkers.Query(userinfo.UserID)
1459 if err != nil {
1460 log.Printf("err: %s", err)
1461 return
1462 }
1463 defer rows.Close()
1464 var zonkers []Zonker
1465 for rows.Next() {
1466 var z Zonker
1467 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1468 zonkers = append(zonkers, z)
1469 }
1470 sort.Slice(zonkers, func(i, j int) bool {
1471 w1 := zonkers[i].Wherefore
1472 w2 := zonkers[j].Wherefore
1473 if w1 == w2 {
1474 return zonkers[i].Name < zonkers[j].Name
1475 }
1476 if w1 == "zonvoy" {
1477 w1 = "zzzzzzz"
1478 }
1479 if w2 == "zonvoy" {
1480 w2 = "zzzzzzz"
1481 }
1482 return w1 < w2
1483 })
1484
1485 templinfo := getInfo(r)
1486 templinfo["Zonkers"] = zonkers
1487 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1488 err = readviews.Execute(w, "zonkers.html", templinfo)
1489 if err != nil {
1490 log.Print(err)
1491 }
1492}
1493
1494func zonkzonk(w http.ResponseWriter, r *http.Request) {
1495 userinfo := login.GetUserInfo(r)
1496 itsok := r.FormValue("itsok")
1497 if itsok == "iforgiveyou" {
1498 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1499 db := opendatabase()
1500 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1501 userinfo.UserID, zonkerid)
1502 bitethethumbs()
1503 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1504 return
1505 }
1506 wherefore := r.FormValue("wherefore")
1507 name := r.FormValue("name")
1508 if name == "" {
1509 return
1510 }
1511 switch wherefore {
1512 case "zonker":
1513 case "zomain":
1514 case "zonvoy":
1515 case "zord":
1516 case "zilence":
1517 default:
1518 return
1519 }
1520 db := opendatabase()
1521 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1522 userinfo.UserID, name, wherefore)
1523 if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" || wherefore == "zilence" {
1524 bitethethumbs()
1525 }
1526
1527 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1528}
1529
1530func accountpage(w http.ResponseWriter, r *http.Request) {
1531 u := login.GetUserInfo(r)
1532 user, _ := butwhatabout(u.Username)
1533 templinfo := getInfo(r)
1534 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1535 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1536 templinfo["User"] = user
1537 err := readviews.Execute(w, "account.html", templinfo)
1538 if err != nil {
1539 log.Print(err)
1540 }
1541}
1542
1543func dochpass(w http.ResponseWriter, r *http.Request) {
1544 err := login.ChangePassword(w, r)
1545 if err != nil {
1546 log.Printf("error changing password: %s", err)
1547 }
1548 http.Redirect(w, r, "/account", http.StatusSeeOther)
1549}
1550
1551func fingerlicker(w http.ResponseWriter, r *http.Request) {
1552 orig := r.FormValue("resource")
1553
1554 log.Printf("finger lick: %s", orig)
1555
1556 if strings.HasPrefix(orig, "acct:") {
1557 orig = orig[5:]
1558 }
1559
1560 name := orig
1561 idx := strings.LastIndexByte(name, '/')
1562 if idx != -1 {
1563 name = name[idx+1:]
1564 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1565 log.Printf("foreign request rejected")
1566 name = ""
1567 }
1568 } else {
1569 idx = strings.IndexByte(name, '@')
1570 if idx != -1 {
1571 name = name[:idx]
1572 if name+"@"+serverName != orig {
1573 log.Printf("foreign request rejected")
1574 name = ""
1575 }
1576 }
1577 }
1578 user, err := butwhatabout(name)
1579 if err != nil {
1580 http.NotFound(w, r)
1581 return
1582 }
1583
1584 j := junk.New()
1585 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1586 j["aliases"] = []string{user.URL}
1587 var links []junk.Junk
1588 l := junk.New()
1589 l["rel"] = "self"
1590 l["type"] = `application/activity+json`
1591 l["href"] = user.URL
1592 links = append(links, l)
1593 j["links"] = links
1594
1595 w.Header().Set("Cache-Control", "max-age=3600")
1596 w.Header().Set("Content-Type", "application/jrd+json")
1597 j.Write(w)
1598}
1599
1600func somedays() string {
1601 secs := 432000 + notrand.Int63n(432000)
1602 return fmt.Sprintf("%d", secs)
1603}
1604
1605func avatate(w http.ResponseWriter, r *http.Request) {
1606 n := r.FormValue("a")
1607 a := avatar(n)
1608 w.Header().Set("Cache-Control", "max-age="+somedays())
1609 w.Write(a)
1610}
1611
1612func servecss(w http.ResponseWriter, r *http.Request) {
1613 w.Header().Set("Cache-Control", "max-age=7776000")
1614 http.ServeFile(w, r, "views"+r.URL.Path)
1615}
1616func servehtml(w http.ResponseWriter, r *http.Request) {
1617 templinfo := getInfo(r)
1618 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1619 if err != nil {
1620 log.Print(err)
1621 }
1622}
1623func serveemu(w http.ResponseWriter, r *http.Request) {
1624 xid := mux.Vars(r)["xid"]
1625 w.Header().Set("Cache-Control", "max-age="+somedays())
1626 http.ServeFile(w, r, "emus/"+xid)
1627}
1628func servememe(w http.ResponseWriter, r *http.Request) {
1629 xid := mux.Vars(r)["xid"]
1630 w.Header().Set("Cache-Control", "max-age="+somedays())
1631 http.ServeFile(w, r, "memes/"+xid)
1632}
1633
1634func servefile(w http.ResponseWriter, r *http.Request) {
1635 xid := mux.Vars(r)["xid"]
1636 row := stmtFileData.QueryRow(xid)
1637 var media string
1638 var data []byte
1639 err := row.Scan(&media, &data)
1640 if err != nil {
1641 log.Printf("error loading file: %s", err)
1642 http.NotFound(w, r)
1643 return
1644 }
1645 w.Header().Set("Content-Type", media)
1646 w.Header().Set("X-Content-Type-Options", "nosniff")
1647 w.Header().Set("Cache-Control", "max-age="+somedays())
1648 w.Write(data)
1649}
1650
1651func nomoroboto(w http.ResponseWriter, r *http.Request) {
1652 io.WriteString(w, "User-agent: *\n")
1653 io.WriteString(w, "Disallow: /a\n")
1654 io.WriteString(w, "Disallow: /d\n")
1655 io.WriteString(w, "Disallow: /meme\n")
1656 for _, u := range allusers() {
1657 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1658 }
1659}
1660
1661func serve() {
1662 db := opendatabase()
1663 login.Init(db)
1664
1665 listener, err := openListener()
1666 if err != nil {
1667 log.Fatal(err)
1668 }
1669 go redeliverator()
1670
1671 debug := false
1672 getconfig("debug", &debug)
1673 readviews = templates.Load(debug,
1674 "views/honkpage.html",
1675 "views/honkfrags.html",
1676 "views/honkers.html",
1677 "views/zonkers.html",
1678 "views/combos.html",
1679 "views/honkform.html",
1680 "views/honk.html",
1681 "views/account.html",
1682 "views/about.html",
1683 "views/funzone.html",
1684 "views/login.html",
1685 "views/xzone.html",
1686 "views/header.html",
1687 "views/onts.html",
1688 )
1689 if !debug {
1690 s := "views/style.css"
1691 savedstyleparams[s] = getstyleparam(s)
1692 s = "views/local.css"
1693 savedstyleparams[s] = getstyleparam(s)
1694 }
1695
1696 bitethethumbs()
1697
1698 mux := mux.NewRouter()
1699 mux.Use(login.Checker)
1700
1701 posters := mux.Methods("POST").Subrouter()
1702 getters := mux.Methods("GET").Subrouter()
1703
1704 getters.HandleFunc("/", homepage)
1705 getters.HandleFunc("/front", homepage)
1706 getters.HandleFunc("/robots.txt", nomoroboto)
1707 getters.HandleFunc("/rss", showrss)
1708 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1709 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1710 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1711 posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1712 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1713 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1714 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1715 getters.HandleFunc("/a", avatate)
1716 getters.HandleFunc("/o", thelistingoftheontologies)
1717 getters.HandleFunc("/o/{name:[a-z0-9-]+}", showontology)
1718 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1719 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1720 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1721 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1722
1723 getters.HandleFunc("/style.css", servecss)
1724 getters.HandleFunc("/local.css", servecss)
1725 getters.HandleFunc("/about", servehtml)
1726 getters.HandleFunc("/login", servehtml)
1727 posters.HandleFunc("/dologin", login.LoginFunc)
1728 getters.HandleFunc("/logout", login.LogoutFunc)
1729
1730 loggedin := mux.NewRoute().Subrouter()
1731 loggedin.Use(login.Required)
1732 loggedin.HandleFunc("/account", accountpage)
1733 loggedin.HandleFunc("/funzone", showfunzone)
1734 loggedin.HandleFunc("/chpass", dochpass)
1735 loggedin.HandleFunc("/atme", homepage)
1736 loggedin.HandleFunc("/zonkzone", zonkzone)
1737 loggedin.HandleFunc("/xzone", xzone)
1738 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1739 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1740 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1741 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1742 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1743 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1744 loggedin.HandleFunc("/honkers", showhonkers)
1745 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1746 loggedin.HandleFunc("/h", showhonker)
1747 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1748 loggedin.HandleFunc("/c", showcombos)
1749 loggedin.HandleFunc("/t", showconvoy)
1750 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1751
1752 err = http.Serve(listener, mux)
1753 if err != nil {
1754 log.Fatal(err)
1755 }
1756}
1757
1758func cleanupdb(arg string) {
1759 db := opendatabase()
1760 days, err := strconv.Atoi(arg)
1761 if err != nil {
1762 honker := arg
1763 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1764 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1765 doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1766 } else {
1767 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1768 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)
1769 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)
1770 }
1771 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1772 for _, u := range allusers() {
1773 doordie(db, "delete from zonkers where userid = ? and wherefore = 'zonvoy' and zonkerid < (select zonkerid from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 1 offset 200)", u.UserID, u.UserID)
1774 }
1775}
1776
1777var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1778var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1779var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1780var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1781var stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1782var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1783var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1784var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
1785var stmtSelectOnts, stmtSaveOnts, stmtUpdateFlags, stmtClearFlags *sql.Stmt
1786
1787func preparetodie(db *sql.DB, s string) *sql.Stmt {
1788 stmt, err := db.Prepare(s)
1789 if err != nil {
1790 log.Fatalf("error %s: %s", err, s)
1791 }
1792 return stmt
1793}
1794
1795func prepareStatements(db *sql.DB) {
1796 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")
1797 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1798 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1799 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1800 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1801 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1802
1803 selecthonks := "select honks.honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, convoy, whofore, flags, onts from honks join users on honks.userid = users.userid "
1804 limit := " order by honks.honkid desc limit 250"
1805 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1806 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1807 stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
1808 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1809 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1810 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)
1811 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1812 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1813 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1814 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1815 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1816 stmtHonksByOntology = preparetodie(db, selecthonks+"join onts on honks.honkid = onts.honkid where onts.ontology = ? and (honks.userid = ? or (? = -1 and honks.whofore = 2))"+limit)
1817
1818 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags, onts) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1819 stmtSaveOnts = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
1820 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1821 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1822 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1823 stmtZonkIt = preparetodie(db, "delete from honks where honkid = ?")
1824 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1825 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1826 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1827 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey, options from users where username = ?")
1828 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1829 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1830 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1831 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1832 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1833 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zomain' or wherefore = 'zord' or wherefore = 'zilence')")
1834 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1835 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1836 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1837 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1838 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1839 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
1840 stmtRecentHonkers = preparetodie(db, "select distinct(honker) from honks where userid = ? and honker not in (select xid from honkers where userid = ? and flavor = 'sub') order by honkid desc limit 100")
1841 stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
1842 stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
1843 stmtSelectOnts = preparetodie(db, "select distinct(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2)")
1844}
1845
1846func ElaborateUnitTests() {
1847}
1848
1849func main() {
1850 cmd := "run"
1851 if len(os.Args) > 1 {
1852 cmd = os.Args[1]
1853 }
1854 switch cmd {
1855 case "init":
1856 initdb()
1857 case "upgrade":
1858 upgradedb()
1859 }
1860 db := opendatabase()
1861 dbversion := 0
1862 getconfig("dbversion", &dbversion)
1863 if dbversion != myVersion {
1864 log.Fatal("incorrect database version. run upgrade.")
1865 }
1866 getconfig("servermsg", &serverMsg)
1867 getconfig("servername", &serverName)
1868 getconfig("usersep", &userSep)
1869 getconfig("honksep", &honkSep)
1870 getconfig("dnf", &donotfedafterdark)
1871 prepareStatements(db)
1872 switch cmd {
1873 case "adduser":
1874 adduser()
1875 case "cleanup":
1876 arg := "30"
1877 if len(os.Args) > 2 {
1878 arg = os.Args[2]
1879 }
1880 cleanupdb(arg)
1881 case "ping":
1882 if len(os.Args) < 4 {
1883 fmt.Printf("usage: honk ping from to\n")
1884 return
1885 }
1886 name := os.Args[2]
1887 targ := os.Args[3]
1888 user, err := butwhatabout(name)
1889 if err != nil {
1890 log.Printf("unknown user")
1891 return
1892 }
1893 ping(user, targ)
1894 case "peep":
1895 peeppeep()
1896 case "run":
1897 serve()
1898 case "test":
1899 ElaborateUnitTests()
1900 default:
1901 log.Fatal("unknown command")
1902 }
1903}