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