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