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 showsearch(w http.ResponseWriter, r *http.Request) {
641 q := r.FormValue("q")
642 u := login.GetUserInfo(r)
643 honks := gethonksbysearch(u.UserID, q)
644 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks for search: "+q)))
645}
646func showontology(w http.ResponseWriter, r *http.Request) {
647 name := mux.Vars(r)["name"]
648 u := login.GetUserInfo(r)
649 var userid int64 = -1
650 if u != nil {
651 userid = u.UserID
652 }
653 honks := gethonksbyontology(userid, "#"+name)
654 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks by ontology: "+name)))
655}
656
657func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
658 u := login.GetUserInfo(r)
659 var userid int64 = -1
660 if u != nil {
661 userid = u.UserID
662 }
663 rows, err := stmtSelectOnts.Query(userid)
664 if err != nil {
665 log.Printf("selection error: %s", err)
666 return
667 }
668 var onts [][]string
669 for rows.Next() {
670 var o string
671 err := rows.Scan(&o)
672 if err != nil {
673 log.Printf("error scanning ont: %s", err)
674 continue
675 }
676 onts = append(onts, []string{o, o[1:]})
677 }
678 if u == nil {
679 w.Header().Set("Cache-Control", "max-age=300")
680 }
681 templinfo := getInfo(r)
682 templinfo["Onts"] = onts
683 err = readviews.Execute(w, "onts.html", templinfo)
684 if err != nil {
685 log.Print(err)
686 }
687}
688
689func showhonk(w http.ResponseWriter, r *http.Request) {
690 name := mux.Vars(r)["name"]
691 user, err := butwhatabout(name)
692 if err != nil {
693 http.NotFound(w, r)
694 return
695 }
696 if stealthed(r) {
697 http.NotFound(w, r)
698 return
699 }
700
701 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
702 honk := getxonk(user.ID, xid)
703 if honk == nil {
704 http.NotFound(w, r)
705 return
706 }
707 u := login.GetUserInfo(r)
708 if u != nil && u.UserID != user.ID {
709 u = nil
710 }
711 if !honk.Public {
712 if u == nil {
713 http.NotFound(w, r)
714 return
715
716 }
717 honkpage(w, r, u, nil, []*Honk{honk}, "one honk maybe more")
718 return
719 }
720 rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy)
721 if friendorfoe(r.Header.Get("Accept")) {
722 for _, h := range rawhonks {
723 if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
724 honk.Replies = append(honk.Replies, h)
725 }
726 }
727 donksforhonks([]*Honk{honk})
728 _, j := jonkjonk(user, honk)
729 j["@context"] = itiswhatitis
730 w.Header().Set("Content-Type", theonetruename)
731 j.Write(w)
732 return
733 }
734 var honks []*Honk
735 for _, h := range rawhonks {
736 if h.Public && (h.Whofore == 2 || h.IsAcked()) {
737 honks = append(honks, h)
738 }
739 }
740
741 honkpage(w, r, u, nil, honks, "one honk maybe more")
742}
743
744func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
745 honks []*Honk, infomsg template.HTML) {
746 templinfo := getInfo(r)
747 var userid int64 = -1
748 if u != nil {
749 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
750 userid = u.UserID
751 }
752 if u == nil {
753 w.Header().Set("Cache-Control", "max-age=60")
754 }
755 reverbolate(userid, honks)
756 if user != nil {
757 filt := htfilter.New()
758 templinfo["Name"] = user.Name
759 whatabout := user.About
760 whatabout = obfusbreak(user.About)
761 templinfo["WhatAbout"], _ = filt.String(whatabout)
762 }
763 templinfo["Honks"] = honks
764 templinfo["ServerMessage"] = infomsg
765 err := readviews.Execute(w, "honkpage.html", templinfo)
766 if err != nil {
767 log.Print(err)
768 }
769}
770
771func saveuser(w http.ResponseWriter, r *http.Request) {
772 whatabout := r.FormValue("whatabout")
773 u := login.GetUserInfo(r)
774 db := opendatabase()
775 options := ""
776 if r.FormValue("skinny") == "skinny" {
777 options += " skinny "
778 }
779 _, err := db.Exec("update users set about = ?, options = ? where username = ?", whatabout, options, u.Username)
780 if err != nil {
781 log.Printf("error bouting what: %s", err)
782 }
783
784 http.Redirect(w, r, "/account", http.StatusSeeOther)
785}
786
787func gethonkers(userid int64) []*Honker {
788 rows, err := stmtHonkers.Query(userid)
789 if err != nil {
790 log.Printf("error querying honkers: %s", err)
791 return nil
792 }
793 defer rows.Close()
794 var honkers []*Honker
795 for rows.Next() {
796 var f Honker
797 var combos string
798 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
799 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
800 if err != nil {
801 log.Printf("error scanning honker: %s", err)
802 return nil
803 }
804 honkers = append(honkers, &f)
805 }
806 return honkers
807}
808
809func getdubs(userid int64) []*Honker {
810 rows, err := stmtDubbers.Query(userid)
811 if err != nil {
812 log.Printf("error querying dubs: %s", err)
813 return nil
814 }
815 defer rows.Close()
816 var honkers []*Honker
817 for rows.Next() {
818 var f Honker
819 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
820 if err != nil {
821 log.Printf("error scanning honker: %s", err)
822 return nil
823 }
824 honkers = append(honkers, &f)
825 }
826 return honkers
827}
828
829func allusers() []login.UserInfo {
830 var users []login.UserInfo
831 rows, _ := opendatabase().Query("select userid, username from users")
832 defer rows.Close()
833 for rows.Next() {
834 var u login.UserInfo
835 rows.Scan(&u.UserID, &u.Username)
836 users = append(users, u)
837 }
838 return users
839}
840
841func getxonk(userid int64, xid string) *Honk {
842 h := new(Honk)
843 var dt, aud, onts string
844 row := stmtOneXonk.QueryRow(userid, xid)
845 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
846 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
847 if err != nil {
848 if err != sql.ErrNoRows {
849 log.Printf("error scanning xonk: %s", err)
850 }
851 return nil
852 }
853 h.Date, _ = time.Parse(dbtimeformat, dt)
854 h.Audience = strings.Split(aud, " ")
855 h.Public = !keepitquiet(h.Audience)
856 if len(onts) > 0 {
857 h.Onts = strings.Split(onts, " ")
858 }
859 return h
860}
861
862func getbonk(userid int64, xid string) *Honk {
863 h := new(Honk)
864 var dt, aud, onts string
865 row := stmtOneBonk.QueryRow(userid, xid)
866 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
867 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
868 if err != nil {
869 if err != sql.ErrNoRows {
870 log.Printf("error scanning xonk: %s", err)
871 }
872 return nil
873 }
874 h.Date, _ = time.Parse(dbtimeformat, dt)
875 h.Audience = strings.Split(aud, " ")
876 h.Public = !keepitquiet(h.Audience)
877 if len(onts) > 0 {
878 h.Onts = strings.Split(onts, " ")
879 }
880 return h
881}
882
883func getpublichonks() []*Honk {
884 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
885 rows, err := stmtPublicHonks.Query(dt)
886 return getsomehonks(rows, err)
887}
888func gethonksbyuser(name string, includeprivate bool) []*Honk {
889 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
890 whofore := 2
891 if includeprivate {
892 whofore = 3
893 }
894 rows, err := stmtUserHonks.Query(whofore, name, dt)
895 return getsomehonks(rows, err)
896}
897func gethonksforuser(userid int64) []*Honk {
898 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
899 rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
900 return getsomehonks(rows, err)
901}
902func gethonksforme(userid int64) []*Honk {
903 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
904 rows, err := stmtHonksForMe.Query(userid, dt, userid)
905 return getsomehonks(rows, err)
906}
907func gethonksbyhonker(userid int64, honker string) []*Honk {
908 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
909 return getsomehonks(rows, err)
910}
911func gethonksbyxonker(userid int64, xonker string) []*Honk {
912 rows, err := stmtHonksByXonker.Query(userid, xonker, xonker, userid)
913 return getsomehonks(rows, err)
914}
915func gethonksbycombo(userid int64, combo string) []*Honk {
916 combo = "% " + combo + " %"
917 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
918 return getsomehonks(rows, err)
919}
920func gethonksbyconvoy(userid int64, convoy string) []*Honk {
921 rows, err := stmtHonksByConvoy.Query(userid, userid, convoy)
922 honks := getsomehonks(rows, err)
923 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
924 honks[i], honks[j] = honks[j], honks[i]
925 }
926 return honks
927}
928func gethonksbysearch(userid int64, q string) []*Honk {
929 q = "%" + q + "%"
930 rows, err := stmtHonksBySearch.Query(userid, q)
931 honks := getsomehonks(rows, err)
932 return honks
933}
934func gethonksbyontology(userid int64, name string) []*Honk {
935 rows, err := stmtHonksByOntology.Query(name, userid, userid)
936 honks := getsomehonks(rows, err)
937 return honks
938}
939
940func getsomehonks(rows *sql.Rows, err error) []*Honk {
941 if err != nil {
942 log.Printf("error querying honks: %s", err)
943 return nil
944 }
945 defer rows.Close()
946 var honks []*Honk
947 for rows.Next() {
948 var h Honk
949 var dt, aud, onts string
950 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID,
951 &h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
952 if err != nil {
953 log.Printf("error scanning honks: %s", err)
954 return nil
955 }
956 h.Date, _ = time.Parse(dbtimeformat, dt)
957 h.Audience = strings.Split(aud, " ")
958 h.Public = !keepitquiet(h.Audience)
959 if len(onts) > 0 {
960 h.Onts = strings.Split(onts, " ")
961 }
962 honks = append(honks, &h)
963 }
964 rows.Close()
965 donksforhonks(honks)
966 return honks
967}
968
969func donksforhonks(honks []*Honk) {
970 db := opendatabase()
971 var ids []string
972 hmap := make(map[int64]*Honk)
973 for _, h := range honks {
974 ids = append(ids, fmt.Sprintf("%d", h.ID))
975 hmap[h.ID] = h
976 }
977 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, ","))
978 rows, err := db.Query(q)
979 if err != nil {
980 log.Printf("error querying donks: %s", err)
981 return
982 }
983 defer rows.Close()
984 for rows.Next() {
985 var hid int64
986 var d Donk
987 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media, &d.Local)
988 if err != nil {
989 log.Printf("error scanning donk: %s", err)
990 continue
991 }
992 h := hmap[hid]
993 h.Donks = append(h.Donks, &d)
994 }
995}
996
997func savebonk(w http.ResponseWriter, r *http.Request) {
998 xid := r.FormValue("xid")
999 userinfo := login.GetUserInfo(r)
1000 user, _ := butwhatabout(userinfo.Username)
1001
1002 log.Printf("bonking %s", xid)
1003
1004 xonk := getxonk(userinfo.UserID, xid)
1005 if xonk == nil {
1006 return
1007 }
1008 if !xonk.Public {
1009 return
1010 }
1011 donksforhonks([]*Honk{xonk})
1012
1013 _, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
1014 if err != nil {
1015 log.Printf("error acking bonk: %s", err)
1016 }
1017
1018 oonker := xonk.Oonker
1019 if oonker == "" {
1020 oonker = xonk.Honker
1021 }
1022 dt := time.Now().UTC()
1023 bonk := Honk{
1024 UserID: userinfo.UserID,
1025 Username: userinfo.Username,
1026 What: "bonk",
1027 Honker: user.URL,
1028 XID: xonk.XID,
1029 Date: dt,
1030 Donks: xonk.Donks,
1031 Convoy: xonk.Convoy,
1032 Audience: []string{thewholeworld, oonker},
1033 Public: true,
1034 }
1035
1036 aud := strings.Join(bonk.Audience, " ")
1037 whofore := 2
1038 onts := xonk.Onts
1039 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
1040 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
1041 xonk.Precis, oonker, 0, strings.Join(onts, " "))
1042 if err != nil {
1043 log.Printf("error saving bonk: %s", err)
1044 return
1045 }
1046 bonk.ID, _ = res.LastInsertId()
1047 for _, d := range bonk.Donks {
1048 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
1049 if err != nil {
1050 log.Printf("err saving donk: %s", err)
1051 return
1052 }
1053 }
1054 for _, o := range onts {
1055 _, err = stmtSaveOnts.Exec(strings.ToLower(o), bonk.ID)
1056 if err != nil {
1057 log.Printf("error saving ont: %s", err)
1058 }
1059 }
1060
1061 go honkworldwide(user, &bonk)
1062}
1063
1064func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
1065 zonk := Honk{
1066 What: what,
1067 XID: xonk.XID,
1068 Date: time.Now().UTC(),
1069 Audience: oneofakind(xonk.Audience),
1070 }
1071 zonk.Public = !keepitquiet(zonk.Audience)
1072
1073 log.Printf("announcing %sed honk: %s", what, xonk.XID)
1074 go honkworldwide(user, &zonk)
1075}
1076
1077func zonkit(w http.ResponseWriter, r *http.Request) {
1078 wherefore := r.FormValue("wherefore")
1079 what := r.FormValue("what")
1080 userinfo := login.GetUserInfo(r)
1081 user, _ := butwhatabout(userinfo.Username)
1082
1083 if wherefore == "ack" {
1084 xonk := getxonk(userinfo.UserID, what)
1085 if xonk != nil {
1086 _, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1087 if err != nil {
1088 log.Printf("error acking: %s", err)
1089 }
1090 sendzonkofsorts(xonk, user, "ack")
1091 }
1092 return
1093 }
1094
1095 if wherefore == "deack" {
1096 xonk := getxonk(userinfo.UserID, what)
1097 if xonk != nil {
1098 _, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1099 if err != nil {
1100 log.Printf("error deacking: %s", err)
1101 }
1102 sendzonkofsorts(xonk, user, "deack")
1103 }
1104 return
1105 }
1106
1107 if wherefore == "unbonk" {
1108 xonk := getbonk(userinfo.UserID, what)
1109 if xonk != nil {
1110 _, err := stmtZonkDonks.Exec(xonk.ID)
1111 if err != nil {
1112 log.Printf("error zonking: %s", err)
1113 }
1114 _, err = stmtZonkIt.Exec(xonk.ID)
1115 if err != nil {
1116 log.Printf("error zonking: %s", err)
1117 }
1118 xonk = getxonk(userinfo.UserID, what)
1119 _, err = stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1120 if err != nil {
1121 log.Printf("error unbonking: %s", err)
1122 }
1123 sendzonkofsorts(xonk, user, "unbonk")
1124 }
1125 return
1126 }
1127
1128 log.Printf("zonking %s %s", wherefore, what)
1129 if wherefore == "zonk" {
1130 xonk := getxonk(userinfo.UserID, what)
1131 if xonk != nil {
1132 _, err := stmtZonkDonks.Exec(xonk.ID)
1133 if err != nil {
1134 log.Printf("error zonking: %s", err)
1135 }
1136 _, err = stmtZonkIt.Exec(xonk.ID)
1137 if err != nil {
1138 log.Printf("error zonking: %s", err)
1139 }
1140 if xonk.Whofore == 2 || xonk.Whofore == 3 {
1141 sendzonkofsorts(xonk, user, "zonk")
1142 }
1143 }
1144 }
1145 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1146 if err != nil {
1147 log.Printf("error saving zonker: %s", err)
1148 return
1149 }
1150}
1151
1152func savehonk(w http.ResponseWriter, r *http.Request) {
1153 rid := r.FormValue("rid")
1154 noise := r.FormValue("noise")
1155
1156 userinfo := login.GetUserInfo(r)
1157 user, _ := butwhatabout(userinfo.Username)
1158
1159 dt := time.Now().UTC()
1160 xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1161 what := "honk"
1162 if rid != "" {
1163 what = "tonk"
1164 }
1165 honk := Honk{
1166 UserID: userinfo.UserID,
1167 Username: userinfo.Username,
1168 What: "honk",
1169 Honker: user.URL,
1170 XID: xid,
1171 Date: dt,
1172 }
1173 if strings.HasPrefix(noise, "DZ:") {
1174 idx := strings.Index(noise, "\n")
1175 if idx == -1 {
1176 honk.Precis = noise
1177 noise = ""
1178 } else {
1179 honk.Precis = noise[:idx]
1180 noise = noise[idx+1:]
1181 }
1182 }
1183 noise = hooterize(noise)
1184 noise = strings.TrimSpace(noise)
1185 honk.Precis = strings.TrimSpace(honk.Precis)
1186
1187 var convoy string
1188 if rid != "" {
1189 xonk := getxonk(userinfo.UserID, rid)
1190 if xonk != nil {
1191 if xonk.Public {
1192 honk.Audience = append(honk.Audience, xonk.Audience...)
1193 }
1194 convoy = xonk.Convoy
1195 } else {
1196 xonkaud, c := whosthere(rid)
1197 honk.Audience = append(honk.Audience, xonkaud...)
1198 convoy = c
1199 }
1200 for i, a := range honk.Audience {
1201 if a == thewholeworld {
1202 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1203 break
1204 }
1205 }
1206 honk.RID = rid
1207 } else {
1208 honk.Audience = []string{thewholeworld}
1209 }
1210 if noise != "" && noise[0] == '@' {
1211 honk.Audience = append(grapevine(noise), honk.Audience...)
1212 } else {
1213 honk.Audience = append(honk.Audience, grapevine(noise)...)
1214 }
1215 if convoy == "" {
1216 convoy = "data:,electrichonkytonk-" + xfiltrate()
1217 }
1218 butnottooloud(honk.Audience)
1219 honk.Audience = oneofakind(honk.Audience)
1220 if len(honk.Audience) == 0 {
1221 log.Printf("honk to nowhere")
1222 http.Error(w, "honk to nowhere...", http.StatusNotFound)
1223 return
1224 }
1225 honk.Public = !keepitquiet(honk.Audience)
1226 noise = obfusbreak(noise)
1227 honk.Noise = noise
1228 honk.Convoy = convoy
1229
1230 donkxid := r.FormValue("donkxid")
1231 if donkxid == "" {
1232 file, filehdr, err := r.FormFile("donk")
1233 if err == nil {
1234 var buf bytes.Buffer
1235 io.Copy(&buf, file)
1236 file.Close()
1237 data := buf.Bytes()
1238 xid := xfiltrate()
1239 var media, name string
1240 img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
1241 if err == nil {
1242 data = img.Data
1243 format := img.Format
1244 media = "image/" + format
1245 if format == "jpeg" {
1246 format = "jpg"
1247 }
1248 name = xid + "." + format
1249 xid = name
1250 } else {
1251 maxsize := 100000
1252 if len(data) > maxsize {
1253 log.Printf("bad image: %s too much text: %d", err, len(data))
1254 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1255 return
1256 }
1257 for i := 0; i < len(data); i++ {
1258 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1259 log.Printf("bad image: %s not text: %d", err, data[i])
1260 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1261 return
1262 }
1263 }
1264 media = "text/plain"
1265 name = filehdr.Filename
1266 if name == "" {
1267 name = xid + ".txt"
1268 }
1269 xid += ".txt"
1270 }
1271 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1272 res, err := stmtSaveFile.Exec(xid, name, url, media, 1, data)
1273 if err != nil {
1274 log.Printf("unable to save image: %s", err)
1275 return
1276 }
1277 var d Donk
1278 d.FileID, _ = res.LastInsertId()
1279 d.XID = name
1280 d.Name = name
1281 d.Media = media
1282 d.URL = url
1283 d.Local = true
1284 honk.Donks = append(honk.Donks, &d)
1285 donkxid = d.XID
1286 }
1287 } else {
1288 xid := donkxid
1289 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1290 var donk Donk
1291 row := stmtFindFile.QueryRow(url)
1292 err := row.Scan(&donk.FileID)
1293 if err == nil {
1294 donk.XID = xid
1295 donk.Local = true
1296 donk.URL = url
1297 honk.Donks = append(honk.Donks, &donk)
1298 } else {
1299 log.Printf("can't find file: %s", xid)
1300 }
1301 }
1302 herd := herdofemus(honk.Noise)
1303 for _, e := range herd {
1304 donk := savedonk(e.ID, e.Name, "image/png", true)
1305 if donk != nil {
1306 donk.Name = e.Name
1307 honk.Donks = append(honk.Donks, donk)
1308 }
1309 }
1310 memetize(&honk)
1311
1312 aud := strings.Join(honk.Audience, " ")
1313 whofore := 2
1314 if !honk.Public {
1315 whofore = 3
1316 }
1317 if r.FormValue("preview") == "preview" {
1318 honks := []*Honk{&honk}
1319 reverbolate(userinfo.UserID, honks)
1320 templinfo := getInfo(r)
1321 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1322 templinfo["Honks"] = honks
1323 templinfo["InReplyTo"] = r.FormValue("rid")
1324 templinfo["Noise"] = r.FormValue("noise")
1325 templinfo["SavedFile"] = donkxid
1326 templinfo["ServerMessage"] = "honk preview"
1327 err := readviews.Execute(w, "honkpage.html", templinfo)
1328 if err != nil {
1329 log.Print(err)
1330 }
1331 return
1332 }
1333 honk.Onts = oneofakind(ontologies(honk.Noise))
1334 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
1335 dt.Format(dbtimeformat), "", aud, honk.Noise, convoy, whofore, "html",
1336 honk.Precis, honk.Oonker, 0, strings.Join(honk.Onts, " "))
1337 if err != nil {
1338 log.Printf("error saving honk: %s", err)
1339 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1340 return
1341 }
1342 honk.ID, _ = res.LastInsertId()
1343 for _, d := range honk.Donks {
1344 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
1345 if err != nil {
1346 log.Printf("err saving donk: %s", err)
1347 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1348 return
1349 }
1350 }
1351 for _, o := range honk.Onts {
1352 _, err = stmtSaveOnts.Exec(strings.ToLower(o), honk.ID)
1353 if err != nil {
1354 log.Printf("error saving ont: %s", err)
1355 }
1356 }
1357
1358 go honkworldwide(user, &honk)
1359
1360 http.Redirect(w, r, xid, http.StatusSeeOther)
1361}
1362
1363func showhonkers(w http.ResponseWriter, r *http.Request) {
1364 userinfo := login.GetUserInfo(r)
1365 templinfo := getInfo(r)
1366 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1367 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1368 err := readviews.Execute(w, "honkers.html", templinfo)
1369 if err != nil {
1370 log.Print(err)
1371 }
1372}
1373
1374func showcombos(w http.ResponseWriter, r *http.Request) {
1375 userinfo := login.GetUserInfo(r)
1376 templinfo := getInfo(r)
1377 honkers := gethonkers(userinfo.UserID)
1378 var combos []string
1379 for _, h := range honkers {
1380 combos = append(combos, h.Combos...)
1381 }
1382 for i, c := range combos {
1383 if c == "-" {
1384 combos[i] = ""
1385 }
1386 }
1387 combos = oneofakind(combos)
1388 sort.Strings(combos)
1389 templinfo["Combos"] = combos
1390 err := readviews.Execute(w, "combos.html", templinfo)
1391 if err != nil {
1392 log.Print(err)
1393 }
1394}
1395
1396func savehonker(w http.ResponseWriter, r *http.Request) {
1397 u := login.GetUserInfo(r)
1398 name := r.FormValue("name")
1399 url := r.FormValue("url")
1400 peep := r.FormValue("peep")
1401 combos := r.FormValue("combos")
1402 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1403
1404 if honkerid > 0 {
1405 goodbye := r.FormValue("goodbye")
1406 if goodbye == "F" {
1407 db := opendatabase()
1408 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1409 honkerid, u.UserID)
1410 var xid string
1411 err := row.Scan(&xid)
1412 if err != nil {
1413 log.Printf("can't get honker xid: %s", err)
1414 return
1415 }
1416 log.Printf("unsubscribing from %s", xid)
1417 user, _ := butwhatabout(u.Username)
1418 go itakeitallback(user, xid)
1419 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1420 if err != nil {
1421 log.Printf("error updating honker: %s", err)
1422 return
1423 }
1424
1425 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1426 return
1427 }
1428 combos = " " + strings.TrimSpace(combos) + " "
1429 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1430 if err != nil {
1431 log.Printf("update honker err: %s", err)
1432 return
1433 }
1434 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1435 }
1436
1437 flavor := "presub"
1438 if peep == "peep" {
1439 flavor = "peep"
1440 }
1441 p, err := investigate(url)
1442 if err != nil {
1443 http.Error(w, "error investigating: "+err.Error(), http.StatusInternalServerError)
1444 log.Printf("failed to investigate honker")
1445 return
1446 }
1447 url = p.XID
1448 if name == "" {
1449 name = p.Handle
1450 }
1451 _, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1452 if err != nil {
1453 log.Print(err)
1454 return
1455 }
1456 if flavor == "presub" {
1457 user, _ := butwhatabout(u.Username)
1458 go subsub(user, url)
1459 }
1460 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1461}
1462
1463type Zonker struct {
1464 ID int64
1465 Name string
1466 Wherefore string
1467}
1468
1469func zonkzone(w http.ResponseWriter, r *http.Request) {
1470 userinfo := login.GetUserInfo(r)
1471 rows, err := stmtGetZonkers.Query(userinfo.UserID)
1472 if err != nil {
1473 log.Printf("err: %s", err)
1474 return
1475 }
1476 defer rows.Close()
1477 var zonkers []Zonker
1478 for rows.Next() {
1479 var z Zonker
1480 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1481 zonkers = append(zonkers, z)
1482 }
1483 sort.Slice(zonkers, func(i, j int) bool {
1484 w1 := zonkers[i].Wherefore
1485 w2 := zonkers[j].Wherefore
1486 if w1 == w2 {
1487 return zonkers[i].Name < zonkers[j].Name
1488 }
1489 if w1 == "zonvoy" {
1490 w1 = "zzzzzzz"
1491 }
1492 if w2 == "zonvoy" {
1493 w2 = "zzzzzzz"
1494 }
1495 return w1 < w2
1496 })
1497
1498 templinfo := getInfo(r)
1499 templinfo["Zonkers"] = zonkers
1500 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1501 err = readviews.Execute(w, "zonkers.html", templinfo)
1502 if err != nil {
1503 log.Print(err)
1504 }
1505}
1506
1507func zonkzonk(w http.ResponseWriter, r *http.Request) {
1508 userinfo := login.GetUserInfo(r)
1509 itsok := r.FormValue("itsok")
1510 if itsok == "iforgiveyou" {
1511 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1512 db := opendatabase()
1513 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1514 userinfo.UserID, zonkerid)
1515 bitethethumbs()
1516 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1517 return
1518 }
1519 wherefore := r.FormValue("wherefore")
1520 name := r.FormValue("name")
1521 if name == "" {
1522 return
1523 }
1524 switch wherefore {
1525 case "zonker":
1526 case "zomain":
1527 case "zonvoy":
1528 case "zord":
1529 case "zilence":
1530 default:
1531 return
1532 }
1533 db := opendatabase()
1534 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1535 userinfo.UserID, name, wherefore)
1536 if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" || wherefore == "zilence" {
1537 bitethethumbs()
1538 }
1539
1540 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1541}
1542
1543func accountpage(w http.ResponseWriter, r *http.Request) {
1544 u := login.GetUserInfo(r)
1545 user, _ := butwhatabout(u.Username)
1546 templinfo := getInfo(r)
1547 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1548 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1549 templinfo["User"] = user
1550 err := readviews.Execute(w, "account.html", templinfo)
1551 if err != nil {
1552 log.Print(err)
1553 }
1554}
1555
1556func dochpass(w http.ResponseWriter, r *http.Request) {
1557 err := login.ChangePassword(w, r)
1558 if err != nil {
1559 log.Printf("error changing password: %s", err)
1560 }
1561 http.Redirect(w, r, "/account", http.StatusSeeOther)
1562}
1563
1564func fingerlicker(w http.ResponseWriter, r *http.Request) {
1565 orig := r.FormValue("resource")
1566
1567 log.Printf("finger lick: %s", orig)
1568
1569 if strings.HasPrefix(orig, "acct:") {
1570 orig = orig[5:]
1571 }
1572
1573 name := orig
1574 idx := strings.LastIndexByte(name, '/')
1575 if idx != -1 {
1576 name = name[idx+1:]
1577 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1578 log.Printf("foreign request rejected")
1579 name = ""
1580 }
1581 } else {
1582 idx = strings.IndexByte(name, '@')
1583 if idx != -1 {
1584 name = name[:idx]
1585 if name+"@"+serverName != orig {
1586 log.Printf("foreign request rejected")
1587 name = ""
1588 }
1589 }
1590 }
1591 user, err := butwhatabout(name)
1592 if err != nil {
1593 http.NotFound(w, r)
1594 return
1595 }
1596
1597 j := junk.New()
1598 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1599 j["aliases"] = []string{user.URL}
1600 var links []junk.Junk
1601 l := junk.New()
1602 l["rel"] = "self"
1603 l["type"] = `application/activity+json`
1604 l["href"] = user.URL
1605 links = append(links, l)
1606 j["links"] = links
1607
1608 w.Header().Set("Cache-Control", "max-age=3600")
1609 w.Header().Set("Content-Type", "application/jrd+json")
1610 j.Write(w)
1611}
1612
1613func somedays() string {
1614 secs := 432000 + notrand.Int63n(432000)
1615 return fmt.Sprintf("%d", secs)
1616}
1617
1618func avatate(w http.ResponseWriter, r *http.Request) {
1619 n := r.FormValue("a")
1620 a := avatar(n)
1621 w.Header().Set("Cache-Control", "max-age="+somedays())
1622 w.Write(a)
1623}
1624
1625func servecss(w http.ResponseWriter, r *http.Request) {
1626 w.Header().Set("Cache-Control", "max-age=7776000")
1627 http.ServeFile(w, r, "views"+r.URL.Path)
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}