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