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