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