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 templinfo := getInfo(r)
673 templinfo["Onts"] = onts
674 err = readviews.Execute(w, "onts.html", templinfo)
675 if err != nil {
676 log.Print(err)
677 }
678}
679
680func showhonk(w http.ResponseWriter, r *http.Request) {
681 name := mux.Vars(r)["name"]
682 user, err := butwhatabout(name)
683 if err != nil {
684 http.NotFound(w, r)
685 return
686 }
687 if stealthed(r) {
688 http.NotFound(w, r)
689 return
690 }
691
692 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
693 honk := getxonk(user.ID, xid)
694 if honk == nil {
695 http.NotFound(w, r)
696 return
697 }
698 u := login.GetUserInfo(r)
699 if u != nil && u.UserID != user.ID {
700 u = nil
701 }
702 if !honk.Public {
703 if u == nil {
704 http.NotFound(w, r)
705 return
706
707 }
708 honkpage(w, r, u, nil, []*Honk{honk}, "one honk maybe more")
709 return
710 }
711 rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy)
712 if friendorfoe(r.Header.Get("Accept")) {
713 for _, h := range rawhonks {
714 if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
715 honk.Replies = append(honk.Replies, h)
716 }
717 }
718 donksforhonks([]*Honk{honk})
719 _, j := jonkjonk(user, honk)
720 j["@context"] = itiswhatitis
721 w.Header().Set("Content-Type", theonetruename)
722 j.Write(w)
723 return
724 }
725 var honks []*Honk
726 for _, h := range rawhonks {
727 if h.Public && (h.Whofore == 2 || h.IsAcked()) {
728 honks = append(honks, h)
729 }
730 }
731
732 honkpage(w, r, u, nil, honks, "one honk maybe more")
733}
734
735func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
736 honks []*Honk, infomsg template.HTML) {
737 templinfo := getInfo(r)
738 var userid int64 = -1
739 if u != nil {
740 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
741 userid = u.UserID
742 }
743 if u == nil {
744 w.Header().Set("Cache-Control", "max-age=60")
745 }
746 reverbolate(userid, honks)
747 if user != nil {
748 filt := htfilter.New()
749 templinfo["Name"] = user.Name
750 whatabout := user.About
751 whatabout = obfusbreak(user.About)
752 templinfo["WhatAbout"], _ = filt.String(whatabout)
753 }
754 templinfo["Honks"] = honks
755 templinfo["ServerMessage"] = infomsg
756 err := readviews.Execute(w, "honkpage.html", templinfo)
757 if err != nil {
758 log.Print(err)
759 }
760}
761
762func saveuser(w http.ResponseWriter, r *http.Request) {
763 whatabout := r.FormValue("whatabout")
764 u := login.GetUserInfo(r)
765 db := opendatabase()
766 options := ""
767 if r.FormValue("skinny") == "skinny" {
768 options += " skinny "
769 }
770 _, err := db.Exec("update users set about = ?, options = ? where username = ?", whatabout, options, u.Username)
771 if err != nil {
772 log.Printf("error bouting what: %s", err)
773 }
774
775 http.Redirect(w, r, "/account", http.StatusSeeOther)
776}
777
778func gethonkers(userid int64) []*Honker {
779 rows, err := stmtHonkers.Query(userid)
780 if err != nil {
781 log.Printf("error querying honkers: %s", err)
782 return nil
783 }
784 defer rows.Close()
785 var honkers []*Honker
786 for rows.Next() {
787 var f Honker
788 var combos string
789 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
790 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
791 if err != nil {
792 log.Printf("error scanning honker: %s", err)
793 return nil
794 }
795 honkers = append(honkers, &f)
796 }
797 return honkers
798}
799
800func getdubs(userid int64) []*Honker {
801 rows, err := stmtDubbers.Query(userid)
802 if err != nil {
803 log.Printf("error querying dubs: %s", err)
804 return nil
805 }
806 defer rows.Close()
807 var honkers []*Honker
808 for rows.Next() {
809 var f Honker
810 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
811 if err != nil {
812 log.Printf("error scanning honker: %s", err)
813 return nil
814 }
815 honkers = append(honkers, &f)
816 }
817 return honkers
818}
819
820func allusers() []login.UserInfo {
821 var users []login.UserInfo
822 rows, _ := opendatabase().Query("select userid, username from users")
823 defer rows.Close()
824 for rows.Next() {
825 var u login.UserInfo
826 rows.Scan(&u.UserID, &u.Username)
827 users = append(users, u)
828 }
829 return users
830}
831
832func getxonk(userid int64, xid string) *Honk {
833 h := new(Honk)
834 var dt, aud, onts string
835 row := stmtOneXonk.QueryRow(userid, xid)
836 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
837 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
838 if err != nil {
839 if err != sql.ErrNoRows {
840 log.Printf("error scanning xonk: %s", err)
841 }
842 return nil
843 }
844 h.Date, _ = time.Parse(dbtimeformat, dt)
845 h.Audience = strings.Split(aud, " ")
846 h.Public = !keepitquiet(h.Audience)
847 h.Onts = strings.Split(onts, " ")
848 return h
849}
850
851func getbonk(userid int64, xid string) *Honk {
852 h := new(Honk)
853 var dt, aud, onts string
854 row := stmtOneBonk.QueryRow(userid, xid)
855 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
856 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
857 if err != nil {
858 if err != sql.ErrNoRows {
859 log.Printf("error scanning xonk: %s", err)
860 }
861 return nil
862 }
863 h.Date, _ = time.Parse(dbtimeformat, dt)
864 h.Audience = strings.Split(aud, " ")
865 h.Public = !keepitquiet(h.Audience)
866 h.Onts = strings.Split(onts, " ")
867 return h
868}
869
870func getpublichonks() []*Honk {
871 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
872 rows, err := stmtPublicHonks.Query(dt)
873 return getsomehonks(rows, err)
874}
875func gethonksbyuser(name string, includeprivate bool) []*Honk {
876 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
877 whofore := 2
878 if includeprivate {
879 whofore = 3
880 }
881 rows, err := stmtUserHonks.Query(whofore, name, dt)
882 return getsomehonks(rows, err)
883}
884func gethonksforuser(userid int64) []*Honk {
885 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
886 rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
887 return getsomehonks(rows, err)
888}
889func gethonksforme(userid int64) []*Honk {
890 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
891 rows, err := stmtHonksForMe.Query(userid, dt, userid)
892 return getsomehonks(rows, err)
893}
894func gethonksbyhonker(userid int64, honker string) []*Honk {
895 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
896 return getsomehonks(rows, err)
897}
898func gethonksbyxonker(userid int64, xonker string) []*Honk {
899 rows, err := stmtHonksByXonker.Query(userid, xonker, xonker, userid)
900 return getsomehonks(rows, err)
901}
902func gethonksbycombo(userid int64, combo string) []*Honk {
903 combo = "% " + combo + " %"
904 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
905 return getsomehonks(rows, err)
906}
907func gethonksbyconvoy(userid int64, convoy string) []*Honk {
908 rows, err := stmtHonksByConvoy.Query(userid, userid, convoy)
909 honks := getsomehonks(rows, err)
910 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
911 honks[i], honks[j] = honks[j], honks[i]
912 }
913 return honks
914}
915func gethonksbyontology(userid int64, name string) []*Honk {
916 rows, err := stmtHonksByOntology.Query(name, userid, userid)
917 honks := getsomehonks(rows, err)
918 return honks
919}
920
921func getsomehonks(rows *sql.Rows, err error) []*Honk {
922 if err != nil {
923 log.Printf("error querying honks: %s", err)
924 return nil
925 }
926 defer rows.Close()
927 var honks []*Honk
928 for rows.Next() {
929 var h Honk
930 var dt, aud, onts string
931 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID,
932 &h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
933 if err != nil {
934 log.Printf("error scanning honks: %s", err)
935 return nil
936 }
937 h.Date, _ = time.Parse(dbtimeformat, dt)
938 h.Audience = strings.Split(aud, " ")
939 h.Public = !keepitquiet(h.Audience)
940 h.Onts = strings.Split(onts, " ")
941 honks = append(honks, &h)
942 }
943 rows.Close()
944 donksforhonks(honks)
945 return honks
946}
947
948func donksforhonks(honks []*Honk) {
949 db := opendatabase()
950 var ids []string
951 hmap := make(map[int64]*Honk)
952 for _, h := range honks {
953 ids = append(ids, fmt.Sprintf("%d", h.ID))
954 hmap[h.ID] = h
955 }
956 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, ","))
957 rows, err := db.Query(q)
958 if err != nil {
959 log.Printf("error querying donks: %s", err)
960 return
961 }
962 defer rows.Close()
963 for rows.Next() {
964 var hid int64
965 var d Donk
966 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media, &d.Local)
967 if err != nil {
968 log.Printf("error scanning donk: %s", err)
969 continue
970 }
971 h := hmap[hid]
972 h.Donks = append(h.Donks, &d)
973 }
974}
975
976func savebonk(w http.ResponseWriter, r *http.Request) {
977 xid := r.FormValue("xid")
978 userinfo := login.GetUserInfo(r)
979 user, _ := butwhatabout(userinfo.Username)
980
981 log.Printf("bonking %s", xid)
982
983 xonk := getxonk(userinfo.UserID, xid)
984 if xonk == nil {
985 return
986 }
987 if !xonk.Public {
988 return
989 }
990 donksforhonks([]*Honk{xonk})
991
992 _, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
993 if err != nil {
994 log.Printf("error acking bonk: %s", err)
995 }
996
997 oonker := xonk.Oonker
998 if oonker == "" {
999 oonker = xonk.Honker
1000 }
1001 dt := time.Now().UTC()
1002 bonk := Honk{
1003 UserID: userinfo.UserID,
1004 Username: userinfo.Username,
1005 What: "bonk",
1006 Honker: user.URL,
1007 XID: xonk.XID,
1008 Date: dt,
1009 Donks: xonk.Donks,
1010 Convoy: xonk.Convoy,
1011 Audience: []string{thewholeworld, oonker},
1012 Public: true,
1013 }
1014
1015 aud := strings.Join(bonk.Audience, " ")
1016 whofore := 2
1017 onts := xonk.Onts
1018 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
1019 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
1020 xonk.Precis, oonker, 0, strings.Join(onts, " "))
1021 if err != nil {
1022 log.Printf("error saving bonk: %s", err)
1023 return
1024 }
1025 bonk.ID, _ = res.LastInsertId()
1026 for _, d := range bonk.Donks {
1027 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
1028 if err != nil {
1029 log.Printf("err saving donk: %s", err)
1030 return
1031 }
1032 }
1033 for _, o := range onts {
1034 _, err = stmtSaveOnts.Exec(strings.ToLower(o), bonk.ID)
1035 if err != nil {
1036 log.Printf("error saving ont: %s", err)
1037 }
1038 }
1039
1040 go honkworldwide(user, &bonk)
1041}
1042
1043func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
1044 zonk := Honk{
1045 What: what,
1046 XID: xonk.XID,
1047 Date: time.Now().UTC(),
1048 Audience: oneofakind(xonk.Audience),
1049 }
1050 zonk.Public = !keepitquiet(zonk.Audience)
1051
1052 log.Printf("announcing %sed honk: %s", what, xonk.XID)
1053 go honkworldwide(user, &zonk)
1054}
1055
1056func zonkit(w http.ResponseWriter, r *http.Request) {
1057 wherefore := r.FormValue("wherefore")
1058 what := r.FormValue("what")
1059 userinfo := login.GetUserInfo(r)
1060 user, _ := butwhatabout(userinfo.Username)
1061
1062 if wherefore == "ack" {
1063 xonk := getxonk(userinfo.UserID, what)
1064 if xonk != nil {
1065 _, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1066 if err != nil {
1067 log.Printf("error acking: %s", err)
1068 }
1069 sendzonkofsorts(xonk, user, "ack")
1070 }
1071 return
1072 }
1073
1074 if wherefore == "deack" {
1075 xonk := getxonk(userinfo.UserID, what)
1076 if xonk != nil {
1077 _, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1078 if err != nil {
1079 log.Printf("error deacking: %s", err)
1080 }
1081 sendzonkofsorts(xonk, user, "deack")
1082 }
1083 return
1084 }
1085
1086 if wherefore == "unbonk" {
1087 xonk := getbonk(userinfo.UserID, what)
1088 if xonk != nil {
1089 _, err := stmtZonkDonks.Exec(xonk.ID)
1090 if err != nil {
1091 log.Printf("error zonking: %s", err)
1092 }
1093 _, err = stmtZonkIt.Exec(xonk.ID)
1094 if err != nil {
1095 log.Printf("error zonking: %s", err)
1096 }
1097 xonk = getxonk(userinfo.UserID, what)
1098 _, err = stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1099 if err != nil {
1100 log.Printf("error unbonking: %s", err)
1101 }
1102 sendzonkofsorts(xonk, user, "unbonk")
1103 }
1104 return
1105 }
1106
1107 log.Printf("zonking %s %s", wherefore, what)
1108 if wherefore == "zonk" {
1109 xonk := getxonk(userinfo.UserID, what)
1110 if xonk != nil {
1111 _, err := stmtZonkDonks.Exec(xonk.ID)
1112 if err != nil {
1113 log.Printf("error zonking: %s", err)
1114 }
1115 _, err = stmtZonkIt.Exec(xonk.ID)
1116 if err != nil {
1117 log.Printf("error zonking: %s", err)
1118 }
1119 if xonk.Whofore == 2 || xonk.Whofore == 3 {
1120 sendzonkofsorts(xonk, user, "zonk")
1121 }
1122 }
1123 }
1124 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1125 if err != nil {
1126 log.Printf("error saving zonker: %s", err)
1127 return
1128 }
1129}
1130
1131func savehonk(w http.ResponseWriter, r *http.Request) {
1132 rid := r.FormValue("rid")
1133 noise := r.FormValue("noise")
1134
1135 userinfo := login.GetUserInfo(r)
1136 user, _ := butwhatabout(userinfo.Username)
1137
1138 dt := time.Now().UTC()
1139 xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1140 what := "honk"
1141 if rid != "" {
1142 what = "tonk"
1143 }
1144 honk := Honk{
1145 UserID: userinfo.UserID,
1146 Username: userinfo.Username,
1147 What: "honk",
1148 Honker: user.URL,
1149 XID: xid,
1150 Date: dt,
1151 }
1152 if strings.HasPrefix(noise, "DZ:") {
1153 idx := strings.Index(noise, "\n")
1154 if idx == -1 {
1155 honk.Precis = noise
1156 noise = ""
1157 } else {
1158 honk.Precis = noise[:idx]
1159 noise = noise[idx+1:]
1160 }
1161 }
1162 noise = hooterize(noise)
1163 noise = strings.TrimSpace(noise)
1164 honk.Precis = strings.TrimSpace(honk.Precis)
1165
1166 var convoy string
1167 if rid != "" {
1168 xonk := getxonk(userinfo.UserID, rid)
1169 if xonk != nil {
1170 if xonk.Public {
1171 honk.Audience = append(honk.Audience, xonk.Audience...)
1172 }
1173 convoy = xonk.Convoy
1174 } else {
1175 xonkaud, c := whosthere(rid)
1176 honk.Audience = append(honk.Audience, xonkaud...)
1177 convoy = c
1178 }
1179 for i, a := range honk.Audience {
1180 if a == thewholeworld {
1181 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1182 break
1183 }
1184 }
1185 honk.RID = rid
1186 } else {
1187 honk.Audience = []string{thewholeworld}
1188 }
1189 if noise != "" && noise[0] == '@' {
1190 honk.Audience = append(grapevine(noise), honk.Audience...)
1191 } else {
1192 honk.Audience = append(honk.Audience, grapevine(noise)...)
1193 }
1194 if convoy == "" {
1195 convoy = "data:,electrichonkytonk-" + xfiltrate()
1196 }
1197 butnottooloud(honk.Audience)
1198 honk.Audience = oneofakind(honk.Audience)
1199 if len(honk.Audience) == 0 {
1200 log.Printf("honk to nowhere")
1201 http.Error(w, "honk to nowhere...", http.StatusNotFound)
1202 return
1203 }
1204 honk.Public = !keepitquiet(honk.Audience)
1205 noise = obfusbreak(noise)
1206 honk.Noise = noise
1207 honk.Convoy = convoy
1208
1209 donkxid := r.FormValue("donkxid")
1210 if donkxid == "" {
1211 file, filehdr, err := r.FormFile("donk")
1212 if err == nil {
1213 var buf bytes.Buffer
1214 io.Copy(&buf, file)
1215 file.Close()
1216 data := buf.Bytes()
1217 xid := xfiltrate()
1218 var media, name string
1219 img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
1220 if err == nil {
1221 data = img.Data
1222 format := img.Format
1223 media = "image/" + format
1224 if format == "jpeg" {
1225 format = "jpg"
1226 }
1227 name = xid + "." + format
1228 xid = name
1229 } else {
1230 maxsize := 100000
1231 if len(data) > maxsize {
1232 log.Printf("bad image: %s too much text: %d", err, len(data))
1233 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1234 return
1235 }
1236 for i := 0; i < len(data); i++ {
1237 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1238 log.Printf("bad image: %s not text: %d", err, data[i])
1239 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1240 return
1241 }
1242 }
1243 media = "text/plain"
1244 name = filehdr.Filename
1245 if name == "" {
1246 name = xid + ".txt"
1247 }
1248 xid += ".txt"
1249 }
1250 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1251 res, err := stmtSaveFile.Exec(xid, name, url, media, 1, data)
1252 if err != nil {
1253 log.Printf("unable to save image: %s", err)
1254 return
1255 }
1256 var d Donk
1257 d.FileID, _ = res.LastInsertId()
1258 d.XID = name
1259 d.Name = name
1260 d.Media = media
1261 d.URL = url
1262 d.Local = true
1263 honk.Donks = append(honk.Donks, &d)
1264 donkxid = d.XID
1265 }
1266 } else {
1267 xid := donkxid
1268 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1269 var donk Donk
1270 row := stmtFindFile.QueryRow(url)
1271 err := row.Scan(&donk.FileID)
1272 if err == nil {
1273 donk.XID = xid
1274 donk.Local = true
1275 donk.URL = url
1276 honk.Donks = append(honk.Donks, &donk)
1277 } else {
1278 log.Printf("can't find file: %s", xid)
1279 }
1280 }
1281 herd := herdofemus(honk.Noise)
1282 for _, e := range herd {
1283 donk := savedonk(e.ID, e.Name, "image/png", true)
1284 if donk != nil {
1285 donk.Name = e.Name
1286 honk.Donks = append(honk.Donks, donk)
1287 }
1288 }
1289 memetize(&honk)
1290
1291 aud := strings.Join(honk.Audience, " ")
1292 whofore := 2
1293 if !honk.Public {
1294 whofore = 3
1295 }
1296 if r.FormValue("preview") == "preview" {
1297 honks := []*Honk{&honk}
1298 reverbolate(userinfo.UserID, honks)
1299 templinfo := getInfo(r)
1300 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1301 templinfo["Honks"] = honks
1302 templinfo["InReplyTo"] = r.FormValue("rid")
1303 templinfo["Noise"] = r.FormValue("noise")
1304 templinfo["SavedFile"] = donkxid
1305 templinfo["ServerMessage"] = "honk preview"
1306 err := readviews.Execute(w, "honkpage.html", templinfo)
1307 if err != nil {
1308 log.Print(err)
1309 }
1310 return
1311 }
1312 onts := ontologies(honk.Noise)
1313 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
1314 dt.Format(dbtimeformat), "", aud, honk.Noise, convoy, whofore, "html",
1315 honk.Precis, honk.Oonker, 0, strings.Join(onts, " "))
1316 if err != nil {
1317 log.Printf("error saving honk: %s", err)
1318 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1319 return
1320 }
1321 honk.ID, _ = res.LastInsertId()
1322 for _, d := range honk.Donks {
1323 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
1324 if err != nil {
1325 log.Printf("err saving donk: %s", err)
1326 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1327 return
1328 }
1329 }
1330 for _, o := range onts {
1331 _, err = stmtSaveOnts.Exec(strings.ToLower(o), honk.ID)
1332 if err != nil {
1333 log.Printf("error saving ont: %s", err)
1334 }
1335 }
1336
1337 go honkworldwide(user, &honk)
1338
1339 http.Redirect(w, r, xid, http.StatusSeeOther)
1340}
1341
1342func showhonkers(w http.ResponseWriter, r *http.Request) {
1343 userinfo := login.GetUserInfo(r)
1344 templinfo := getInfo(r)
1345 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1346 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1347 err := readviews.Execute(w, "honkers.html", templinfo)
1348 if err != nil {
1349 log.Print(err)
1350 }
1351}
1352
1353func showcombos(w http.ResponseWriter, r *http.Request) {
1354 userinfo := login.GetUserInfo(r)
1355 templinfo := getInfo(r)
1356 honkers := gethonkers(userinfo.UserID)
1357 var combos []string
1358 for _, h := range honkers {
1359 combos = append(combos, h.Combos...)
1360 }
1361 for i, c := range combos {
1362 if c == "-" {
1363 combos[i] = ""
1364 }
1365 }
1366 combos = oneofakind(combos)
1367 sort.Strings(combos)
1368 templinfo["Combos"] = combos
1369 err := readviews.Execute(w, "combos.html", templinfo)
1370 if err != nil {
1371 log.Print(err)
1372 }
1373}
1374
1375func savehonker(w http.ResponseWriter, r *http.Request) {
1376 u := login.GetUserInfo(r)
1377 name := r.FormValue("name")
1378 url := r.FormValue("url")
1379 peep := r.FormValue("peep")
1380 combos := r.FormValue("combos")
1381 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1382
1383 if honkerid > 0 {
1384 goodbye := r.FormValue("goodbye")
1385 if goodbye == "F" {
1386 db := opendatabase()
1387 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1388 honkerid, u.UserID)
1389 var xid string
1390 err := row.Scan(&xid)
1391 if err != nil {
1392 log.Printf("can't get honker xid: %s", err)
1393 return
1394 }
1395 log.Printf("unsubscribing from %s", xid)
1396 user, _ := butwhatabout(u.Username)
1397 go itakeitallback(user, xid)
1398 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1399 if err != nil {
1400 log.Printf("error updating honker: %s", err)
1401 return
1402 }
1403
1404 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1405 return
1406 }
1407 combos = " " + strings.TrimSpace(combos) + " "
1408 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1409 if err != nil {
1410 log.Printf("update honker err: %s", err)
1411 return
1412 }
1413 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1414 }
1415
1416 flavor := "presub"
1417 if peep == "peep" {
1418 flavor = "peep"
1419 }
1420 p := investigate(url)
1421 if p == nil {
1422 log.Printf("failed to investigate honker")
1423 return
1424 }
1425 url = p.XID
1426 if name == "" {
1427 name = p.Handle
1428 }
1429 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1430 if err != nil {
1431 log.Print(err)
1432 return
1433 }
1434 if flavor == "presub" {
1435 user, _ := butwhatabout(u.Username)
1436 go subsub(user, url)
1437 }
1438 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1439}
1440
1441type Zonker struct {
1442 ID int64
1443 Name string
1444 Wherefore string
1445}
1446
1447func zonkzone(w http.ResponseWriter, r *http.Request) {
1448 userinfo := login.GetUserInfo(r)
1449 rows, err := stmtGetZonkers.Query(userinfo.UserID)
1450 if err != nil {
1451 log.Printf("err: %s", err)
1452 return
1453 }
1454 defer rows.Close()
1455 var zonkers []Zonker
1456 for rows.Next() {
1457 var z Zonker
1458 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1459 zonkers = append(zonkers, z)
1460 }
1461 sort.Slice(zonkers, func(i, j int) bool {
1462 w1 := zonkers[i].Wherefore
1463 w2 := zonkers[j].Wherefore
1464 if w1 == w2 {
1465 return zonkers[i].Name < zonkers[j].Name
1466 }
1467 if w1 == "zonvoy" {
1468 w1 = "zzzzzzz"
1469 }
1470 if w2 == "zonvoy" {
1471 w2 = "zzzzzzz"
1472 }
1473 return w1 < w2
1474 })
1475
1476 templinfo := getInfo(r)
1477 templinfo["Zonkers"] = zonkers
1478 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1479 err = readviews.Execute(w, "zonkers.html", templinfo)
1480 if err != nil {
1481 log.Print(err)
1482 }
1483}
1484
1485func zonkzonk(w http.ResponseWriter, r *http.Request) {
1486 userinfo := login.GetUserInfo(r)
1487 itsok := r.FormValue("itsok")
1488 if itsok == "iforgiveyou" {
1489 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1490 db := opendatabase()
1491 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1492 userinfo.UserID, zonkerid)
1493 bitethethumbs()
1494 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1495 return
1496 }
1497 wherefore := r.FormValue("wherefore")
1498 name := r.FormValue("name")
1499 if name == "" {
1500 return
1501 }
1502 switch wherefore {
1503 case "zonker":
1504 case "zomain":
1505 case "zonvoy":
1506 case "zord":
1507 case "zilence":
1508 default:
1509 return
1510 }
1511 db := opendatabase()
1512 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1513 userinfo.UserID, name, wherefore)
1514 if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" || wherefore == "zilence" {
1515 bitethethumbs()
1516 }
1517
1518 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1519}
1520
1521func accountpage(w http.ResponseWriter, r *http.Request) {
1522 u := login.GetUserInfo(r)
1523 user, _ := butwhatabout(u.Username)
1524 templinfo := getInfo(r)
1525 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1526 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1527 templinfo["User"] = user
1528 err := readviews.Execute(w, "account.html", templinfo)
1529 if err != nil {
1530 log.Print(err)
1531 }
1532}
1533
1534func dochpass(w http.ResponseWriter, r *http.Request) {
1535 err := login.ChangePassword(w, r)
1536 if err != nil {
1537 log.Printf("error changing password: %s", err)
1538 }
1539 http.Redirect(w, r, "/account", http.StatusSeeOther)
1540}
1541
1542func fingerlicker(w http.ResponseWriter, r *http.Request) {
1543 orig := r.FormValue("resource")
1544
1545 log.Printf("finger lick: %s", orig)
1546
1547 if strings.HasPrefix(orig, "acct:") {
1548 orig = orig[5:]
1549 }
1550
1551 name := orig
1552 idx := strings.LastIndexByte(name, '/')
1553 if idx != -1 {
1554 name = name[idx+1:]
1555 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1556 log.Printf("foreign request rejected")
1557 name = ""
1558 }
1559 } else {
1560 idx = strings.IndexByte(name, '@')
1561 if idx != -1 {
1562 name = name[:idx]
1563 if name+"@"+serverName != orig {
1564 log.Printf("foreign request rejected")
1565 name = ""
1566 }
1567 }
1568 }
1569 user, err := butwhatabout(name)
1570 if err != nil {
1571 http.NotFound(w, r)
1572 return
1573 }
1574
1575 j := junk.New()
1576 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1577 j["aliases"] = []string{user.URL}
1578 var links []junk.Junk
1579 l := junk.New()
1580 l["rel"] = "self"
1581 l["type"] = `application/activity+json`
1582 l["href"] = user.URL
1583 links = append(links, l)
1584 j["links"] = links
1585
1586 w.Header().Set("Cache-Control", "max-age=3600")
1587 w.Header().Set("Content-Type", "application/jrd+json")
1588 j.Write(w)
1589}
1590
1591func somedays() string {
1592 secs := 432000 + notrand.Int63n(432000)
1593 return fmt.Sprintf("%d", secs)
1594}
1595
1596func avatate(w http.ResponseWriter, r *http.Request) {
1597 n := r.FormValue("a")
1598 a := avatar(n)
1599 w.Header().Set("Cache-Control", "max-age="+somedays())
1600 w.Write(a)
1601}
1602
1603func servecss(w http.ResponseWriter, r *http.Request) {
1604 w.Header().Set("Cache-Control", "max-age=7776000")
1605 http.ServeFile(w, r, "views"+r.URL.Path)
1606}
1607func servehtml(w http.ResponseWriter, r *http.Request) {
1608 templinfo := getInfo(r)
1609 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1610 if err != nil {
1611 log.Print(err)
1612 }
1613}
1614func serveemu(w http.ResponseWriter, r *http.Request) {
1615 xid := mux.Vars(r)["xid"]
1616 w.Header().Set("Cache-Control", "max-age="+somedays())
1617 http.ServeFile(w, r, "emus/"+xid)
1618}
1619func servememe(w http.ResponseWriter, r *http.Request) {
1620 xid := mux.Vars(r)["xid"]
1621 w.Header().Set("Cache-Control", "max-age="+somedays())
1622 http.ServeFile(w, r, "memes/"+xid)
1623}
1624
1625func servefile(w http.ResponseWriter, r *http.Request) {
1626 xid := mux.Vars(r)["xid"]
1627 row := stmtFileData.QueryRow(xid)
1628 var media string
1629 var data []byte
1630 err := row.Scan(&media, &data)
1631 if err != nil {
1632 log.Printf("error loading file: %s", err)
1633 http.NotFound(w, r)
1634 return
1635 }
1636 w.Header().Set("Content-Type", media)
1637 w.Header().Set("X-Content-Type-Options", "nosniff")
1638 w.Header().Set("Cache-Control", "max-age="+somedays())
1639 w.Write(data)
1640}
1641
1642func nomoroboto(w http.ResponseWriter, r *http.Request) {
1643 io.WriteString(w, "User-agent: *\n")
1644 io.WriteString(w, "Disallow: /a\n")
1645 io.WriteString(w, "Disallow: /d\n")
1646 io.WriteString(w, "Disallow: /meme\n")
1647 for _, u := range allusers() {
1648 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1649 }
1650}
1651
1652func serve() {
1653 db := opendatabase()
1654 login.Init(db)
1655
1656 listener, err := openListener()
1657 if err != nil {
1658 log.Fatal(err)
1659 }
1660 go redeliverator()
1661
1662 debug := false
1663 getconfig("debug", &debug)
1664 readviews = templates.Load(debug,
1665 "views/honkpage.html",
1666 "views/honkfrags.html",
1667 "views/honkers.html",
1668 "views/zonkers.html",
1669 "views/combos.html",
1670 "views/honkform.html",
1671 "views/honk.html",
1672 "views/account.html",
1673 "views/about.html",
1674 "views/funzone.html",
1675 "views/login.html",
1676 "views/xzone.html",
1677 "views/header.html",
1678 "views/onts.html",
1679 )
1680 if !debug {
1681 s := "views/style.css"
1682 savedstyleparams[s] = getstyleparam(s)
1683 s = "views/local.css"
1684 savedstyleparams[s] = getstyleparam(s)
1685 }
1686
1687 bitethethumbs()
1688
1689 mux := mux.NewRouter()
1690 mux.Use(login.Checker)
1691
1692 posters := mux.Methods("POST").Subrouter()
1693 getters := mux.Methods("GET").Subrouter()
1694
1695 getters.HandleFunc("/", homepage)
1696 getters.HandleFunc("/front", homepage)
1697 getters.HandleFunc("/robots.txt", nomoroboto)
1698 getters.HandleFunc("/rss", showrss)
1699 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1700 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1701 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1702 posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1703 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1704 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1705 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1706 getters.HandleFunc("/a", avatate)
1707 getters.HandleFunc("/o", thelistingoftheontologies)
1708 getters.HandleFunc("/o/{name:[a-z0-9-]+}", showontology)
1709 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1710 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1711 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1712 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1713
1714 getters.HandleFunc("/style.css", servecss)
1715 getters.HandleFunc("/local.css", servecss)
1716 getters.HandleFunc("/about", servehtml)
1717 getters.HandleFunc("/login", servehtml)
1718 posters.HandleFunc("/dologin", login.LoginFunc)
1719 getters.HandleFunc("/logout", login.LogoutFunc)
1720
1721 loggedin := mux.NewRoute().Subrouter()
1722 loggedin.Use(login.Required)
1723 loggedin.HandleFunc("/account", accountpage)
1724 loggedin.HandleFunc("/funzone", showfunzone)
1725 loggedin.HandleFunc("/chpass", dochpass)
1726 loggedin.HandleFunc("/atme", homepage)
1727 loggedin.HandleFunc("/zonkzone", zonkzone)
1728 loggedin.HandleFunc("/xzone", xzone)
1729 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1730 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1731 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1732 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1733 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1734 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1735 loggedin.HandleFunc("/honkers", showhonkers)
1736 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1737 loggedin.HandleFunc("/h", showhonker)
1738 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1739 loggedin.HandleFunc("/c", showcombos)
1740 loggedin.HandleFunc("/t", showconvoy)
1741 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1742
1743 err = http.Serve(listener, mux)
1744 if err != nil {
1745 log.Fatal(err)
1746 }
1747}
1748
1749func cleanupdb(arg string) {
1750 db := opendatabase()
1751 days, err := strconv.Atoi(arg)
1752 if err != nil {
1753 honker := arg
1754 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1755 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1756 doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1757 } else {
1758 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1759 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)
1760 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)
1761 }
1762 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1763 for _, u := range allusers() {
1764 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)
1765 }
1766}
1767
1768var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1769var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1770var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1771var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1772var stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1773var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1774var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1775var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
1776var stmtSelectOnts, stmtSaveOnts, stmtUpdateFlags, stmtClearFlags *sql.Stmt
1777
1778func preparetodie(db *sql.DB, s string) *sql.Stmt {
1779 stmt, err := db.Prepare(s)
1780 if err != nil {
1781 log.Fatalf("error %s: %s", err, s)
1782 }
1783 return stmt
1784}
1785
1786func prepareStatements(db *sql.DB) {
1787 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")
1788 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1789 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1790 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1791 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1792 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1793
1794 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 "
1795 limit := " order by honks.honkid desc limit 250"
1796 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1797 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1798 stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
1799 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1800 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1801 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)
1802 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1803 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1804 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1805 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1806 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1807 stmtHonksByOntology = preparetodie(db, selecthonks+"join onts on honks.honkid = onts.honkid where onts.ontology = ? and (honks.userid = ? or (? = -1 and honks.whofore = 2))"+limit)
1808
1809 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags, onts) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1810 stmtSaveOnts = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
1811 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1812 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1813 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1814 stmtZonkIt = preparetodie(db, "delete from honks where honkid = ?")
1815 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1816 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1817 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1818 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey, options from users where username = ?")
1819 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1820 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1821 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1822 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1823 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1824 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zomain' or wherefore = 'zord' or wherefore = 'zilence')")
1825 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1826 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1827 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1828 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1829 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1830 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
1831 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")
1832 stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
1833 stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
1834 stmtSelectOnts = preparetodie(db, "select distinct(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2)")
1835}
1836
1837func ElaborateUnitTests() {
1838}
1839
1840func main() {
1841 cmd := "run"
1842 if len(os.Args) > 1 {
1843 cmd = os.Args[1]
1844 }
1845 switch cmd {
1846 case "init":
1847 initdb()
1848 case "upgrade":
1849 upgradedb()
1850 }
1851 db := opendatabase()
1852 dbversion := 0
1853 getconfig("dbversion", &dbversion)
1854 if dbversion != myVersion {
1855 log.Fatal("incorrect database version. run upgrade.")
1856 }
1857 getconfig("servermsg", &serverMsg)
1858 getconfig("servername", &serverName)
1859 getconfig("usersep", &userSep)
1860 getconfig("honksep", &honkSep)
1861 getconfig("dnf", &donotfedafterdark)
1862 prepareStatements(db)
1863 switch cmd {
1864 case "adduser":
1865 adduser()
1866 case "cleanup":
1867 arg := "30"
1868 if len(os.Args) > 2 {
1869 arg = os.Args[2]
1870 }
1871 cleanupdb(arg)
1872 case "ping":
1873 if len(os.Args) < 4 {
1874 fmt.Printf("usage: honk ping from to\n")
1875 return
1876 }
1877 name := os.Args[2]
1878 targ := os.Args[3]
1879 user, err := butwhatabout(name)
1880 if err != nil {
1881 log.Printf("unknown user")
1882 return
1883 }
1884 ping(user, targ)
1885 case "peep":
1886 peeppeep()
1887 case "run":
1888 serve()
1889 case "test":
1890 ElaborateUnitTests()
1891 default:
1892 log.Fatal("unknown command")
1893 }
1894}