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