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