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