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