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