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