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