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