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