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