web.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/template"
23 "io"
24 notrand "math/rand"
25 "mime/multipart"
26 "net/http"
27 "net/url"
28 "os"
29 "os/signal"
30 "path"
31 "regexp"
32 "sort"
33 "strconv"
34 "strings"
35 "syscall"
36 "time"
37 "unicode/utf8"
38
39 "github.com/gorilla/handlers"
40 "github.com/gorilla/mux"
41 "humungus.tedunangst.com/r/webs/cache"
42 "humungus.tedunangst.com/r/webs/gencache"
43 "humungus.tedunangst.com/r/webs/httpsig"
44 "humungus.tedunangst.com/r/webs/junk"
45 "humungus.tedunangst.com/r/webs/login"
46 "humungus.tedunangst.com/r/webs/rss"
47 "humungus.tedunangst.com/r/webs/templates"
48)
49
50var readviews *templates.Template
51
52var userSep = "u"
53var honkSep = "h"
54
55var develMode = false
56
57var allemus []Emu
58
59func getuserstyle(u *login.UserInfo) template.HTMLAttr {
60 if u == nil {
61 return ""
62 }
63 user, _ := butwhatabout(u.Username)
64 class := template.HTMLAttr("")
65 if user.Options.SkinnyCSS {
66 class += `class="skinny"`
67 }
68 return class
69}
70
71func getmaplink(u *login.UserInfo) string {
72 if u == nil {
73 return "osm"
74 }
75 user, _ := butwhatabout(u.Username)
76 ml := user.Options.MapLink
77 if ml == "" {
78 ml = "osm"
79 }
80 return ml
81}
82
83func getInfo(r *http.Request) map[string]interface{} {
84 templinfo := make(map[string]interface{})
85 templinfo["StyleParam"] = getassetparam(viewDir + "/views/style.css")
86 templinfo["LocalStyleParam"] = getassetparam(dataDir + "/views/local.css")
87 templinfo["JSParam"] = getassetparam(viewDir + "/views/honkpage.js")
88 templinfo["MiscJSParam"] = getassetparam(viewDir + "/views/misc.js")
89 templinfo["LocalJSParam"] = getassetparam(dataDir + "/views/local.js")
90 templinfo["ServerName"] = serverName
91 templinfo["IconName"] = iconName
92 templinfo["UserSep"] = userSep
93 if r == nil {
94 return templinfo
95 }
96 if u := login.GetUserInfo(r); u != nil {
97 templinfo["UserInfo"], _ = butwhatabout(u.Username)
98 templinfo["UserStyle"] = getuserstyle(u)
99 var combos []string
100 combocache.Get(u.UserID, &combos)
101 templinfo["Combos"] = combos
102 }
103 return templinfo
104}
105
106var oldnews = gencache.New(gencache.Options[string, []byte]{
107 Fill: func(url string) ([]byte, bool) {
108 templinfo := getInfo(nil)
109 var honks []*Honk
110 var userid int64 = -1
111
112 templinfo["ServerMessage"] = serverMsg
113 switch url {
114 case "/events":
115 honks = geteventhonks(userid)
116 templinfo["ServerMessage"] = "some recent and upcoming events"
117 default:
118 templinfo["ShowRSS"] = true
119 honks = getpublichonks()
120 }
121 reverbolate(userid, honks)
122 templinfo["Honks"] = honks
123 templinfo["MapLink"] = getmaplink(nil)
124 var buf bytes.Buffer
125 err := readviews.Execute(&buf, "honkpage.html", templinfo)
126 if err != nil {
127 elog.Print(err)
128 }
129 return buf.Bytes(), true
130
131 },
132 Duration: 1 * time.Minute,
133})
134
135func lonelypage(w http.ResponseWriter, r *http.Request) {
136 page, _ := oldnews.Get(r.URL.Path)
137 if !develMode {
138 w.Header().Set("Cache-Control", "max-age=60")
139 }
140 w.Write(page)
141}
142
143func homepage(w http.ResponseWriter, r *http.Request) {
144 u := login.GetUserInfo(r)
145 if u == nil {
146 lonelypage(w, r)
147 return
148 }
149 templinfo := getInfo(r)
150 var honks []*Honk
151 var userid int64 = -1
152
153 templinfo["ServerMessage"] = serverMsg
154 if u == nil || r.URL.Path == "/front" {
155 switch r.URL.Path {
156 case "/events":
157 honks = geteventhonks(userid)
158 templinfo["ServerMessage"] = "some recent and upcoming events"
159 default:
160 templinfo["ShowRSS"] = true
161 honks = getpublichonks()
162 }
163 } else {
164 userid = u.UserID
165 switch r.URL.Path {
166 case "/atme":
167 templinfo["ServerMessage"] = "at me!"
168 templinfo["PageName"] = "atme"
169 honks = gethonksforme(userid, 0)
170 honks = osmosis(honks, userid, false)
171 menewnone(userid)
172 templinfo["UserInfo"], _ = butwhatabout(u.Username)
173 case "/longago":
174 templinfo["ServerMessage"] = "long ago and far away!"
175 templinfo["PageName"] = "longago"
176 honks = gethonksfromlongago(userid, 0)
177 honks = osmosis(honks, userid, false)
178 case "/events":
179 templinfo["ServerMessage"] = "some recent and upcoming events"
180 templinfo["PageName"] = "events"
181 honks = geteventhonks(userid)
182 honks = osmosis(honks, userid, true)
183 case "/first":
184 templinfo["PageName"] = "first"
185 honks = gethonksforuserfirstclass(userid, 0)
186 honks = osmosis(honks, userid, true)
187 case "/saved":
188 templinfo["ServerMessage"] = "saved honks"
189 templinfo["PageName"] = "saved"
190 honks = getsavedhonks(userid, 0)
191 default:
192 templinfo["PageName"] = "home"
193 honks = gethonksforuser(userid, 0)
194 honks = osmosis(honks, userid, true)
195 }
196 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
197 }
198
199 honkpage(w, u, honks, templinfo)
200}
201
202func showemus(w http.ResponseWriter, r *http.Request) {
203 templinfo := getInfo(r)
204 templinfo["Emus"] = allemus
205 err := readviews.Execute(w, "emus.html", templinfo)
206 if err != nil {
207 elog.Print(err)
208 }
209}
210
211func showfunzone(w http.ResponseWriter, r *http.Request) {
212 var emunames, memenames []string
213 emuext := make(map[string]string)
214 dir, err := os.Open(dataDir + "/emus")
215 if err == nil {
216 emunames, _ = dir.Readdirnames(0)
217 dir.Close()
218 }
219 for i, e := range emunames {
220 if len(e) > 4 {
221 emunames[i] = e[:len(e)-4]
222 emuext[emunames[i]] = e[len(e)-4:]
223 }
224 }
225 dir, err = os.Open(dataDir + "/memes")
226 if err == nil {
227 memenames, _ = dir.Readdirnames(0)
228 dir.Close()
229 }
230 sort.Strings(emunames)
231 sort.Strings(memenames)
232 templinfo := getInfo(r)
233 templinfo["Emus"] = emunames
234 templinfo["Emuext"] = emuext
235 templinfo["Memes"] = memenames
236 err = readviews.Execute(w, "funzone.html", templinfo)
237 if err != nil {
238 elog.Print(err)
239 }
240}
241
242func showrss(w http.ResponseWriter, r *http.Request) {
243 name := mux.Vars(r)["name"]
244
245 var honks []*Honk
246 if name != "" {
247 honks = gethonksbyuser(name, false, 0)
248 } else {
249 honks = getpublichonks()
250 }
251 reverbolate(-1, honks)
252
253 home := fmt.Sprintf("https://%s/", serverName)
254 base := home
255 if name != "" {
256 home += "u/" + name
257 name += " "
258 }
259 feed := rss.Feed{
260 Title: name + "honk",
261 Link: home,
262 Description: name + "honk rss",
263 Image: &rss.Image{
264 URL: base + "icon.png",
265 Title: name + "honk rss",
266 Link: home,
267 },
268 }
269 var modtime time.Time
270 for _, honk := range honks {
271 if !firstclass(honk) {
272 continue
273 }
274 desc := string(honk.HTML)
275 if t := honk.Time; t != nil {
276 desc += fmt.Sprintf(`<p>Time: %s`, t.StartTime.Local().Format("03:04PM EDT Mon Jan 02"))
277 if t.Duration != 0 {
278 desc += fmt.Sprintf(`<br>Duration: %s`, t.Duration)
279 }
280 }
281 if p := honk.Place; p != nil {
282 desc += string(templates.Sprintf(`<p>Location: <a href="%s">%s</a> %f %f`,
283 p.Url, p.Name, p.Latitude, p.Longitude))
284 }
285 for _, d := range honk.Donks {
286 desc += string(templates.Sprintf(`<p><a href="%s">Attachment: %s</a>`,
287 d.URL, d.Desc))
288 if strings.HasPrefix(d.Media, "image") {
289 desc += string(templates.Sprintf(`<img src="%s">`, d.URL))
290 }
291 }
292
293 feed.Items = append(feed.Items, &rss.Item{
294 Title: fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
295 Description: rss.CData{Data: desc},
296 Link: honk.URL,
297 PubDate: honk.Date.Format(time.RFC1123),
298 Guid: &rss.Guid{IsPermaLink: true, Value: honk.URL},
299 })
300 if honk.Date.After(modtime) {
301 modtime = honk.Date
302 }
303 }
304 if !develMode {
305 w.Header().Set("Cache-Control", "max-age=300")
306 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
307 }
308
309 err := feed.Write(w)
310 if err != nil {
311 elog.Printf("error writing rss: %s", err)
312 }
313}
314
315func crappola(j junk.Junk) bool {
316 t, _ := j.GetString("type")
317 a, _ := j.GetString("actor")
318 o, _ := j.GetString("object")
319 if t == "Delete" && a == o {
320 dlog.Printf("crappola from %s", a)
321 return true
322 }
323 return false
324}
325
326func ping(user *WhatAbout, who string) {
327 if targ := fullname(who, user.ID); targ != "" {
328 who = targ
329 }
330 if !strings.HasPrefix(who, "https:") {
331 who = gofish(who)
332 }
333 if who == "" {
334 ilog.Printf("nobody to ping!")
335 return
336 }
337 var box *Box
338 ok := boxofboxes.Get(who, &box)
339 if !ok {
340 ilog.Printf("no inbox to ping %s", who)
341 return
342 }
343 ilog.Printf("sending ping to %s", box.In)
344 j := junk.New()
345 j["@context"] = itiswhatitis
346 j["type"] = "Ping"
347 j["id"] = user.URL + "/ping/" + xfiltrate()
348 j["actor"] = user.URL
349 j["to"] = who
350 ki := ziggy(user.ID)
351 if ki == nil {
352 return
353 }
354 err := PostJunk(ki.keyname, ki.seckey, box.In, j)
355 if err != nil {
356 elog.Printf("can't send ping: %s", err)
357 return
358 }
359 ilog.Printf("sent ping to %s: %s", who, j["id"])
360}
361
362func pong(user *WhatAbout, who string, obj string) {
363 var box *Box
364 ok := boxofboxes.Get(who, &box)
365 if !ok {
366 ilog.Printf("no inbox to pong %s", who)
367 return
368 }
369 j := junk.New()
370 j["@context"] = itiswhatitis
371 j["type"] = "Pong"
372 j["id"] = user.URL + "/pong/" + xfiltrate()
373 j["actor"] = user.URL
374 j["to"] = who
375 j["object"] = obj
376 ki := ziggy(user.ID)
377 if ki == nil {
378 return
379 }
380 err := PostJunk(ki.keyname, ki.seckey, box.In, j)
381 if err != nil {
382 elog.Printf("can't send pong: %s", err)
383 return
384 }
385}
386
387func inbox(w http.ResponseWriter, r *http.Request) {
388 name := mux.Vars(r)["name"]
389 user, err := butwhatabout(name)
390 if err != nil {
391 http.NotFound(w, r)
392 return
393 }
394 if stealthmode(user.ID, r) {
395 http.NotFound(w, r)
396 return
397 }
398 var buf bytes.Buffer
399 limiter := io.LimitReader(r.Body, 1*1024*1024)
400 io.Copy(&buf, limiter)
401 payload := buf.Bytes()
402 j, err := junk.FromBytes(payload)
403 if err != nil {
404 ilog.Printf("bad payload: %s", err)
405 ilog.Writer().Write(payload)
406 ilog.Writer().Write([]byte{'\n'})
407 return
408 }
409
410 if crappola(j) {
411 return
412 }
413 what, _ := j.GetString("type")
414 obj, _ := j.GetString("object")
415 if what == "Like" || what == "Dislike" || (what == "EmojiReact" && originate(obj) != serverName) {
416 return
417 }
418 who, _ := j.GetString("actor")
419 if rejectactor(user.ID, who) {
420 return
421 }
422
423 keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
424 if err != nil && keyname != "" {
425 savingthrow(keyname)
426 keyname, err = httpsig.VerifyRequest(r, payload, zaggy)
427 }
428 if err != nil {
429 ilog.Printf("inbox message failed signature for %s from %s: %s", keyname, r.Header.Get("X-Forwarded-For"), err)
430 if keyname != "" {
431 ilog.Printf("bad signature from %s", keyname)
432 }
433 http.Error(w, "what did you call me?", http.StatusTeapot)
434 return
435 }
436 origin := keymatch(keyname, who)
437 if origin == "" {
438 ilog.Printf("keyname actor mismatch: %s <> %s", keyname, who)
439 return
440 }
441
442 switch what {
443 case "Ping":
444 id, _ := j.GetString("id")
445 ilog.Printf("ping from %s: %s", who, id)
446 pong(user, who, id)
447 case "Pong":
448 ilog.Printf("pong from %s: %s", who, obj)
449 case "Follow":
450 if obj != user.URL {
451 ilog.Printf("can't follow %s", obj)
452 return
453 }
454 followme(user, who, who, j)
455 case "Accept":
456 followyou2(user, j)
457 case "Reject":
458 nofollowyou2(user, j)
459 case "Update":
460 obj, ok := j.GetMap("object")
461 if ok {
462 what, _ := obj.GetString("type")
463 switch what {
464 case "Service":
465 fallthrough
466 case "Person":
467 return
468 case "Question":
469 return
470 }
471 }
472 go xonksaver(user, j, origin)
473 case "Undo":
474 obj, ok := j.GetMap("object")
475 if !ok {
476 folxid, ok := j.GetString("object")
477 if ok && originate(folxid) == origin {
478 unfollowme(user, "", "", j)
479 }
480 return
481 }
482 what, _ := obj.GetString("type")
483 switch what {
484 case "Follow":
485 unfollowme(user, who, who, j)
486 case "Announce":
487 xid, _ := obj.GetString("object")
488 dlog.Printf("undo announce: %s", xid)
489 case "Like":
490 default:
491 ilog.Printf("unknown undo: %s", what)
492 }
493 case "EmojiReact":
494 obj, ok := j.GetString("object")
495 if ok {
496 content, _ := j.GetString("content")
497 addreaction(user, obj, who, content)
498 }
499 default:
500 go saveandcheck(user, j, origin)
501 }
502}
503
504func saveandcheck(user *WhatAbout, j junk.Junk, origin string) {
505 xonk := xonksaver(user, j, origin)
506 if xonk == nil {
507 return
508 }
509 if sname := shortname(user.ID, xonk.Honker); sname == "" {
510 dlog.Printf("received unexpected activity from %s", xonk.Honker)
511 if xonk.Whofore == 0 {
512 dlog.Printf("it's not even for me!")
513 }
514 }
515}
516
517func serverinbox(w http.ResponseWriter, r *http.Request) {
518 user := getserveruser()
519 if stealthmode(user.ID, r) {
520 http.NotFound(w, r)
521 return
522 }
523 var buf bytes.Buffer
524 io.Copy(&buf, r.Body)
525 payload := buf.Bytes()
526 j, err := junk.FromBytes(payload)
527 if err != nil {
528 ilog.Printf("bad payload: %s", err)
529 ilog.Writer().Write(payload)
530 ilog.Writer().Write([]byte{'\n'})
531 return
532 }
533 if crappola(j) {
534 return
535 }
536 keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
537 if err != nil && keyname != "" {
538 savingthrow(keyname)
539 keyname, err = httpsig.VerifyRequest(r, payload, zaggy)
540 }
541 if err != nil {
542 ilog.Printf("inbox message failed signature for %s from %s: %s", keyname, r.Header.Get("X-Forwarded-For"), err)
543 if keyname != "" {
544 ilog.Printf("bad signature from %s", keyname)
545 }
546 http.Error(w, "what did you call me?", http.StatusTeapot)
547 return
548 }
549 who, _ := j.GetString("actor")
550 origin := keymatch(keyname, who)
551 if origin == "" {
552 ilog.Printf("keyname actor mismatch: %s <> %s", keyname, who)
553 return
554 }
555 if rejectactor(user.ID, who) {
556 return
557 }
558 re_ont := regexp.MustCompile("https://" + serverName + "/o/([\\pL[:digit:]]+)")
559 what, _ := j.GetString("type")
560 dlog.Printf("server got a %s", what)
561 switch what {
562 case "Follow":
563 obj, _ := j.GetString("object")
564 if obj == user.URL {
565 ilog.Printf("can't follow the server!")
566 return
567 }
568 m := re_ont.FindStringSubmatch(obj)
569 if len(m) != 2 {
570 ilog.Printf("not sure how to handle this")
571 return
572 }
573 ont := "#" + m[1]
574
575 followme(user, who, ont, j)
576 case "Undo":
577 obj, ok := j.GetMap("object")
578 if !ok {
579 ilog.Printf("unknown undo no object")
580 return
581 }
582 what, _ := obj.GetString("type")
583 if what != "Follow" {
584 ilog.Printf("unknown undo: %s", what)
585 return
586 }
587 targ, _ := obj.GetString("object")
588 m := re_ont.FindStringSubmatch(targ)
589 if len(m) != 2 {
590 ilog.Printf("not sure how to handle this")
591 return
592 }
593 ont := "#" + m[1]
594 unfollowme(user, who, ont, j)
595 default:
596 ilog.Printf("unhandled server activity: %s", what)
597 dumpactivity(j)
598 }
599}
600
601func serveractor(w http.ResponseWriter, r *http.Request) {
602 user := getserveruser()
603 if stealthmode(user.ID, r) {
604 http.NotFound(w, r)
605 return
606 }
607 j := junkuser(user)
608 j.Write(w)
609}
610
611func ximport(w http.ResponseWriter, r *http.Request) {
612 u := login.GetUserInfo(r)
613 xid := strings.TrimSpace(r.FormValue("q"))
614 xonk := getxonk(u.UserID, xid)
615 if xonk == nil {
616 p, _ := investigate(xid)
617 if p != nil {
618 xid = p.XID
619 }
620 j, err := GetJunk(u.UserID, xid)
621 if err != nil {
622 http.Error(w, "error getting external object", http.StatusInternalServerError)
623 ilog.Printf("error getting external object: %s", err)
624 return
625 }
626 allinjest(originate(xid), j)
627 dlog.Printf("importing %s", xid)
628 user, _ := butwhatabout(u.Username)
629
630 info, _ := somethingabout(j)
631 if info == nil {
632 xonk = xonksaver(user, j, originate(xid))
633 } else if info.What == SomeActor {
634 outbox, _ := j.GetString("outbox")
635 gimmexonks(user, outbox)
636 http.Redirect(w, r, "/h?xid="+url.QueryEscape(xid), http.StatusSeeOther)
637 return
638 } else if info.What == SomeCollection {
639 gimmexonks(user, xid)
640 http.Redirect(w, r, "/xzone", http.StatusSeeOther)
641 return
642 }
643 }
644 convoy := ""
645 if xonk != nil {
646 convoy = xonk.Convoy
647 }
648 http.Redirect(w, r, "/t?c="+url.QueryEscape(convoy), http.StatusSeeOther)
649}
650
651func xzone(w http.ResponseWriter, r *http.Request) {
652 u := login.GetUserInfo(r)
653 rows, err := stmtRecentHonkers.Query(u.UserID, u.UserID)
654 if err != nil {
655 elog.Printf("query err: %s", err)
656 return
657 }
658 defer rows.Close()
659 var honkers []Honker
660 for rows.Next() {
661 var xid string
662 rows.Scan(&xid)
663 honkers = append(honkers, Honker{XID: xid})
664 }
665 rows.Close()
666 for i := range honkers {
667 _, honkers[i].Handle = handles(honkers[i].XID)
668 }
669 templinfo := getInfo(r)
670 templinfo["XCSRF"] = login.GetCSRF("ximport", r)
671 templinfo["Honkers"] = honkers
672 err = readviews.Execute(w, "xzone.html", templinfo)
673 if err != nil {
674 elog.Print(err)
675 }
676}
677
678var oldoutbox = cache.New(cache.Options{Filler: func(name string) ([]byte, bool) {
679 user, err := butwhatabout(name)
680 if err != nil {
681 return nil, false
682 }
683 honks := gethonksbyuser(name, false, 0)
684 if len(honks) > 20 {
685 honks = honks[0:20]
686 }
687
688 var jonks []junk.Junk
689 for _, h := range honks {
690 j, _ := jonkjonk(user, h)
691 jonks = append(jonks, j)
692 }
693
694 j := junk.New()
695 j["@context"] = itiswhatitis
696 j["id"] = user.URL + "/outbox"
697 j["attributedTo"] = user.URL
698 j["type"] = "OrderedCollection"
699 j["totalItems"] = len(jonks)
700 j["orderedItems"] = jonks
701
702 return j.ToBytes(), true
703}, Duration: 1 * time.Minute})
704
705func outbox(w http.ResponseWriter, r *http.Request) {
706 name := mux.Vars(r)["name"]
707 user, err := butwhatabout(name)
708 if err != nil {
709 http.NotFound(w, r)
710 return
711 }
712 if stealthmode(user.ID, r) {
713 http.NotFound(w, r)
714 return
715 }
716 var j []byte
717 ok := oldoutbox.Get(name, &j)
718 if ok {
719 w.Header().Set("Content-Type", theonetruename)
720 w.Write(j)
721 } else {
722 http.NotFound(w, r)
723 }
724}
725
726var oldempties = cache.New(cache.Options{Filler: func(url string) ([]byte, bool) {
727 colname := "/followers"
728 if strings.HasSuffix(url, "/following") {
729 colname = "/following"
730 }
731 user := fmt.Sprintf("https://%s%s", serverName, url[:len(url)-10])
732 j := junk.New()
733 j["@context"] = itiswhatitis
734 j["id"] = user + colname
735 j["attributedTo"] = user
736 j["type"] = "OrderedCollection"
737 j["totalItems"] = 0
738 j["orderedItems"] = []junk.Junk{}
739
740 return j.ToBytes(), true
741}})
742
743func emptiness(w http.ResponseWriter, r *http.Request) {
744 name := mux.Vars(r)["name"]
745 user, err := butwhatabout(name)
746 if err != nil {
747 http.NotFound(w, r)
748 return
749 }
750 if stealthmode(user.ID, r) {
751 http.NotFound(w, r)
752 return
753 }
754 var j []byte
755 ok := oldempties.Get(r.URL.Path, &j)
756 if ok {
757 w.Header().Set("Content-Type", theonetruename)
758 w.Write(j)
759 } else {
760 http.NotFound(w, r)
761 }
762}
763
764func showuser(w http.ResponseWriter, r *http.Request) {
765 name := mux.Vars(r)["name"]
766 user, err := butwhatabout(name)
767 if err != nil {
768 ilog.Printf("user not found %s: %s", name, err)
769 http.NotFound(w, r)
770 return
771 }
772 if stealthmode(user.ID, r) {
773 http.NotFound(w, r)
774 return
775 }
776 if friendorfoe(r.Header.Get("Accept")) {
777 j, ok := asjonker(name)
778 if ok {
779 w.Header().Set("Content-Type", theonetruename)
780 w.Write(j)
781 } else {
782 http.NotFound(w, r)
783 }
784 return
785 }
786 u := login.GetUserInfo(r)
787 if u != nil && u.Username != name {
788 u = nil
789 }
790 honks := gethonksbyuser(name, u != nil, 0)
791 templinfo := getInfo(r)
792 templinfo["PageName"] = "user"
793 templinfo["PageArg"] = name
794 templinfo["Name"] = user.Name
795 templinfo["WhatAbout"] = user.HTAbout
796 templinfo["ServerMessage"] = ""
797 templinfo["APAltLink"] = templates.Sprintf("<link href='%s' rel='alternate' type='application/activity+json'>", user.URL)
798 if u != nil {
799 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
800 }
801 honkpage(w, u, honks, templinfo)
802}
803
804func showhonker(w http.ResponseWriter, r *http.Request) {
805 u := login.GetUserInfo(r)
806 name := mux.Vars(r)["name"]
807 var honks []*Honk
808 if name == "" {
809 name = r.FormValue("xid")
810 honks = gethonksbyxonker(u.UserID, name, 0)
811 } else {
812 honks = gethonksbyhonker(u.UserID, name, 0)
813 }
814 miniform := templates.Sprintf(`<form action="/submithonker" method="POST">
815<input type="hidden" name="CSRF" value="%s">
816<input type="hidden" name="url" value="%s">
817<button tabindex=1 name="add honker" value="add honker">add honker</button>
818</form>`, login.GetCSRF("submithonker", r), name)
819 msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>%s`, name, name, miniform)
820 templinfo := getInfo(r)
821 templinfo["PageName"] = "honker"
822 templinfo["PageArg"] = name
823 templinfo["ServerMessage"] = msg
824 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
825 honkpage(w, u, honks, templinfo)
826}
827
828func showcombo(w http.ResponseWriter, r *http.Request) {
829 name := mux.Vars(r)["name"]
830 u := login.GetUserInfo(r)
831 honks := gethonksbycombo(u.UserID, name, 0)
832 honks = osmosis(honks, u.UserID, true)
833 templinfo := getInfo(r)
834 templinfo["PageName"] = "combo"
835 templinfo["PageArg"] = name
836 templinfo["ServerMessage"] = "honks by combo: " + name
837 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
838 honkpage(w, u, honks, templinfo)
839}
840func showconvoy(w http.ResponseWriter, r *http.Request) {
841 c := r.FormValue("c")
842 u := login.GetUserInfo(r)
843 honks := gethonksbyconvoy(u.UserID, c, 0)
844 templinfo := getInfo(r)
845 if len(honks) > 0 {
846 templinfo["TopHID"] = honks[0].ID
847 }
848 honks = osmosis(honks, u.UserID, false)
849 //reversehonks(honks)
850 honks = threadsort(honks)
851 templinfo["PageName"] = "convoy"
852 templinfo["PageArg"] = c
853 templinfo["ServerMessage"] = "honks in convoy: " + c
854 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
855 honkpage(w, u, honks, templinfo)
856}
857func showsearch(w http.ResponseWriter, r *http.Request) {
858 q := r.FormValue("q")
859 if strings.HasPrefix(q, "https://") {
860 ximport(w, r)
861 return
862 }
863 u := login.GetUserInfo(r)
864 honks := gethonksbysearch(u.UserID, q, 0)
865 templinfo := getInfo(r)
866 templinfo["PageName"] = "search"
867 templinfo["PageArg"] = q
868 templinfo["ServerMessage"] = "honks for search: " + q
869 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
870 honkpage(w, u, honks, templinfo)
871}
872func showontology(w http.ResponseWriter, r *http.Request) {
873 name := mux.Vars(r)["name"]
874 u := login.GetUserInfo(r)
875 var userid int64 = -1
876 if u != nil {
877 userid = u.UserID
878 }
879 honks := gethonksbyontology(userid, "#"+name, 0)
880 if friendorfoe(r.Header.Get("Accept")) {
881 if len(honks) > 40 {
882 honks = honks[0:40]
883 }
884
885 var xids []string
886 for _, h := range honks {
887 xids = append(xids, h.XID)
888 }
889
890 user := getserveruser()
891
892 j := junk.New()
893 j["@context"] = itiswhatitis
894 j["id"] = fmt.Sprintf("https://%s/o/%s", serverName, name)
895 j["name"] = "#" + name
896 j["attributedTo"] = user.URL
897 j["type"] = "OrderedCollection"
898 j["totalItems"] = len(xids)
899 j["orderedItems"] = xids
900
901 j.Write(w)
902 return
903 }
904
905 templinfo := getInfo(r)
906 templinfo["ServerMessage"] = "honks by ontology: " + name
907 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
908 honkpage(w, u, honks, templinfo)
909}
910
911type Ont struct {
912 Name string
913 Count int64
914}
915
916func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
917 u := login.GetUserInfo(r)
918 var userid int64 = -1
919 if u != nil {
920 userid = u.UserID
921 }
922 rows, err := stmtAllOnts.Query(userid)
923 if err != nil {
924 elog.Printf("selection error: %s", err)
925 return
926 }
927 defer rows.Close()
928 var onts []Ont
929 for rows.Next() {
930 var o Ont
931 err := rows.Scan(&o.Name, &o.Count)
932 if err != nil {
933 elog.Printf("error scanning ont: %s", err)
934 continue
935 }
936 if utf8.RuneCountInString(o.Name) > 24 {
937 continue
938 }
939 o.Name = o.Name[1:]
940 onts = append(onts, o)
941 }
942 sort.Slice(onts, func(i, j int) bool {
943 return onts[i].Name < onts[j].Name
944 })
945 if u == nil && !develMode {
946 w.Header().Set("Cache-Control", "max-age=300")
947 }
948 templinfo := getInfo(r)
949 templinfo["Onts"] = onts
950 templinfo["FirstRune"] = func(s string) rune { r, _ := utf8.DecodeRuneInString(s); return r }
951 err = readviews.Execute(w, "onts.html", templinfo)
952 if err != nil {
953 elog.Print(err)
954 }
955}
956
957type Track struct {
958 xid string
959 who string
960}
961
962func getbacktracks(xid string) []string {
963 c := make(chan bool)
964 dumptracks <- c
965 <-c
966 row := stmtGetTracks.QueryRow(xid)
967 var rawtracks string
968 err := row.Scan(&rawtracks)
969 if err != nil {
970 if err != sql.ErrNoRows {
971 elog.Printf("error scanning tracks: %s", err)
972 }
973 return nil
974 }
975 var rcpts []string
976 for _, f := range strings.Split(rawtracks, " ") {
977 idx := strings.LastIndexByte(f, '#')
978 if idx != -1 {
979 f = f[:idx]
980 }
981 if !strings.HasPrefix(f, "https://") {
982 f = fmt.Sprintf("%%https://%s/inbox", f)
983 }
984 rcpts = append(rcpts, f)
985 }
986 return rcpts
987}
988
989func savetracks(tracks map[string][]string) {
990 db := opendatabase()
991 tx, err := db.Begin()
992 if err != nil {
993 elog.Printf("savetracks begin error: %s", err)
994 return
995 }
996 defer func() {
997 err := tx.Commit()
998 if err != nil {
999 elog.Printf("savetracks commit error: %s", err)
1000 }
1001
1002 }()
1003 stmtGetTracks, err := tx.Prepare("select fetches from tracks where xid = ?")
1004 if err != nil {
1005 elog.Printf("savetracks error: %s", err)
1006 return
1007 }
1008 stmtNewTracks, err := tx.Prepare("insert into tracks (xid, fetches) values (?, ?)")
1009 if err != nil {
1010 elog.Printf("savetracks error: %s", err)
1011 return
1012 }
1013 stmtUpdateTracks, err := tx.Prepare("update tracks set fetches = ? where xid = ?")
1014 if err != nil {
1015 elog.Printf("savetracks error: %s", err)
1016 return
1017 }
1018 count := 0
1019 for xid, f := range tracks {
1020 count += len(f)
1021 var prev string
1022 row := stmtGetTracks.QueryRow(xid)
1023 err := row.Scan(&prev)
1024 if err == sql.ErrNoRows {
1025 f = oneofakind(f)
1026 stmtNewTracks.Exec(xid, strings.Join(f, " "))
1027 } else if err == nil {
1028 all := append(strings.Split(prev, " "), f...)
1029 all = oneofakind(all)
1030 stmtUpdateTracks.Exec(strings.Join(all, " "))
1031 } else {
1032 elog.Printf("savetracks error: %s", err)
1033 }
1034 }
1035 dlog.Printf("saved %d new fetches", count)
1036}
1037
1038var trackchan = make(chan Track)
1039var dumptracks = make(chan chan bool)
1040
1041func tracker() {
1042 timeout := 4 * time.Minute
1043 sleeper := time.NewTimer(timeout)
1044 tracks := make(map[string][]string)
1045 workinprogress++
1046 for {
1047 select {
1048 case track := <-trackchan:
1049 tracks[track.xid] = append(tracks[track.xid], track.who)
1050 case <-sleeper.C:
1051 if len(tracks) > 0 {
1052 go savetracks(tracks)
1053 tracks = make(map[string][]string)
1054 }
1055 sleeper.Reset(timeout)
1056 case c := <-dumptracks:
1057 if len(tracks) > 0 {
1058 savetracks(tracks)
1059 }
1060 c <- true
1061 case <-endoftheworld:
1062 if len(tracks) > 0 {
1063 savetracks(tracks)
1064 }
1065 readyalready <- true
1066 return
1067 }
1068 }
1069}
1070
1071var re_keyholder = regexp.MustCompile(`keyId="([^"]+)"`)
1072
1073func trackback(xid string, r *http.Request) {
1074 agent := r.UserAgent()
1075 who := originate(agent)
1076 sig := r.Header.Get("Signature")
1077 if sig != "" {
1078 m := re_keyholder.FindStringSubmatch(sig)
1079 if len(m) == 2 {
1080 who = m[1]
1081 }
1082 }
1083 if who != "" {
1084 trackchan <- Track{xid: xid, who: who}
1085 }
1086}
1087
1088func sameperson(h1, h2 *Honk) bool {
1089 n1, n2 := h1.Honker, h2.Honker
1090 if h1.Oonker != "" {
1091 n1 = h1.Oonker
1092 }
1093 if h2.Oonker != "" {
1094 n2 = h2.Oonker
1095 }
1096 return n1 == n2
1097}
1098
1099func threadsort(honks []*Honk) []*Honk {
1100 sort.Slice(honks, func(i, j int) bool {
1101 return honks[i].Date.Before(honks[j].Date)
1102 })
1103 honkx := make(map[string]*Honk)
1104 kids := make(map[string][]*Honk)
1105 for _, h := range honks {
1106 honkx[h.XID] = h
1107 rid := h.RID
1108 kids[rid] = append(kids[rid], h)
1109 }
1110 done := make(map[*Honk]bool)
1111 var thread []*Honk
1112 var nextlevel func(p *Honk)
1113 level := 0
1114 nextlevel = func(p *Honk) {
1115 levelup := level < 4
1116 if pp := honkx[p.RID]; p.RID == "" || (pp != nil && sameperson(p, pp)) {
1117 levelup = false
1118 }
1119 if level > 0 && len(kids[p.RID]) == 1 {
1120 if pp := honkx[p.RID]; pp != nil && len(kids[pp.RID]) == 1 {
1121 levelup = false
1122 }
1123 }
1124 if levelup {
1125 level++
1126 }
1127 p.Style += fmt.Sprintf(" level%d", level)
1128 childs := kids[p.XID]
1129 if false {
1130 sort.SliceStable(childs, func(i, j int) bool {
1131 return sameperson(childs[i], p) && !sameperson(childs[j], p)
1132 })
1133 }
1134 if true {
1135 sort.SliceStable(childs, func(i, j int) bool {
1136 return !sameperson(childs[i], p) && sameperson(childs[j], p)
1137 })
1138 }
1139 for _, h := range childs {
1140 if !done[h] {
1141 done[h] = true
1142 thread = append(thread, h)
1143 nextlevel(h)
1144 }
1145 }
1146 if levelup {
1147 level--
1148 }
1149 }
1150 for _, h := range honks {
1151 if !done[h] && h.RID == "" {
1152 done[h] = true
1153 thread = append(thread, h)
1154 nextlevel(h)
1155 }
1156 }
1157 for _, h := range honks {
1158 if !done[h] {
1159 done[h] = true
1160 thread = append(thread, h)
1161 nextlevel(h)
1162 }
1163 }
1164 return thread
1165}
1166
1167func honkology(honk *Honk) template.HTML {
1168 var user *WhatAbout
1169 ok := somenumberedusers.Get(honk.UserID, &user)
1170 if !ok {
1171 return ""
1172 }
1173 title := fmt.Sprintf("%s: %s", user.Display, honk.Precis)
1174 imgurl := avatarURL(user)
1175 for _, d := range honk.Donks {
1176 if d.Local && strings.HasPrefix(d.Media, "image") {
1177 imgurl = d.URL
1178 break
1179 }
1180 }
1181 short := honk.Noise
1182 if len(short) > 160 {
1183 short = short[0:160] + "..."
1184 }
1185 return templates.Sprintf(
1186 `<meta property="og:title" content="%s" />
1187<meta property="og:type" content="article" />
1188<meta property="article:author" content="%s" />
1189<meta property="og:url" content="%s" />
1190<meta property="og:image" content="%s" />
1191<meta property="og:description" content="%s" />`,
1192 title, user.URL, honk.XID, imgurl, short)
1193}
1194
1195func showonehonk(w http.ResponseWriter, r *http.Request) {
1196 name := mux.Vars(r)["name"]
1197 user, err := butwhatabout(name)
1198 if err != nil {
1199 http.NotFound(w, r)
1200 return
1201 }
1202 if stealthmode(user.ID, r) {
1203 http.NotFound(w, r)
1204 return
1205 }
1206 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
1207
1208 if friendorfoe(r.Header.Get("Accept")) {
1209 j, ok := gimmejonk(xid)
1210 if ok {
1211 trackback(xid, r)
1212 w.Header().Set("Content-Type", theonetruename)
1213 w.Write(j)
1214 } else {
1215 http.NotFound(w, r)
1216 }
1217 return
1218 }
1219 honk := getxonk(user.ID, xid)
1220 if honk == nil {
1221 http.NotFound(w, r)
1222 return
1223 }
1224 u := login.GetUserInfo(r)
1225 if u != nil && u.UserID != user.ID {
1226 u = nil
1227 }
1228 if !honk.Public {
1229 if u == nil {
1230 http.NotFound(w, r)
1231 return
1232
1233 }
1234 honks := []*Honk{honk}
1235 donksforhonks(honks)
1236 templinfo := getInfo(r)
1237 templinfo["ServerMessage"] = "one honk maybe more"
1238 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1239 honkpage(w, u, honks, templinfo)
1240 return
1241 }
1242
1243 templinfo := getInfo(r)
1244 rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy, 0)
1245 //reversehonks(rawhonks)
1246 rawhonks = threadsort(rawhonks)
1247 var honks []*Honk
1248 for i, h := range rawhonks {
1249 if h.XID == xid {
1250 templinfo["Honkology"] = honkology(h)
1251 if i > 0 {
1252 h.Style += " glow"
1253 }
1254 }
1255 if h.Public && (h.Whofore == 2 || h.IsAcked()) {
1256 honks = append(honks, h)
1257 }
1258 }
1259
1260 templinfo["ServerMessage"] = "one honk maybe more"
1261 if u != nil {
1262 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1263 }
1264 templinfo["APAltLink"] = templates.Sprintf("<link href='%s' rel='alternate' type='application/activity+json'>", xid)
1265 honkpage(w, u, honks, templinfo)
1266}
1267
1268func honkpage(w http.ResponseWriter, u *login.UserInfo, honks []*Honk, templinfo map[string]interface{}) {
1269 var emunames []string
1270 dir, err := os.Open(dataDir + "/emus")
1271 if err == nil {
1272 emunames, _ = dir.Readdirnames(0)
1273 dir.Close()
1274 }
1275 for i, e := range emunames {
1276 if len(e) > 4 {
1277 emunames[i] = e[:len(e)-4]
1278 }
1279 }
1280 templinfo["Emus"] = emunames
1281 var userid int64 = -1
1282 if u != nil {
1283 userid = u.UserID
1284 templinfo["User"], _ = butwhatabout(u.Username)
1285 }
1286 reverbolate(userid, honks)
1287 templinfo["Honks"] = honks
1288 templinfo["MapLink"] = getmaplink(u)
1289 if templinfo["TopHID"] == nil {
1290 if len(honks) > 0 {
1291 templinfo["TopHID"] = honks[0].ID
1292 } else {
1293 templinfo["TopHID"] = 0
1294 }
1295 }
1296 if u == nil && !develMode {
1297 w.Header().Set("Cache-Control", "max-age=60")
1298 }
1299 err = readviews.Execute(w, "honkpage.html", templinfo)
1300 if err != nil {
1301 elog.Print(err)
1302 }
1303}
1304
1305func saveuser(w http.ResponseWriter, r *http.Request) {
1306 whatabout := r.FormValue("whatabout")
1307 whatabout = strings.Replace(whatabout, "\r", "", -1)
1308 u := login.GetUserInfo(r)
1309 user, _ := butwhatabout(u.Username)
1310 db := opendatabase()
1311
1312 options := user.Options
1313 if r.FormValue("skinny") == "skinny" {
1314 options.SkinnyCSS = true
1315 } else {
1316 options.SkinnyCSS = false
1317 }
1318 if r.FormValue("omitimages") == "omitimages" {
1319 options.OmitImages = true
1320 } else {
1321 options.OmitImages = false
1322 }
1323 if r.FormValue("mentionall") == "mentionall" {
1324 options.MentionAll = true
1325 } else {
1326 options.MentionAll = false
1327 }
1328 if r.FormValue("inlineqts") == "inlineqts" {
1329 options.InlineQuotes = true
1330 } else {
1331 options.InlineQuotes = false
1332 }
1333 if r.FormValue("maps") == "apple" {
1334 options.MapLink = "apple"
1335 } else {
1336 options.MapLink = ""
1337 }
1338
1339 sendupdate := false
1340 if r.FormValue("displayname") != "" {
1341 options.CustomDisplay = r.FormValue("displayname")
1342 _, err := db.Exec("update users set displayname = ? where username = ?", options.CustomDisplay, u.Username)
1343 if err != nil {
1344 elog.Printf("error setting displayname: %s", err)
1345 }
1346 sendupdate = true
1347 } else {
1348 options.CustomDisplay = ""
1349 }
1350
1351 options.Reaction = r.FormValue("reaction")
1352
1353 ava := re_avatar.FindString(whatabout)
1354 if ava != "" {
1355 whatabout = re_avatar.ReplaceAllString(whatabout, "")
1356 ava = ava[7:]
1357 if ava[0] == ' ' {
1358 ava = ava[1:]
1359 }
1360 ava = fmt.Sprintf("https://%s/meme/%s", serverName, ava)
1361 }
1362 if ava != options.Avatar {
1363 options.Avatar = ava
1364 sendupdate = true
1365 }
1366 ban := re_banner.FindString(whatabout)
1367 if ban != "" {
1368 whatabout = re_banner.ReplaceAllString(whatabout, "")
1369 ban = ban[7:]
1370 if ban[0] == ' ' {
1371 ban = ban[1:]
1372 }
1373 ban = fmt.Sprintf("https://%s/meme/%s", serverName, ban)
1374 }
1375 if ban != options.Banner {
1376 options.Banner = ban
1377 sendupdate = true
1378 }
1379 whatabout = strings.TrimSpace(whatabout)
1380 if whatabout != user.About {
1381 sendupdate = true
1382 }
1383 j, err := jsonify(options)
1384 if err == nil {
1385 _, err = db.Exec("update users set about = ?, options = ? where username = ?", whatabout, j, u.Username)
1386 }
1387 if err != nil {
1388 elog.Printf("error bouting what: %s", err)
1389 }
1390 somenamedusers.Clear(u.Username)
1391 somenumberedusers.Clear(u.UserID)
1392 oldjonkers.Clear(u.Username)
1393
1394 if sendupdate {
1395 updateMe(u.Username)
1396 }
1397
1398 http.Redirect(w, r, "/account", http.StatusSeeOther)
1399}
1400
1401func bonkit(xid string, user *WhatAbout) {
1402 dlog.Printf("bonking %s", xid)
1403
1404 xonk := getxonk(user.ID, xid)
1405 if xonk == nil {
1406 return
1407 }
1408 if !xonk.Public {
1409 return
1410 }
1411 if xonk.IsBonked() {
1412 return
1413 }
1414 donksforhonks([]*Honk{xonk})
1415
1416 _, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
1417 if err != nil {
1418 elog.Printf("error acking bonk: %s", err)
1419 }
1420
1421 oonker := xonk.Oonker
1422 if oonker == "" {
1423 oonker = xonk.Honker
1424 }
1425 dt := time.Now().UTC()
1426 bonk := &Honk{
1427 UserID: user.ID,
1428 Username: user.Name,
1429 What: "bonk",
1430 Honker: user.URL,
1431 Oonker: oonker,
1432 XID: xonk.XID,
1433 RID: xonk.RID,
1434 Noise: xonk.Noise,
1435 Precis: xonk.Precis,
1436 URL: xonk.URL,
1437 Date: dt,
1438 Donks: xonk.Donks,
1439 Whofore: 2,
1440 Convoy: xonk.Convoy,
1441 Audience: []string{thewholeworld, oonker},
1442 Public: true,
1443 Format: xonk.Format,
1444 Place: xonk.Place,
1445 Onts: xonk.Onts,
1446 Time: xonk.Time,
1447 }
1448
1449 err = savehonk(bonk)
1450 if err != nil {
1451 elog.Printf("uh oh")
1452 return
1453 }
1454
1455 go honkworldwide(user, bonk)
1456}
1457
1458func submitbonk(w http.ResponseWriter, r *http.Request) {
1459 xid := r.FormValue("xid")
1460 userinfo := login.GetUserInfo(r)
1461 user, _ := butwhatabout(userinfo.Username)
1462
1463 bonkit(xid, user)
1464
1465 if r.FormValue("js") != "1" {
1466 templinfo := getInfo(r)
1467 templinfo["ServerMessage"] = "Bonked!"
1468 err := readviews.Execute(w, "msg.html", templinfo)
1469 if err != nil {
1470 elog.Print(err)
1471 }
1472 }
1473}
1474
1475func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string, aux string) {
1476 zonk := &Honk{
1477 What: what,
1478 XID: xonk.XID,
1479 Date: time.Now().UTC(),
1480 Audience: oneofakind(xonk.Audience),
1481 Noise: aux,
1482 }
1483 zonk.Public = loudandproud(zonk.Audience)
1484
1485 dlog.Printf("announcing %sed honk: %s", what, xonk.XID)
1486 go honkworldwide(user, zonk)
1487}
1488
1489func zonkit(w http.ResponseWriter, r *http.Request) {
1490 wherefore := r.FormValue("wherefore")
1491 what := r.FormValue("what")
1492 userinfo := login.GetUserInfo(r)
1493 user, _ := butwhatabout(userinfo.Username)
1494
1495 if wherefore == "save" {
1496 xonk := getxonk(userinfo.UserID, what)
1497 if xonk != nil {
1498 _, err := stmtUpdateFlags.Exec(flagIsSaved, xonk.ID)
1499 if err != nil {
1500 elog.Printf("error saving: %s", err)
1501 }
1502 }
1503 return
1504 }
1505
1506 if wherefore == "unsave" {
1507 xonk := getxonk(userinfo.UserID, what)
1508 if xonk != nil {
1509 _, err := stmtClearFlags.Exec(flagIsSaved, xonk.ID)
1510 if err != nil {
1511 elog.Printf("error unsaving: %s", err)
1512 }
1513 }
1514 return
1515 }
1516
1517 if wherefore == "react" {
1518 reaction := user.Options.Reaction
1519 if r2 := r.FormValue("reaction"); r2 != "" {
1520 reaction = r2
1521 }
1522 if reaction == "none" {
1523 return
1524 }
1525 xonk := getxonk(userinfo.UserID, what)
1526 if xonk != nil {
1527 _, err := stmtUpdateFlags.Exec(flagIsReacted, xonk.ID)
1528 if err != nil {
1529 elog.Printf("error saving: %s", err)
1530 }
1531 sendzonkofsorts(xonk, user, "react", reaction)
1532 }
1533 return
1534 }
1535
1536 // my hammer is too big, oh well
1537 defer oldjonks.Flush()
1538
1539 if wherefore == "ack" {
1540 xonk := getxonk(userinfo.UserID, what)
1541 if xonk != nil && !xonk.IsAcked() {
1542 _, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1543 if err != nil {
1544 elog.Printf("error acking: %s", err)
1545 }
1546 sendzonkofsorts(xonk, user, "ack", "")
1547 }
1548 return
1549 }
1550
1551 if wherefore == "deack" {
1552 xonk := getxonk(userinfo.UserID, what)
1553 if xonk != nil && xonk.IsAcked() {
1554 _, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1555 if err != nil {
1556 elog.Printf("error deacking: %s", err)
1557 }
1558 sendzonkofsorts(xonk, user, "deack", "")
1559 }
1560 return
1561 }
1562
1563 if wherefore == "bonk" {
1564 user, _ := butwhatabout(userinfo.Username)
1565 bonkit(what, user)
1566 return
1567 }
1568
1569 if wherefore == "unbonk" {
1570 xonk := getbonk(userinfo.UserID, what)
1571 if xonk != nil {
1572 deletehonk(xonk.ID)
1573 xonk = getxonk(userinfo.UserID, what)
1574 _, err := stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1575 if err != nil {
1576 elog.Printf("error unbonking: %s", err)
1577 }
1578 sendzonkofsorts(xonk, user, "unbonk", "")
1579 }
1580 return
1581 }
1582
1583 if wherefore == "untag" {
1584 xonk := getxonk(userinfo.UserID, what)
1585 if xonk != nil {
1586 _, err := stmtUpdateFlags.Exec(flagIsUntagged, xonk.ID)
1587 if err != nil {
1588 elog.Printf("error untagging: %s", err)
1589 }
1590 }
1591 var badparents map[string]bool
1592 untagged.GetAndLock(userinfo.UserID, &badparents)
1593 badparents[what] = true
1594 untagged.Unlock()
1595 return
1596 }
1597
1598 ilog.Printf("zonking %s %s", wherefore, what)
1599 if wherefore == "zonk" {
1600 xonk := getxonk(userinfo.UserID, what)
1601 if xonk != nil {
1602 deletehonk(xonk.ID)
1603 if xonk.Whofore == 2 || xonk.Whofore == 3 {
1604 sendzonkofsorts(xonk, user, "zonk", "")
1605 }
1606 }
1607 }
1608 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1609 if err != nil {
1610 elog.Printf("error saving zonker: %s", err)
1611 return
1612 }
1613}
1614
1615func edithonkpage(w http.ResponseWriter, r *http.Request) {
1616 u := login.GetUserInfo(r)
1617 user, _ := butwhatabout(u.Username)
1618 xid := r.FormValue("xid")
1619 honk := getxonk(u.UserID, xid)
1620 if !canedithonk(user, honk) {
1621 http.Error(w, "no editing that please", http.StatusInternalServerError)
1622 return
1623 }
1624
1625 noise := honk.Noise
1626
1627 honks := []*Honk{honk}
1628 donksforhonks(honks)
1629 reverbolate(u.UserID, honks)
1630 templinfo := getInfo(r)
1631 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1632 templinfo["Honks"] = honks
1633 templinfo["MapLink"] = getmaplink(u)
1634 templinfo["Noise"] = noise
1635 templinfo["SavedPlace"] = honk.Place
1636 if tm := honk.Time; tm != nil {
1637 templinfo["ShowTime"] = " "
1638 templinfo["StartTime"] = tm.StartTime.Format("2006-01-02 15:04")
1639 if tm.Duration != 0 {
1640 templinfo["Duration"] = tm.Duration
1641 }
1642 }
1643 templinfo["ServerMessage"] = "honk edit"
1644 templinfo["IsPreview"] = true
1645 templinfo["UpdateXID"] = honk.XID
1646 if len(honk.Donks) > 0 {
1647 var savedfiles []string
1648 for _, d := range honk.Donks {
1649 savedfiles = append(savedfiles, fmt.Sprintf("%s:%d", d.XID, d.FileID))
1650 }
1651 templinfo["SavedFile"] = strings.Join(savedfiles, ",")
1652 }
1653 err := readviews.Execute(w, "honkpage.html", templinfo)
1654 if err != nil {
1655 elog.Print(err)
1656 }
1657}
1658
1659func newhonkpage(w http.ResponseWriter, r *http.Request) {
1660 u := login.GetUserInfo(r)
1661 rid := r.FormValue("rid")
1662 noise := ""
1663
1664 xonk := getxonk(u.UserID, rid)
1665 if xonk != nil {
1666 _, replto := handles(xonk.Honker)
1667 if replto != "" {
1668 noise = "@" + replto + " "
1669 }
1670 }
1671
1672 templinfo := getInfo(r)
1673 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1674 templinfo["InReplyTo"] = rid
1675 templinfo["Noise"] = noise
1676 templinfo["ServerMessage"] = "compose honk"
1677 templinfo["IsPreview"] = true
1678 err := readviews.Execute(w, "honkpage.html", templinfo)
1679 if err != nil {
1680 elog.Print(err)
1681 }
1682}
1683
1684func canedithonk(user *WhatAbout, honk *Honk) bool {
1685 if honk == nil || honk.Honker != user.URL || honk.What == "bonk" {
1686 return false
1687 }
1688 return true
1689}
1690
1691func submitdonk(w http.ResponseWriter, r *http.Request) ([]*Donk, error) {
1692 if !strings.HasPrefix(strings.ToLower(r.Header.Get("Content-Type")), "multipart/form-data") {
1693 return nil, nil
1694 }
1695 var donks []*Donk
1696 for i, hdr := range r.MultipartForm.File["donk"] {
1697 if i > 16 {
1698 break
1699 }
1700 donk, err := formtodonk(w, r, hdr)
1701 if err != nil {
1702 return nil, err
1703 }
1704 donks = append(donks, donk)
1705 }
1706 return donks, nil
1707}
1708
1709func formtodonk(w http.ResponseWriter, r *http.Request, filehdr *multipart.FileHeader) (*Donk, error) {
1710 file, err := filehdr.Open()
1711 if err != nil {
1712 if err == http.ErrMissingFile {
1713 return nil, nil
1714 }
1715 elog.Printf("error reading donk: %s", err)
1716 http.Error(w, "error reading donk", http.StatusUnsupportedMediaType)
1717 return nil, err
1718 }
1719 var buf bytes.Buffer
1720 io.Copy(&buf, file)
1721 file.Close()
1722 data := buf.Bytes()
1723 var media, name string
1724 img, err := bigshrink(data)
1725 if err == nil {
1726 data = img.Data
1727 format := img.Format
1728 media = "image/" + format
1729 if format == "jpeg" {
1730 format = "jpg"
1731 }
1732 if format == "svg+xml" {
1733 format = "svg"
1734 }
1735 name = xfiltrate() + "." + format
1736 } else {
1737 ct := http.DetectContentType(data)
1738 switch ct {
1739 case "application/pdf":
1740 maxsize := 10000000
1741 if len(data) > maxsize {
1742 ilog.Printf("bad image: %s too much pdf: %d", err, len(data))
1743 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1744 return nil, err
1745 }
1746 media = ct
1747 name = filehdr.Filename
1748 if name == "" {
1749 name = xfiltrate() + ".pdf"
1750 }
1751 default:
1752 maxsize := 100000
1753 if len(data) > maxsize {
1754 ilog.Printf("bad image: %s too much text: %d", err, len(data))
1755 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1756 return nil, err
1757 }
1758 for i := 0; i < len(data); i++ {
1759 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1760 ilog.Printf("bad image: %s not text: %d", err, data[i])
1761 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1762 return nil, err
1763 }
1764 }
1765 media = "text/plain"
1766 name = filehdr.Filename
1767 if name == "" {
1768 name = xfiltrate() + ".txt"
1769 }
1770 }
1771 }
1772 desc := strings.TrimSpace(r.FormValue("donkdesc"))
1773 if desc == "" {
1774 desc = name
1775 }
1776 fileid, xid, err := savefileandxid(name, desc, "", media, true, data)
1777 if err != nil {
1778 elog.Printf("unable to save image: %s", err)
1779 http.Error(w, "failed to save attachment", http.StatusUnsupportedMediaType)
1780 return nil, err
1781 }
1782 d := &Donk{
1783 FileID: fileid,
1784 XID: xid,
1785 Desc: desc,
1786 Local: true,
1787 }
1788 return d, nil
1789}
1790
1791func websubmithonk(w http.ResponseWriter, r *http.Request) {
1792 h := submithonk(w, r)
1793 if h == nil {
1794 return
1795 }
1796 http.Redirect(w, r, h.XID[len(serverName)+8:], http.StatusSeeOther)
1797}
1798
1799// what a hot mess this function is
1800func submithonk(w http.ResponseWriter, r *http.Request) *Honk {
1801 rid := r.FormValue("rid")
1802 noise := r.FormValue("noise")
1803 format := r.FormValue("format")
1804 if format == "" {
1805 format = "markdown"
1806 }
1807 if !(format == "markdown" || format == "html") {
1808 http.Error(w, "unknown format", 500)
1809 return nil
1810 }
1811
1812 userinfo := login.GetUserInfo(r)
1813 user, _ := butwhatabout(userinfo.Username)
1814
1815 dt := time.Now().UTC()
1816 updatexid := r.FormValue("updatexid")
1817 var honk *Honk
1818 if updatexid != "" {
1819 honk = getxonk(userinfo.UserID, updatexid)
1820 if !canedithonk(user, honk) {
1821 http.Error(w, "no editing that please", http.StatusInternalServerError)
1822 return nil
1823 }
1824 honk.Date = dt
1825 honk.What = "update"
1826 honk.Format = format
1827 } else {
1828 xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1829 what := "honk"
1830 honk = &Honk{
1831 UserID: userinfo.UserID,
1832 Username: userinfo.Username,
1833 What: what,
1834 Honker: user.URL,
1835 XID: xid,
1836 Date: dt,
1837 Format: format,
1838 }
1839 }
1840
1841 var convoy string
1842 noise = strings.Replace(noise, "\r", "", -1)
1843 if updatexid == "" && rid == "" {
1844 noise = re_convoy.ReplaceAllStringFunc(noise, func(m string) string {
1845 convoy = m[7:]
1846 convoy = strings.TrimSpace(convoy)
1847 if !re_convalidate.MatchString(convoy) {
1848 convoy = ""
1849 }
1850 return ""
1851 })
1852 }
1853 noise = quickrename(noise, userinfo.UserID)
1854 noise = hooterize(noise)
1855 honk.Noise = noise
1856 precipitate(honk)
1857 noise = honk.Noise
1858 recategorize(honk)
1859 translate(honk)
1860
1861 if rid != "" {
1862 xonk := getxonk(userinfo.UserID, rid)
1863 if xonk == nil {
1864 http.Error(w, "replyto disappeared", http.StatusNotFound)
1865 return nil
1866 }
1867 if xonk.Public {
1868 honk.Audience = append(honk.Audience, xonk.Audience...)
1869 }
1870 convoy = xonk.Convoy
1871 for i, a := range honk.Audience {
1872 if a == thewholeworld {
1873 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1874 break
1875 }
1876 }
1877 honk.RID = rid
1878 if xonk.Precis != "" && honk.Precis == "" {
1879 honk.Precis = xonk.Precis
1880 if !re_dangerous.MatchString(honk.Precis) {
1881 honk.Precis = "re: " + honk.Precis
1882 }
1883 }
1884 } else if updatexid == "" {
1885 honk.Audience = []string{thewholeworld}
1886 }
1887 if honk.Noise != "" && honk.Noise[0] == '@' {
1888 honk.Audience = append(grapevine(honk.Mentions), honk.Audience...)
1889 } else {
1890 honk.Audience = append(honk.Audience, grapevine(honk.Mentions)...)
1891 }
1892
1893 if convoy == "" {
1894 convoy = fmt.Sprintf("data:,%s-", masqName) + xfiltrate()
1895 }
1896 butnottooloud(honk.Audience)
1897 honk.Audience = oneofakind(honk.Audience)
1898 if len(honk.Audience) == 0 {
1899 ilog.Printf("honk to nowhere")
1900 http.Error(w, "honk to nowhere...", http.StatusNotFound)
1901 return nil
1902 }
1903 honk.Public = loudandproud(honk.Audience)
1904 honk.Convoy = convoy
1905 donkxid := strings.Join(r.Form["donkxid"], ",")
1906 if donkxid == "" {
1907 donks, err := submitdonk(w, r)
1908 if err != nil && err != http.ErrMissingFile {
1909 return nil
1910 }
1911 if len(donks) > 0 {
1912 honk.Donks = append(honk.Donks, donks...)
1913 var xids []string
1914 for _, d := range honk.Donks {
1915 xids = append(xids, fmt.Sprintf("%s:%d", d.XID, d.FileID))
1916 }
1917 donkxid = strings.Join(xids, ",")
1918 }
1919 } else {
1920 xids := strings.Split(donkxid, ",")
1921 for i, xid := range xids {
1922 if i > 16 {
1923 break
1924 }
1925 p := strings.Split(xid, ":")
1926 xid = p[0]
1927 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1928 var donk *Donk
1929 if len(p) > 1 {
1930 fileid, _ := strconv.ParseInt(p[1], 10, 0)
1931 donk = finddonkid(fileid, url)
1932 } else {
1933 donk = finddonk(url)
1934 }
1935 if donk != nil {
1936 honk.Donks = append(honk.Donks, donk)
1937 } else {
1938 ilog.Printf("can't find file: %s", xid)
1939 }
1940 }
1941 }
1942 memetize(honk)
1943 imaginate(honk)
1944
1945 placename := strings.TrimSpace(r.FormValue("placename"))
1946 placelat := strings.TrimSpace(r.FormValue("placelat"))
1947 placelong := strings.TrimSpace(r.FormValue("placelong"))
1948 placeurl := strings.TrimSpace(r.FormValue("placeurl"))
1949 if placename != "" || placelat != "" || placelong != "" || placeurl != "" {
1950 p := new(Place)
1951 p.Name = placename
1952 p.Latitude, _ = strconv.ParseFloat(placelat, 64)
1953 p.Longitude, _ = strconv.ParseFloat(placelong, 64)
1954 p.Url = placeurl
1955 honk.Place = p
1956 }
1957 timestart := strings.TrimSpace(r.FormValue("timestart"))
1958 if timestart != "" {
1959 t := new(Time)
1960 now := time.Now().Local()
1961 for _, layout := range []string{"2006-01-02 3:04pm", "2006-01-02 15:04", "3:04pm", "15:04"} {
1962 start, err := time.ParseInLocation(layout, timestart, now.Location())
1963 if err == nil {
1964 if start.Year() == 0 {
1965 start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
1966 }
1967 t.StartTime = start
1968 break
1969 }
1970 }
1971 timeend := r.FormValue("timeend")
1972 dur := parseDuration(timeend)
1973 if dur != 0 {
1974 t.Duration = Duration(dur)
1975 }
1976 if !t.StartTime.IsZero() {
1977 honk.What = "event"
1978 honk.Time = t
1979 }
1980 }
1981
1982 if honk.Public {
1983 honk.Whofore = 2
1984 } else {
1985 honk.Whofore = 3
1986 }
1987
1988 // back to markdown
1989 honk.Noise = noise
1990
1991 if r.FormValue("preview") == "preview" {
1992 honks := []*Honk{honk}
1993 reverbolate(userinfo.UserID, honks)
1994 templinfo := getInfo(r)
1995 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1996 templinfo["Honks"] = honks
1997 templinfo["MapLink"] = getmaplink(userinfo)
1998 templinfo["InReplyTo"] = r.FormValue("rid")
1999 templinfo["Noise"] = r.FormValue("noise")
2000 templinfo["SavedFile"] = donkxid
2001 if tm := honk.Time; tm != nil {
2002 templinfo["ShowTime"] = " "
2003 templinfo["StartTime"] = tm.StartTime.Format("2006-01-02 15:04")
2004 if tm.Duration != 0 {
2005 templinfo["Duration"] = tm.Duration
2006 }
2007 }
2008 templinfo["IsPreview"] = true
2009 templinfo["UpdateXID"] = updatexid
2010 templinfo["ServerMessage"] = "honk preview"
2011 err := readviews.Execute(w, "honkpage.html", templinfo)
2012 if err != nil {
2013 elog.Print(err)
2014 }
2015 return nil
2016 }
2017
2018 if updatexid != "" {
2019 updatehonk(honk)
2020 oldjonks.Clear(honk.XID)
2021 } else {
2022 err := savehonk(honk)
2023 if err != nil {
2024 elog.Printf("uh oh")
2025 return nil
2026 }
2027 }
2028
2029 // reload for consistency
2030 honk.Donks = nil
2031 donksforhonks([]*Honk{honk})
2032
2033 go honkworldwide(user, honk)
2034
2035 return honk
2036}
2037
2038func showhonkers(w http.ResponseWriter, r *http.Request) {
2039 userinfo := login.GetUserInfo(r)
2040 templinfo := getInfo(r)
2041 templinfo["Honkers"] = gethonkers(userinfo.UserID)
2042 templinfo["HonkerCSRF"] = login.GetCSRF("submithonker", r)
2043 err := readviews.Execute(w, "honkers.html", templinfo)
2044 if err != nil {
2045 elog.Print(err)
2046 }
2047}
2048
2049func showchatter(w http.ResponseWriter, r *http.Request) {
2050 u := login.GetUserInfo(r)
2051 chatnewnone(u.UserID)
2052 chatter := loadchatter(u.UserID)
2053 for _, chat := range chatter {
2054 for _, ch := range chat.Chonks {
2055 filterchonk(ch)
2056 }
2057 }
2058
2059 templinfo := getInfo(r)
2060 templinfo["Chatter"] = chatter
2061 templinfo["ChonkCSRF"] = login.GetCSRF("sendchonk", r)
2062 err := readviews.Execute(w, "chatter.html", templinfo)
2063 if err != nil {
2064 elog.Print(err)
2065 }
2066}
2067
2068func submitchonk(w http.ResponseWriter, r *http.Request) {
2069 u := login.GetUserInfo(r)
2070 user, _ := butwhatabout(u.Username)
2071 noise := r.FormValue("noise")
2072 target := r.FormValue("target")
2073 format := "markdown"
2074 dt := time.Now().UTC()
2075 xid := fmt.Sprintf("%s/%s/%s", user.URL, "chonk", xfiltrate())
2076
2077 if !strings.HasPrefix(target, "https://") {
2078 target = fullname(target, u.UserID)
2079 }
2080 if target == "" {
2081 http.Error(w, "who is that?", http.StatusInternalServerError)
2082 return
2083 }
2084 ch := Chonk{
2085 UserID: u.UserID,
2086 XID: xid,
2087 Who: user.URL,
2088 Target: target,
2089 Date: dt,
2090 Noise: noise,
2091 Format: format,
2092 }
2093 donks, err := submitdonk(w, r)
2094 if err != nil && err != http.ErrMissingFile {
2095 return
2096 }
2097 if len(donks) > 0 {
2098 ch.Donks = append(ch.Donks, donks...)
2099 }
2100
2101 translatechonk(&ch)
2102 savechonk(&ch)
2103 // reload for consistency
2104 ch.Donks = nil
2105 donksforchonks([]*Chonk{&ch})
2106 go sendchonk(user, &ch)
2107
2108 http.Redirect(w, r, "/chatter", http.StatusSeeOther)
2109}
2110
2111var combocache = cache.New(cache.Options{Filler: func(userid int64) ([]string, bool) {
2112 honkers := gethonkers(userid)
2113 var combos []string
2114 for _, h := range honkers {
2115 combos = append(combos, h.Combos...)
2116 }
2117 for i, c := range combos {
2118 if c == "-" {
2119 combos[i] = ""
2120 }
2121 }
2122 combos = oneofakind(combos)
2123 sort.Strings(combos)
2124 return combos, true
2125}, Invalidator: &honkerinvalidator})
2126
2127func showcombos(w http.ResponseWriter, r *http.Request) {
2128 userinfo := login.GetUserInfo(r)
2129 var combos []string
2130 combocache.Get(userinfo.UserID, &combos)
2131 templinfo := getInfo(r)
2132 err := readviews.Execute(w, "combos.html", templinfo)
2133 if err != nil {
2134 elog.Print(err)
2135 }
2136}
2137
2138func websubmithonker(w http.ResponseWriter, r *http.Request) {
2139 h := submithonker(w, r)
2140 if h == nil {
2141 return
2142 }
2143 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
2144}
2145
2146func submithonker(w http.ResponseWriter, r *http.Request) *Honker {
2147 u := login.GetUserInfo(r)
2148 user, _ := butwhatabout(u.Username)
2149 name := strings.TrimSpace(r.FormValue("name"))
2150 url := strings.TrimSpace(r.FormValue("url"))
2151 peep := r.FormValue("peep")
2152 combos := strings.TrimSpace(r.FormValue("combos"))
2153 combos = " " + combos + " "
2154 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
2155
2156 re_namecheck := regexp.MustCompile("^[\\pL[:digit:]_.-]+$")
2157 if name != "" && !re_namecheck.MatchString(name) {
2158 http.Error(w, "please use a plainer name", http.StatusInternalServerError)
2159 return nil
2160 }
2161
2162 var meta HonkerMeta
2163 meta.Notes = strings.TrimSpace(r.FormValue("notes"))
2164 mj, _ := jsonify(&meta)
2165
2166 defer honkerinvalidator.Clear(u.UserID)
2167
2168 // mostly dummy, fill in later...
2169 h := &Honker{
2170 ID: honkerid,
2171 }
2172
2173 if honkerid > 0 {
2174 if r.FormValue("delete") == "delete" {
2175 unfollowyou(user, honkerid, false)
2176 stmtDeleteHonker.Exec(honkerid)
2177 return h
2178 }
2179 if r.FormValue("unsub") == "unsub" {
2180 unfollowyou(user, honkerid, false)
2181 }
2182 if r.FormValue("sub") == "sub" {
2183 followyou(user, honkerid, false)
2184 }
2185 _, err := stmtUpdateHonker.Exec(name, combos, mj, honkerid, u.UserID)
2186 if err != nil {
2187 elog.Printf("update honker err: %s", err)
2188 return nil
2189 }
2190 return h
2191 }
2192
2193 if url == "" {
2194 http.Error(w, "subscribing to nothing?", http.StatusInternalServerError)
2195 return nil
2196 }
2197
2198 flavor := "presub"
2199 if peep == "peep" {
2200 flavor = "peep"
2201 }
2202
2203 var err error
2204 honkerid, err = savehonker(user, url, name, flavor, combos, mj)
2205 if err != nil {
2206 http.Error(w, "had some trouble with that: "+err.Error(), http.StatusInternalServerError)
2207 return nil
2208 }
2209 if flavor == "presub" {
2210 followyou(user, honkerid, false)
2211 }
2212 h.ID = honkerid
2213 return h
2214}
2215
2216func hfcspage(w http.ResponseWriter, r *http.Request) {
2217 userinfo := login.GetUserInfo(r)
2218
2219 filters := getfilters(userinfo.UserID, filtAny)
2220
2221 templinfo := getInfo(r)
2222 templinfo["Filters"] = filters
2223 templinfo["FilterCSRF"] = login.GetCSRF("filter", r)
2224 err := readviews.Execute(w, "hfcs.html", templinfo)
2225 if err != nil {
2226 elog.Print(err)
2227 }
2228}
2229
2230func savehfcs(w http.ResponseWriter, r *http.Request) {
2231 userinfo := login.GetUserInfo(r)
2232 itsok := r.FormValue("itsok")
2233 if itsok == "iforgiveyou" {
2234 hfcsid, _ := strconv.ParseInt(r.FormValue("hfcsid"), 10, 0)
2235 _, err := stmtDeleteFilter.Exec(userinfo.UserID, hfcsid)
2236 if err != nil {
2237 elog.Printf("error deleting filter: %s", err)
2238 }
2239 filtInvalidator.Clear(userinfo.UserID)
2240 http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
2241 return
2242 }
2243
2244 filt := new(Filter)
2245 filt.Name = strings.TrimSpace(r.FormValue("name"))
2246 filt.Date = time.Now().UTC()
2247 filt.Actor = strings.TrimSpace(r.FormValue("actor"))
2248 filt.IncludeAudience = r.FormValue("incaud") == "yes"
2249 filt.Text = strings.TrimSpace(r.FormValue("filttext"))
2250 filt.IsReply = r.FormValue("isreply") == "yes"
2251 filt.IsAnnounce = r.FormValue("isannounce") == "yes"
2252 filt.AnnounceOf = strings.TrimSpace(r.FormValue("announceof"))
2253 filt.Reject = r.FormValue("doreject") == "yes"
2254 filt.SkipMedia = r.FormValue("doskipmedia") == "yes"
2255 filt.Hide = r.FormValue("dohide") == "yes"
2256 filt.Collapse = r.FormValue("docollapse") == "yes"
2257 filt.Rewrite = strings.TrimSpace(r.FormValue("filtrewrite"))
2258 filt.Replace = strings.TrimSpace(r.FormValue("filtreplace"))
2259 if dur := parseDuration(r.FormValue("filtduration")); dur > 0 {
2260 filt.Expiration = time.Now().UTC().Add(dur)
2261 }
2262 filt.Notes = strings.TrimSpace(r.FormValue("filtnotes"))
2263
2264 if filt.Actor == "" && filt.Text == "" && !filt.IsAnnounce {
2265 ilog.Printf("blank filter")
2266 http.Error(w, "can't save a blank filter", http.StatusInternalServerError)
2267 return
2268 }
2269
2270 j, err := jsonify(filt)
2271 if err == nil {
2272 _, err = stmtSaveFilter.Exec(userinfo.UserID, j)
2273 }
2274 if err != nil {
2275 elog.Printf("error saving filter: %s", err)
2276 }
2277
2278 filtInvalidator.Clear(userinfo.UserID)
2279 http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
2280}
2281
2282func accountpage(w http.ResponseWriter, r *http.Request) {
2283 u := login.GetUserInfo(r)
2284 user, _ := butwhatabout(u.Username)
2285 templinfo := getInfo(r)
2286 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
2287 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
2288 templinfo["User"] = user
2289 about := user.About
2290 if ava := user.Options.Avatar; ava != "" {
2291 about += "\n\navatar: " + ava[strings.LastIndexByte(ava, '/')+1:]
2292 }
2293 if ban := user.Options.Banner; ban != "" {
2294 about += "\n\nbanner: " + ban[strings.LastIndexByte(ban, '/')+1:]
2295 }
2296 templinfo["WhatAbout"] = about
2297 err := readviews.Execute(w, "account.html", templinfo)
2298 if err != nil {
2299 elog.Print(err)
2300 }
2301}
2302
2303func dochpass(w http.ResponseWriter, r *http.Request) {
2304 err := login.ChangePassword(w, r)
2305 if err != nil {
2306 elog.Printf("error changing password: %s", err)
2307 }
2308 http.Redirect(w, r, "/account", http.StatusSeeOther)
2309}
2310
2311var oldfingers = cache.New(cache.Options{Filler: func(orig string) ([]byte, bool) {
2312 if strings.HasPrefix(orig, "acct:") {
2313 orig = orig[5:]
2314 }
2315 name := orig
2316 idx := strings.LastIndexByte(name, '/')
2317 if idx != -1 {
2318 name = name[idx+1:]
2319 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
2320 ilog.Printf("foreign request rejected")
2321 name = ""
2322 }
2323 } else {
2324 idx = strings.IndexByte(name, '@')
2325 if idx != -1 {
2326 name = name[:idx]
2327 if !(name+"@"+serverName == orig || name+"@"+masqName == orig) {
2328 ilog.Printf("foreign request rejected")
2329 name = ""
2330 }
2331 }
2332 }
2333 user, err := butwhatabout(name)
2334 if err != nil {
2335 return nil, false
2336 }
2337
2338 j := junk.New()
2339 pretty := fmt.Sprintf("https://%s/@%s", serverName, name)
2340 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, masqName)
2341 j["aliases"] = []string{pretty, user.URL}
2342 l := junk.New()
2343 l["rel"] = "self"
2344 l["type"] = `application/activity+json`
2345 l["href"] = user.URL
2346 j["links"] = []junk.Junk{l}
2347 l2 := junk.New()
2348 l2["rel"] = "http://webfinger.net/rel/profile-page"
2349 l2["type"] = "text/html"
2350 l2["href"] = pretty
2351 j["links"] = []junk.Junk{l, l2}
2352
2353 return j.ToBytes(), true
2354}})
2355
2356func fingerlicker(w http.ResponseWriter, r *http.Request) {
2357 orig := r.FormValue("resource")
2358
2359 dlog.Printf("finger lick: %s", orig)
2360
2361 var j []byte
2362 ok := oldfingers.Get(orig, &j)
2363 if ok {
2364 w.Header().Set("Content-Type", "application/jrd+json")
2365 w.Write(j)
2366 } else {
2367 http.NotFound(w, r)
2368 }
2369}
2370
2371func knowninformation(w http.ResponseWriter, r *http.Request) {
2372 j := junk.New()
2373 l := junk.New()
2374
2375 l["rel"] = `http://nodeinfo.diaspora.software/ns/schema/2.0`
2376 l["href"] = fmt.Sprintf("https://%s/nodeinfo/2.0", serverName)
2377 j["links"] = []junk.Junk{l}
2378
2379 w.Header().Set("Content-Type", "application/json")
2380 j.Write(w)
2381}
2382
2383func actualinformation(w http.ResponseWriter, r *http.Request) {
2384 j := junk.New()
2385
2386 soft := junk.New()
2387 soft["name"] = "honk"
2388 soft["version"] = softwareVersion
2389
2390 services := junk.New()
2391 services["inbound"] = []string{}
2392 services["outbound"] = []string{"rss2.0"}
2393
2394 users := junk.New()
2395 users["total"] = getusercount()
2396 users["activeHalfyear"] = getactiveusercount(6)
2397 users["activeMonth"] = getactiveusercount(1)
2398
2399 usage := junk.New()
2400 usage["users"] = users
2401 usage["localPosts"] = getlocalhonkcount()
2402
2403 j["version"] = "2.0"
2404 j["protocols"] = []string{"activitypub"}
2405 j["software"] = soft
2406 j["services"] = services
2407 j["openRegistrations"] = false
2408 j["usage"] = usage
2409
2410 w.Header().Set("Content-Type", "application/json")
2411 j.Write(w)
2412}
2413
2414func somedays() string {
2415 secs := 432000 + notrand.Int63n(432000)
2416 return fmt.Sprintf("%d", secs)
2417}
2418
2419func isurl(s string) bool {
2420 u, err := url.Parse(s)
2421 return err == nil && u.Scheme != "" && u.Host != ""
2422}
2423
2424func avatateautogen(r *http.Request) []byte {
2425 n := r.FormValue("a")
2426 return genAvatar(n)
2427}
2428
2429func avatate(w http.ResponseWriter, r *http.Request) {
2430 if develMode {
2431 loadAvatarColors()
2432 }
2433 var a []byte
2434 n := r.FormValue("a")
2435
2436 if isurl(n) {
2437 uinfo := login.GetUserInfo(r)
2438 if uinfo != nil {
2439 j, err := GetJunkFast(uinfo.UserID, n)
2440 if err != nil {
2441 dlog.Println("avatating: getting junk:", err)
2442 a = avatateautogen(r)
2443 }
2444 pfpurl, _ := j.GetString("icon", "url")
2445 res, err := http.Get(pfpurl)
2446 if err != nil {
2447 dlog.Println("avatating: getting pfp url:", err)
2448 a = avatateautogen(r)
2449 } else {
2450 defer res.Body.Close()
2451
2452 pfpbytes, err := io.ReadAll(res.Body)
2453 if err != nil {
2454 dlog.Println("avatating: bruh shits clapped:", err)
2455 a = avatateautogen(r)
2456 }
2457 a = pfpbytes
2458 }
2459 } else {
2460 a = avatateautogen(r)
2461 }
2462 } else {
2463 a = avatateautogen(r)
2464 }
2465
2466 if !develMode {
2467 w.Header().Set("Cache-Control", "max-age="+somedays())
2468 }
2469
2470 w.Write(a)
2471}
2472
2473func serveviewasset(w http.ResponseWriter, r *http.Request) {
2474 serveasset(w, r, viewDir)
2475}
2476func servedataasset(w http.ResponseWriter, r *http.Request) {
2477 if r.URL.Path == "/favicon.ico" {
2478 r.URL.Path = "/icon.png"
2479 }
2480 serveasset(w, r, dataDir)
2481}
2482
2483func serveasset(w http.ResponseWriter, r *http.Request, basedir string) {
2484 if !develMode {
2485 w.Header().Set("Cache-Control", "max-age=7776000")
2486 }
2487 http.ServeFile(w, r, basedir+"/views"+r.URL.Path)
2488}
2489func servehelp(w http.ResponseWriter, r *http.Request) {
2490 name := mux.Vars(r)["name"]
2491 if !develMode {
2492 w.Header().Set("Cache-Control", "max-age=3600")
2493 }
2494 http.ServeFile(w, r, viewDir+"/docs/"+name)
2495}
2496func servehtml(w http.ResponseWriter, r *http.Request) {
2497 u := login.GetUserInfo(r)
2498 templinfo := getInfo(r)
2499 templinfo["AboutMsg"] = aboutMsg
2500 templinfo["LoginMsg"] = loginMsg
2501 templinfo["HonkVersion"] = softwareVersion
2502 if r.URL.Path == "/about" {
2503 templinfo["Sensors"] = getSensors()
2504 }
2505 if u == nil && !develMode {
2506 w.Header().Set("Cache-Control", "max-age=60")
2507 }
2508 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
2509 if err != nil {
2510 elog.Print(err)
2511 }
2512}
2513func serveemu(w http.ResponseWriter, r *http.Request) {
2514 emu := mux.Vars(r)["emu"]
2515
2516 w.Header().Set("Cache-Control", "max-age="+somedays())
2517 http.ServeFile(w, r, dataDir+"/emus/"+emu)
2518}
2519func servememe(w http.ResponseWriter, r *http.Request) {
2520 meme := mux.Vars(r)["meme"]
2521
2522 w.Header().Set("Cache-Control", "max-age="+somedays())
2523 http.ServeFile(w, r, dataDir+"/memes/"+meme)
2524}
2525
2526func servefile(w http.ResponseWriter, r *http.Request) {
2527 xid := mux.Vars(r)["xid"]
2528 var media string
2529 var data []byte
2530 row := stmtGetFileData.QueryRow(xid)
2531 err := row.Scan(&media, &data)
2532 if err != nil {
2533 elog.Printf("error loading file: %s", err)
2534 http.NotFound(w, r)
2535 return
2536 }
2537 w.Header().Set("Content-Type", media)
2538 w.Header().Set("X-Content-Type-Options", "nosniff")
2539 w.Header().Set("Cache-Control", "max-age="+somedays())
2540 w.Write(data)
2541}
2542
2543func nomoroboto(w http.ResponseWriter, r *http.Request) {
2544 io.WriteString(w, "User-agent: *\n")
2545 io.WriteString(w, "Disallow: /a\n")
2546 io.WriteString(w, "Disallow: /d/\n")
2547 io.WriteString(w, "Disallow: /meme/\n")
2548 io.WriteString(w, "Disallow: /o\n")
2549 io.WriteString(w, "Disallow: /o/\n")
2550 io.WriteString(w, "Disallow: /help/\n")
2551 for _, u := range allusers() {
2552 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
2553 }
2554}
2555
2556type Hydration struct {
2557 Tophid int64
2558 Srvmsg template.HTML
2559 Honks string
2560 MeCount int64
2561 ChatCount int64
2562}
2563
2564func webhydra(w http.ResponseWriter, r *http.Request) {
2565 u := login.GetUserInfo(r)
2566 userid := u.UserID
2567 templinfo := getInfo(r)
2568 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
2569 page := r.FormValue("page")
2570
2571 wanted, _ := strconv.ParseInt(r.FormValue("tophid"), 10, 0)
2572
2573 var hydra Hydration
2574
2575 var honks []*Honk
2576 switch page {
2577 case "atme":
2578 honks = gethonksforme(userid, wanted)
2579 honks = osmosis(honks, userid, false)
2580 menewnone(userid)
2581 hydra.Srvmsg = "at me!"
2582 case "longago":
2583 honks = gethonksfromlongago(userid, wanted)
2584 honks = osmosis(honks, userid, false)
2585 hydra.Srvmsg = "from long ago"
2586 case "home":
2587 honks = gethonksforuser(userid, wanted)
2588 honks = osmosis(honks, userid, true)
2589 hydra.Srvmsg = serverMsg
2590 case "first":
2591 honks = gethonksforuserfirstclass(userid, wanted)
2592 honks = osmosis(honks, userid, true)
2593 hydra.Srvmsg = "first class only"
2594 case "saved":
2595 honks = getsavedhonks(userid, wanted)
2596 templinfo["PageName"] = "saved"
2597 hydra.Srvmsg = "saved honks"
2598 case "combo":
2599 c := r.FormValue("c")
2600 honks = gethonksbycombo(userid, c, wanted)
2601 honks = osmosis(honks, userid, false)
2602 hydra.Srvmsg = templates.Sprintf("honks by combo: %s", c)
2603 case "convoy":
2604 c := r.FormValue("c")
2605 honks = gethonksbyconvoy(userid, c, wanted)
2606 honks = osmosis(honks, userid, false)
2607 honks = threadsort(honks)
2608 reversehonks(honks)
2609 hydra.Srvmsg = templates.Sprintf("honks in convoy: %s", c)
2610 case "honker":
2611 xid := r.FormValue("xid")
2612 honks = gethonksbyxonker(userid, xid, wanted)
2613 miniform := templates.Sprintf(`<form action="/submithonker" method="POST">
2614 <input type="hidden" name="CSRF" value="%s">
2615 <input type="hidden" name="url" value="%s">
2616 <button tabindex=1 name="add honker" value="add honker">add honker</button>
2617 </form>`, login.GetCSRF("submithonker", r), xid)
2618 msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>%s`, xid, xid, miniform)
2619 hydra.Srvmsg = msg
2620 case "user":
2621 uname := r.FormValue("uname")
2622 honks = gethonksbyuser(uname, u != nil && u.Username == uname, wanted)
2623 hydra.Srvmsg = templates.Sprintf("honks by user: %s", uname)
2624 default:
2625 http.NotFound(w, r)
2626 }
2627
2628 if len(honks) > 0 {
2629 hydra.Tophid = honks[0].ID
2630 } else {
2631 hydra.Tophid = wanted
2632 }
2633 reverbolate(userid, honks)
2634
2635 user, _ := butwhatabout(u.Username)
2636
2637 var buf strings.Builder
2638 templinfo["Honks"] = honks
2639 templinfo["MapLink"] = getmaplink(u)
2640 templinfo["User"], _ = butwhatabout(u.Username)
2641 err := readviews.Execute(&buf, "honkfrags.html", templinfo)
2642 if err != nil {
2643 elog.Printf("frag error: %s", err)
2644 return
2645 }
2646 hydra.Honks = buf.String()
2647 hydra.MeCount = user.Options.MeCount
2648 hydra.ChatCount = user.Options.ChatCount
2649 w.Header().Set("Content-Type", "application/json")
2650 j, _ := jsonify(&hydra)
2651 io.WriteString(w, j)
2652}
2653
2654var honkline = make(chan bool)
2655
2656func honkhonkline() {
2657 for {
2658 select {
2659 case honkline <- true:
2660 default:
2661 return
2662 }
2663 }
2664}
2665
2666func apihandler(w http.ResponseWriter, r *http.Request) {
2667 u := login.GetUserInfo(r)
2668 userid := u.UserID
2669 action := r.FormValue("action")
2670 wait, _ := strconv.ParseInt(r.FormValue("wait"), 10, 0)
2671 dlog.Printf("api request '%s' on behalf of %s", action, u.Username)
2672 switch action {
2673 case "honk":
2674 h := submithonk(w, r)
2675 if h == nil {
2676 return
2677 }
2678
2679 fmt.Fprintf(w, "%s", h.XID)
2680 case "donk":
2681 donks, err := submitdonk(w, r)
2682 if err != nil {
2683 http.Error(w, err.Error(), http.StatusBadRequest)
2684 return
2685 }
2686 if len(donks) == 0 {
2687 http.Error(w, "missing donk", http.StatusBadRequest)
2688 return
2689 }
2690 d := donks[0]
2691 donkxid := fmt.Sprintf("%s:%d", d.XID, d.FileID)
2692 w.Write([]byte(donkxid))
2693 case "zonkit":
2694 zonkit(w, r)
2695 case "gethonks":
2696 var honks []*Honk
2697 wanted, _ := strconv.ParseInt(r.FormValue("after"), 10, 0)
2698 page := r.FormValue("page")
2699 var waitchan <-chan time.Time
2700 requery:
2701 switch page {
2702 case "atme":
2703 honks = gethonksforme(userid, wanted)
2704 honks = osmosis(honks, userid, false)
2705 menewnone(userid)
2706 case "longago":
2707 honks = gethonksfromlongago(userid, wanted)
2708 honks = osmosis(honks, userid, false)
2709 case "home":
2710 honks = gethonksforuser(userid, wanted)
2711 honks = osmosis(honks, userid, true)
2712 case "myhonks":
2713 honks = gethonksbyuser(u.Username, true, wanted)
2714 honks = osmosis(honks, userid, true)
2715 default:
2716 http.Error(w, "unknown page", http.StatusNotFound)
2717 return
2718 }
2719 if len(honks) == 0 && wait > 0 {
2720 if waitchan == nil {
2721 waitchan = time.After(time.Duration(wait) * time.Second)
2722 }
2723 select {
2724 case <-honkline:
2725 goto requery
2726 case <-waitchan:
2727 }
2728 }
2729 reverbolate(userid, honks)
2730 j := junk.New()
2731 j["honks"] = honks
2732 j.Write(w)
2733 case "sendactivity":
2734 user, _ := butwhatabout(u.Username)
2735 public := r.FormValue("public") == "1"
2736 rcpts := boxuprcpts(user, r.Form["rcpt"], public)
2737 msg := []byte(r.FormValue("msg"))
2738 for rcpt := range rcpts {
2739 go deliverate(userid, rcpt, msg)
2740 }
2741 case "gethonkers":
2742 j := junk.New()
2743 j["honkers"] = gethonkers(u.UserID)
2744 j.Write(w)
2745 case "savehonker":
2746 h := submithonker(w, r)
2747 if h == nil {
2748 return
2749 }
2750 fmt.Fprintf(w, "%d", h.ID)
2751 default:
2752 http.Error(w, "unknown action", http.StatusNotFound)
2753 return
2754 }
2755}
2756
2757func fiveoh(w http.ResponseWriter, r *http.Request) {
2758 if !develMode {
2759 return
2760 }
2761 fd, err := os.OpenFile("violations.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
2762 if err != nil {
2763 elog.Printf("error opening violations! %s", err)
2764 return
2765 }
2766 defer fd.Close()
2767 io.Copy(fd, r.Body)
2768 fd.WriteString("\n")
2769}
2770
2771var endoftheworld = make(chan bool)
2772var readyalready = make(chan bool)
2773var workinprogress = 0
2774
2775func enditall() {
2776 sig := make(chan os.Signal, 1)
2777 signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
2778 <-sig
2779 ilog.Printf("stopping...")
2780 for i := 0; i < workinprogress; i++ {
2781 endoftheworld <- true
2782 }
2783 ilog.Printf("waiting...")
2784 for i := 0; i < workinprogress; i++ {
2785 <-readyalready
2786 }
2787 ilog.Printf("apocalypse")
2788 os.Exit(0)
2789}
2790
2791var preservehooks []func()
2792
2793func bgmonitor() {
2794 for {
2795 when := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
2796 _, err := stmtDeleteOldXonkers.Exec("pubkey", when)
2797 if err != nil {
2798 elog.Printf("error deleting old xonkers: %s", err)
2799 }
2800 zaggies.Flush()
2801 time.Sleep(50 * time.Minute)
2802 }
2803}
2804
2805func emuinit() {
2806 var emunames []string
2807 dir, err := os.Open(dataDir + "/emus")
2808 if err == nil {
2809 emunames, _ = dir.Readdirnames(0)
2810 dir.Close()
2811 }
2812 for _, e := range emunames {
2813 if len(e) <= 4 {
2814 continue
2815 }
2816 ext := e[len(e)-4:]
2817 emu := Emu{
2818 ID: fmt.Sprintf("/emu/%s", e),
2819 Name: e[:len(e)-4],
2820 Type: "image/" + ext[1:],
2821 }
2822 allemus = append(allemus, emu)
2823 }
2824 sort.Slice(allemus, func(i, j int) bool {
2825 return allemus[i].Name < allemus[j].Name
2826 })
2827}
2828
2829func redirectPretty(w http.ResponseWriter, r *http.Request) {
2830 last := path.Base(r.URL.Path)
2831 name := mux.Vars(r)["name"]
2832 aturl := "/@" + name
2833
2834 if last == name {
2835 last = ""
2836 }
2837 http.Redirect(w, r, path.Join(aturl, last), http.StatusMovedPermanently)
2838}
2839
2840func serve() {
2841 db := opendatabase()
2842 login.Init(login.InitArgs{Db: db, Logger: ilog, Insecure: develMode, SameSiteStrict: !develMode})
2843
2844 listener, err := openListener()
2845 if err != nil {
2846 elog.Fatal(err)
2847 }
2848 runBackendServer()
2849 go enditall()
2850 go redeliverator()
2851 go tracker()
2852 go bgmonitor()
2853 go qotd()
2854 loadLingo()
2855 emuinit()
2856
2857 readviews = templates.Load(develMode,
2858 viewDir+"/views/honkpage.html",
2859 viewDir+"/views/honkfrags.html",
2860 viewDir+"/views/honkers.html",
2861 viewDir+"/views/chatter.html",
2862 viewDir+"/views/hfcs.html",
2863 viewDir+"/views/combos.html",
2864 viewDir+"/views/honkform.html",
2865 viewDir+"/views/honk.html",
2866 viewDir+"/views/account.html",
2867 viewDir+"/views/about.html",
2868 viewDir+"/views/funzone.html",
2869 viewDir+"/views/login.html",
2870 viewDir+"/views/xzone.html",
2871 viewDir+"/views/msg.html",
2872 viewDir+"/views/header.html",
2873 viewDir+"/views/onts.html",
2874 viewDir+"/views/emus.html",
2875 viewDir+"/views/oauthlogin.html",
2876 viewDir+"/views/honkpage.js",
2877 )
2878 if !develMode {
2879 assets := []string{
2880 viewDir + "/views/style.css",
2881 dataDir + "/views/local.css",
2882 viewDir + "/views/honkpage.js",
2883 viewDir + "/views/misc.js",
2884 dataDir + "/views/local.js",
2885 viewDir + "/views/manifest.webmanifest",
2886 viewDir + "/views/sw.js",
2887 }
2888 for _, s := range assets {
2889 savedassetparams[s] = getassetparam(s)
2890 }
2891 loadAvatarColors()
2892 }
2893
2894 for _, h := range preservehooks {
2895 h()
2896 }
2897
2898 mux := mux.NewRouter()
2899 mux.Use(login.Checker)
2900
2901 mux.Handle("/api", login.TokenRequired(http.HandlerFunc(apihandler)))
2902
2903 posters := mux.Methods("POST").Subrouter()
2904 getters := mux.Methods("GET").Subrouter()
2905
2906 getters.HandleFunc("/", homepage)
2907 getters.HandleFunc("/home", homepage)
2908 getters.HandleFunc("/front", homepage)
2909 getters.HandleFunc("/events", homepage)
2910 getters.HandleFunc("/robots.txt", nomoroboto)
2911 getters.HandleFunc("/rss", showrss)
2912 getters.HandleFunc("/@{name:[\\pL[:digit:]]+}", showuser)
2913 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}", redirectPretty)
2914 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/"+honkSep+"/{xid:[\\pL[:digit:]]+}", showonehonk)
2915 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/rss", showrss)
2916 posters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/inbox", inbox)
2917 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/outbox", outbox)
2918 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/followers", emptiness)
2919 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/following", emptiness)
2920 getters.HandleFunc("/a", avatate)
2921 getters.HandleFunc("/o", thelistingoftheontologies)
2922 getters.HandleFunc("/o/{name:.+}", showontology)
2923 getters.HandleFunc("/d/{xid:[\\pL[:digit:].]+}", servefile)
2924 getters.HandleFunc("/emu/{emu:[^.]*[^/]+}", serveemu)
2925 getters.HandleFunc("/meme/{meme:[^.]*[^/]+}", servememe)
2926 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
2927 getters.HandleFunc("/.well-known/nodeinfo", knowninformation)
2928 getters.HandleFunc("/nodeinfo/2.0", actualinformation)
2929
2930 getters.HandleFunc("/flag/{code:.+}", showflag)
2931
2932 getters.HandleFunc("/server", serveractor)
2933 posters.HandleFunc("/server/inbox", serverinbox)
2934 posters.HandleFunc("/inbox", serverinbox)
2935
2936 posters.HandleFunc("/csp-violation", fiveoh)
2937
2938 getters.HandleFunc("/style.css", serveviewasset)
2939 getters.HandleFunc("/sw.js", serveviewasset)
2940 getters.HandleFunc("/honkpage.js", serveviewasset)
2941 getters.HandleFunc("/misc.js", serveviewasset)
2942 getters.HandleFunc("/local.css", servedataasset)
2943 getters.HandleFunc("/local.js", servedataasset)
2944 getters.HandleFunc("/icon.png", servedataasset)
2945 getters.HandleFunc("/favicon.ico", servedataasset)
2946 getters.HandleFunc("/manifest.webmanifest", serveviewasset)
2947
2948 getters.HandleFunc("/about", servehtml)
2949 getters.HandleFunc("/login", servehtml)
2950 posters.HandleFunc("/dologin", login.LoginFunc)
2951 getters.HandleFunc("/logout", login.LogoutFunc)
2952 getters.HandleFunc("/help/{name:[\\pL[:digit:]_.-]+}", servehelp)
2953
2954 loggedin := mux.NewRoute().Subrouter()
2955 loggedin.Use(login.Required)
2956 loggedin.HandleFunc("/first", homepage)
2957 loggedin.HandleFunc("/chatter", showchatter)
2958 loggedin.Handle("/sendchonk", login.CSRFWrap("sendchonk", http.HandlerFunc(submitchonk)))
2959 loggedin.HandleFunc("/saved", homepage)
2960 loggedin.HandleFunc("/account", accountpage)
2961 loggedin.HandleFunc("/funzone", showfunzone)
2962 loggedin.HandleFunc("/chpass", dochpass)
2963 loggedin.HandleFunc("/atme", homepage)
2964 loggedin.HandleFunc("/longago", homepage)
2965 loggedin.HandleFunc("/hfcs", hfcspage)
2966 loggedin.HandleFunc("/xzone", xzone)
2967 loggedin.HandleFunc("/newhonk", newhonkpage)
2968 loggedin.HandleFunc("/edit", edithonkpage)
2969 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(websubmithonk)))
2970 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitbonk)))
2971 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
2972 loggedin.Handle("/savehfcs", login.CSRFWrap("filter", http.HandlerFunc(savehfcs)))
2973 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
2974 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
2975 loggedin.HandleFunc("/honkers", showhonkers)
2976 loggedin.HandleFunc("/h/{name:[\\pL[:digit:]_.-]+}", showhonker)
2977 loggedin.HandleFunc("/h", showhonker)
2978 loggedin.HandleFunc("/c/{name:[\\pL[:digit:]_.-]+}", showcombo)
2979 loggedin.HandleFunc("/c", showcombos)
2980 loggedin.HandleFunc("/t", showconvoy)
2981 loggedin.HandleFunc("/q", showsearch)
2982 loggedin.HandleFunc("/hydra", webhydra)
2983 loggedin.HandleFunc("/emus", showemus)
2984 loggedin.Handle("/submithonker", login.CSRFWrap("submithonker", http.HandlerFunc(websubmithonker)))
2985
2986 // mastoshit
2987 mastopost := mux.Methods("POST").Subrouter()
2988 mastoget := mux.Methods("GET").Subrouter()
2989 handlers.LoggingHandler(os.Stdout, mastopost)
2990 handlers.LoggingHandler(os.Stdout, mastoget)
2991
2992 mastoget.HandleFunc("/oauth/authorize", showoauthlogin)
2993 mastopost.HandleFunc("/oauth/authorize", oauthorize)
2994 mastopost.HandleFunc("/oauth/token", oauthtoken)
2995 mastoget.HandleFunc("/api/v1/instance", instance)
2996 mastopost.HandleFunc("/api/v1/apps", apiapps)
2997
2998 err = http.Serve(listener, mux)
2999 if err != nil {
3000 elog.Fatal(err)
3001 }
3002}