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 j, _ := jonkjonk(user, h)
514 jonks = append(jonks, j)
515 }
516
517 j := junk.New()
518 j["@context"] = itiswhatitis
519 j["id"] = user.URL + "/outbox"
520 j["type"] = "OrderedCollection"
521 j["totalItems"] = len(jonks)
522 j["orderedItems"] = jonks
523
524 w.Header().Set("Content-Type", theonetruename)
525 j.Write(w)
526}
527
528func emptiness(w http.ResponseWriter, r *http.Request) {
529 name := mux.Vars(r)["name"]
530 user, err := butwhatabout(name)
531 if err != nil {
532 http.NotFound(w, r)
533 return
534 }
535 colname := "/followers"
536 if strings.HasSuffix(r.URL.Path, "/following") {
537 colname = "/following"
538 }
539 j := junk.New()
540 j["@context"] = itiswhatitis
541 j["id"] = user.URL + colname
542 j["type"] = "OrderedCollection"
543 j["totalItems"] = 0
544 j["orderedItems"] = []junk.Junk{}
545
546 w.Header().Set("Content-Type", theonetruename)
547 j.Write(w)
548}
549
550func showuser(w http.ResponseWriter, r *http.Request) {
551 name := mux.Vars(r)["name"]
552 user, err := butwhatabout(name)
553 if err != nil {
554 log.Printf("user not found %s: %s", name, err)
555 http.NotFound(w, r)
556 return
557 }
558 if friendorfoe(r.Header.Get("Accept")) {
559 j := asjonker(user)
560 w.Header().Set("Content-Type", theonetruename)
561 j.Write(w)
562 return
563 }
564 u := login.GetUserInfo(r)
565 honks := gethonksbyuser(name, u != nil && u.Username == name)
566 honkpage(w, r, u, user, honks, "")
567}
568
569func showhonker(w http.ResponseWriter, r *http.Request) {
570 u := login.GetUserInfo(r)
571 name := mux.Vars(r)["name"]
572 var honks []*Honk
573 if name == "" {
574 name = r.FormValue("xid")
575 honks = gethonksbyxonker(u.UserID, name)
576 } else {
577 honks = gethonksbyhonker(u.UserID, name)
578 }
579 name = html.EscapeString(name)
580 msg := fmt.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, name, name)
581 honkpage(w, r, u, nil, honks, template.HTML(msg))
582}
583
584func showcombo(w http.ResponseWriter, r *http.Request) {
585 name := mux.Vars(r)["name"]
586 u := login.GetUserInfo(r)
587 honks := gethonksbycombo(u.UserID, name)
588 honks = osmosis(honks, u.UserID)
589 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks by combo: "+name)))
590}
591func showconvoy(w http.ResponseWriter, r *http.Request) {
592 c := r.FormValue("c")
593 u := login.GetUserInfo(r)
594 honks := gethonksbyconvoy(u.UserID, c)
595 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks in convoy: "+c)))
596}
597
598func showhonk(w http.ResponseWriter, r *http.Request) {
599 name := mux.Vars(r)["name"]
600 user, err := butwhatabout(name)
601 if err != nil {
602 http.NotFound(w, r)
603 return
604 }
605 if stealthed(r) {
606 http.NotFound(w, r)
607 return
608 }
609
610 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
611 h := getxonk(user.ID, xid)
612 if h == nil {
613 http.NotFound(w, r)
614 return
615 }
616 u := login.GetUserInfo(r)
617 if u != nil && u.UserID != user.ID {
618 u = nil
619 }
620 if !h.Public {
621 if u == nil {
622 http.NotFound(w, r)
623 return
624
625 }
626 honkpage(w, r, u, nil, []*Honk{h}, "one honk maybe more")
627 return
628 }
629 if friendorfoe(r.Header.Get("Accept")) {
630 donksforhonks([]*Honk{h})
631 _, j := jonkjonk(user, h)
632 j["@context"] = itiswhatitis
633 w.Header().Set("Content-Type", theonetruename)
634 j.Write(w)
635 return
636 }
637 honks := gethonksbyconvoy(-1, h.Convoy)
638 honkpage(w, r, u, nil, honks, "one honk maybe more")
639}
640
641func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
642 honks []*Honk, infomsg template.HTML) {
643 templinfo := getInfo(r)
644 var userid int64 = -1
645 if u != nil {
646 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
647 userid = u.UserID
648 }
649 if u == nil {
650 w.Header().Set("Cache-Control", "max-age=60")
651 }
652 reverbolate(userid, honks)
653 if user != nil {
654 filt := htfilter.New()
655 templinfo["Name"] = user.Name
656 whatabout := user.About
657 whatabout = obfusbreak(user.About)
658 templinfo["WhatAbout"], _ = filt.String(whatabout)
659 }
660 templinfo["Honks"] = honks
661 templinfo["ServerMessage"] = infomsg
662 err := readviews.Execute(w, "honkpage.html", templinfo)
663 if err != nil {
664 log.Print(err)
665 }
666}
667
668func saveuser(w http.ResponseWriter, r *http.Request) {
669 whatabout := r.FormValue("whatabout")
670 u := login.GetUserInfo(r)
671 db := opendatabase()
672 options := ""
673 if r.FormValue("skinny") == "skinny" {
674 options += " skinny "
675 }
676 _, err := db.Exec("update users set about = ?, options = ? where username = ?", whatabout, options, u.Username)
677 if err != nil {
678 log.Printf("error bouting what: %s", err)
679 }
680
681 http.Redirect(w, r, "/account", http.StatusSeeOther)
682}
683
684func gethonkers(userid int64) []*Honker {
685 rows, err := stmtHonkers.Query(userid)
686 if err != nil {
687 log.Printf("error querying honkers: %s", err)
688 return nil
689 }
690 defer rows.Close()
691 var honkers []*Honker
692 for rows.Next() {
693 var f Honker
694 var combos string
695 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
696 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
697 if err != nil {
698 log.Printf("error scanning honker: %s", err)
699 return nil
700 }
701 honkers = append(honkers, &f)
702 }
703 return honkers
704}
705
706func getdubs(userid int64) []*Honker {
707 rows, err := stmtDubbers.Query(userid)
708 if err != nil {
709 log.Printf("error querying dubs: %s", err)
710 return nil
711 }
712 defer rows.Close()
713 var honkers []*Honker
714 for rows.Next() {
715 var f Honker
716 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
717 if err != nil {
718 log.Printf("error scanning honker: %s", err)
719 return nil
720 }
721 honkers = append(honkers, &f)
722 }
723 return honkers
724}
725
726func allusers() []login.UserInfo {
727 var users []login.UserInfo
728 rows, _ := opendatabase().Query("select userid, username from users")
729 defer rows.Close()
730 for rows.Next() {
731 var u login.UserInfo
732 rows.Scan(&u.UserID, &u.Username)
733 users = append(users, u)
734 }
735 return users
736}
737
738func getxonk(userid int64, xid string) *Honk {
739 h := new(Honk)
740 var dt, aud string
741 row := stmtOneXonk.QueryRow(userid, xid)
742 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
743 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore)
744 if err != nil {
745 if err != sql.ErrNoRows {
746 log.Printf("error scanning xonk: %s", err)
747 }
748 return nil
749 }
750 h.Date, _ = time.Parse(dbtimeformat, dt)
751 h.Audience = strings.Split(aud, " ")
752 h.Public = !keepitquiet(h.Audience)
753 return h
754}
755
756func getpublichonks() []*Honk {
757 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
758 rows, err := stmtPublicHonks.Query(dt)
759 return getsomehonks(rows, err)
760}
761func gethonksbyuser(name string, includeprivate bool) []*Honk {
762 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
763 whofore := 2
764 if includeprivate {
765 whofore = 3
766 }
767 rows, err := stmtUserHonks.Query(whofore, name, dt)
768 return getsomehonks(rows, err)
769}
770func gethonksforuser(userid int64) []*Honk {
771 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
772 rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
773 return getsomehonks(rows, err)
774}
775func gethonksforme(userid int64) []*Honk {
776 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
777 rows, err := stmtHonksForMe.Query(userid, dt, userid)
778 return getsomehonks(rows, err)
779}
780func gethonksbyhonker(userid int64, honker string) []*Honk {
781 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
782 return getsomehonks(rows, err)
783}
784func gethonksbyxonker(userid int64, xonker string) []*Honk {
785 rows, err := stmtHonksByXonker.Query(userid, xonker, xonker, userid)
786 return getsomehonks(rows, err)
787}
788func gethonksbycombo(userid int64, combo string) []*Honk {
789 combo = "% " + combo + " %"
790 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
791 return getsomehonks(rows, err)
792}
793func gethonksbyconvoy(userid int64, convoy string) []*Honk {
794 rows, err := stmtHonksByConvoy.Query(userid, userid, convoy)
795 honks := getsomehonks(rows, err)
796 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
797 honks[i], honks[j] = honks[j], honks[i]
798 }
799 return honks
800}
801
802func getsomehonks(rows *sql.Rows, err error) []*Honk {
803 if err != nil {
804 log.Printf("error querying honks: %s", err)
805 return nil
806 }
807 defer rows.Close()
808 var honks []*Honk
809 for rows.Next() {
810 var h Honk
811 var dt, aud string
812 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker,
813 &h.XID, &h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore)
814 if err != nil {
815 log.Printf("error scanning honks: %s", err)
816 return nil
817 }
818 h.Date, _ = time.Parse(dbtimeformat, dt)
819 h.Audience = strings.Split(aud, " ")
820 h.Public = !keepitquiet(h.Audience)
821 honks = append(honks, &h)
822 }
823 rows.Close()
824 donksforhonks(honks)
825 return honks
826}
827
828func donksforhonks(honks []*Honk) {
829 db := opendatabase()
830 var ids []string
831 hmap := make(map[int64]*Honk)
832 for _, h := range honks {
833 ids = append(ids, fmt.Sprintf("%d", h.ID))
834 hmap[h.ID] = h
835 }
836 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, ","))
837 rows, err := db.Query(q)
838 if err != nil {
839 log.Printf("error querying donks: %s", err)
840 return
841 }
842 defer rows.Close()
843 for rows.Next() {
844 var hid int64
845 var d Donk
846 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media, &d.Local)
847 if err != nil {
848 log.Printf("error scanning donk: %s", err)
849 continue
850 }
851 h := hmap[hid]
852 h.Donks = append(h.Donks, &d)
853 }
854}
855
856func savebonk(w http.ResponseWriter, r *http.Request) {
857 xid := r.FormValue("xid")
858 userinfo := login.GetUserInfo(r)
859 user, _ := butwhatabout(userinfo.Username)
860
861 log.Printf("bonking %s", xid)
862
863 xonk := getxonk(userinfo.UserID, xid)
864 if xonk == nil {
865 return
866 }
867 if !xonk.Public {
868 return
869 }
870 donksforhonks([]*Honk{xonk})
871
872 oonker := xonk.Oonker
873 if oonker == "" {
874 oonker = xonk.Honker
875 }
876 dt := time.Now().UTC()
877 bonk := Honk{
878 UserID: userinfo.UserID,
879 Username: userinfo.Username,
880 What: "bonk",
881 Honker: user.URL,
882 XID: xonk.XID,
883 Date: dt,
884 Donks: xonk.Donks,
885 Convoy: xonk.Convoy,
886 Audience: []string{oonker, thewholeworld},
887 Public: true,
888 }
889
890 aud := strings.Join(bonk.Audience, " ")
891 whofore := 2
892 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
893 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
894 xonk.Precis, oonker)
895 if err != nil {
896 log.Printf("error saving bonk: %s", err)
897 return
898 }
899 bonk.ID, _ = res.LastInsertId()
900 for _, d := range bonk.Donks {
901 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
902 if err != nil {
903 log.Printf("err saving donk: %s", err)
904 return
905 }
906 }
907
908 go honkworldwide(user, &bonk)
909}
910
911func zonkit(w http.ResponseWriter, r *http.Request) {
912 wherefore := r.FormValue("wherefore")
913 what := r.FormValue("what")
914 switch wherefore {
915 case "zonk":
916 case "zonvoy":
917 }
918
919 log.Printf("zonking %s %s", wherefore, what)
920 userinfo := login.GetUserInfo(r)
921 if wherefore == "zonk" {
922 xonk := getxonk(userinfo.UserID, what)
923 if xonk != nil {
924 _, err := stmtZonkDonks.Exec(xonk.ID)
925 if err != nil {
926 log.Printf("error zonking: %s", err)
927 }
928 _, err = stmtZonkIt.Exec(userinfo.UserID, what)
929 if err != nil {
930 log.Printf("error zonking: %s", err)
931 }
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 go honkworldwide(user, &honk)
1153
1154 http.Redirect(w, r, xid, http.StatusSeeOther)
1155}
1156
1157func showhonkers(w http.ResponseWriter, r *http.Request) {
1158 userinfo := login.GetUserInfo(r)
1159 templinfo := getInfo(r)
1160 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1161 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1162 err := readviews.Execute(w, "honkers.html", templinfo)
1163 if err != nil {
1164 log.Print(err)
1165 }
1166}
1167
1168func showcombos(w http.ResponseWriter, r *http.Request) {
1169 userinfo := login.GetUserInfo(r)
1170 templinfo := getInfo(r)
1171 honkers := gethonkers(userinfo.UserID)
1172 var combos []string
1173 for _, h := range honkers {
1174 combos = append(combos, h.Combos...)
1175 }
1176 for i, c := range combos {
1177 if c == "-" {
1178 combos[i] = ""
1179 }
1180 }
1181 combos = oneofakind(combos)
1182 sort.Strings(combos)
1183 templinfo["Combos"] = combos
1184 err := readviews.Execute(w, "combos.html", templinfo)
1185 if err != nil {
1186 log.Print(err)
1187 }
1188}
1189
1190func savehonker(w http.ResponseWriter, r *http.Request) {
1191 u := login.GetUserInfo(r)
1192 name := r.FormValue("name")
1193 url := r.FormValue("url")
1194 peep := r.FormValue("peep")
1195 combos := r.FormValue("combos")
1196 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1197
1198 if honkerid > 0 {
1199 goodbye := r.FormValue("goodbye")
1200 if goodbye == "F" {
1201 db := opendatabase()
1202 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1203 honkerid, u.UserID)
1204 var xid string
1205 err := row.Scan(&xid)
1206 if err != nil {
1207 log.Printf("can't get honker xid: %s", err)
1208 return
1209 }
1210 log.Printf("unsubscribing from %s", xid)
1211 user, _ := butwhatabout(u.Username)
1212 go itakeitallback(user, xid)
1213 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1214 if err != nil {
1215 log.Printf("error updating honker: %s", err)
1216 return
1217 }
1218
1219 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1220 return
1221 }
1222 combos = " " + strings.TrimSpace(combos) + " "
1223 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1224 if err != nil {
1225 log.Printf("update honker err: %s", err)
1226 return
1227 }
1228 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1229 }
1230
1231 flavor := "presub"
1232 if peep == "peep" {
1233 flavor = "peep"
1234 }
1235 p := investigate(url)
1236 if p == nil {
1237 log.Printf("failed to investigate honker")
1238 return
1239 }
1240 url = p.XID
1241 if name == "" {
1242 name = p.Handle
1243 }
1244 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1245 if err != nil {
1246 log.Print(err)
1247 return
1248 }
1249 if flavor == "presub" {
1250 user, _ := butwhatabout(u.Username)
1251 go subsub(user, url)
1252 }
1253 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1254}
1255
1256type Zonker struct {
1257 ID int64
1258 Name string
1259 Wherefore string
1260}
1261
1262func zonkzone(w http.ResponseWriter, r *http.Request) {
1263 userinfo := login.GetUserInfo(r)
1264 rows, err := stmtGetZonkers.Query(userinfo.UserID)
1265 if err != nil {
1266 log.Printf("err: %s", err)
1267 return
1268 }
1269 defer rows.Close()
1270 var zonkers []Zonker
1271 for rows.Next() {
1272 var z Zonker
1273 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1274 zonkers = append(zonkers, z)
1275 }
1276 sort.Slice(zonkers, func(i, j int) bool {
1277 w1 := zonkers[i].Wherefore
1278 w2 := zonkers[j].Wherefore
1279 if w1 == w2 {
1280 return zonkers[i].Name < zonkers[j].Name
1281 }
1282 if w1 == "zonvoy" {
1283 w1 = "zzzzzzz"
1284 }
1285 if w2 == "zonvoy" {
1286 w2 = "zzzzzzz"
1287 }
1288 return w1 < w2
1289 })
1290
1291 templinfo := getInfo(r)
1292 templinfo["Zonkers"] = zonkers
1293 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1294 err = readviews.Execute(w, "zonkers.html", templinfo)
1295 if err != nil {
1296 log.Print(err)
1297 }
1298}
1299
1300func zonkzonk(w http.ResponseWriter, r *http.Request) {
1301 userinfo := login.GetUserInfo(r)
1302 itsok := r.FormValue("itsok")
1303 if itsok == "iforgiveyou" {
1304 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1305 db := opendatabase()
1306 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1307 userinfo.UserID, zonkerid)
1308 bitethethumbs()
1309 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1310 return
1311 }
1312 wherefore := r.FormValue("wherefore")
1313 name := r.FormValue("name")
1314 if name == "" {
1315 return
1316 }
1317 switch wherefore {
1318 case "zonker":
1319 case "zomain":
1320 case "zonvoy":
1321 case "zord":
1322 case "zilence":
1323 default:
1324 return
1325 }
1326 db := opendatabase()
1327 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1328 userinfo.UserID, name, wherefore)
1329 if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" || wherefore == "zilence" {
1330 bitethethumbs()
1331 }
1332
1333 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1334}
1335
1336func accountpage(w http.ResponseWriter, r *http.Request) {
1337 u := login.GetUserInfo(r)
1338 user, _ := butwhatabout(u.Username)
1339 templinfo := getInfo(r)
1340 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1341 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1342 templinfo["User"] = user
1343 err := readviews.Execute(w, "account.html", templinfo)
1344 if err != nil {
1345 log.Print(err)
1346 }
1347}
1348
1349func dochpass(w http.ResponseWriter, r *http.Request) {
1350 err := login.ChangePassword(w, r)
1351 if err != nil {
1352 log.Printf("error changing password: %s", err)
1353 }
1354 http.Redirect(w, r, "/account", http.StatusSeeOther)
1355}
1356
1357func fingerlicker(w http.ResponseWriter, r *http.Request) {
1358 orig := r.FormValue("resource")
1359
1360 log.Printf("finger lick: %s", orig)
1361
1362 if strings.HasPrefix(orig, "acct:") {
1363 orig = orig[5:]
1364 }
1365
1366 name := orig
1367 idx := strings.LastIndexByte(name, '/')
1368 if idx != -1 {
1369 name = name[idx+1:]
1370 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1371 log.Printf("foreign request rejected")
1372 name = ""
1373 }
1374 } else {
1375 idx = strings.IndexByte(name, '@')
1376 if idx != -1 {
1377 name = name[:idx]
1378 if name+"@"+serverName != orig {
1379 log.Printf("foreign request rejected")
1380 name = ""
1381 }
1382 }
1383 }
1384 user, err := butwhatabout(name)
1385 if err != nil {
1386 http.NotFound(w, r)
1387 return
1388 }
1389
1390 j := junk.New()
1391 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1392 j["aliases"] = []string{user.URL}
1393 var links []junk.Junk
1394 l := junk.New()
1395 l["rel"] = "self"
1396 l["type"] = `application/activity+json`
1397 l["href"] = user.URL
1398 links = append(links, l)
1399 j["links"] = links
1400
1401 w.Header().Set("Cache-Control", "max-age=3600")
1402 w.Header().Set("Content-Type", "application/jrd+json")
1403 j.Write(w)
1404}
1405
1406func somedays() string {
1407 secs := 432000 + notrand.Int63n(432000)
1408 return fmt.Sprintf("%d", secs)
1409}
1410
1411func avatate(w http.ResponseWriter, r *http.Request) {
1412 n := r.FormValue("a")
1413 a := avatar(n)
1414 w.Header().Set("Cache-Control", "max-age="+somedays())
1415 w.Write(a)
1416}
1417
1418func servecss(w http.ResponseWriter, r *http.Request) {
1419 w.Header().Set("Cache-Control", "max-age=7776000")
1420 http.ServeFile(w, r, "views"+r.URL.Path)
1421}
1422func servehtml(w http.ResponseWriter, r *http.Request) {
1423 templinfo := getInfo(r)
1424 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1425 if err != nil {
1426 log.Print(err)
1427 }
1428}
1429func serveemu(w http.ResponseWriter, r *http.Request) {
1430 xid := mux.Vars(r)["xid"]
1431 w.Header().Set("Cache-Control", "max-age="+somedays())
1432 http.ServeFile(w, r, "emus/"+xid)
1433}
1434func servememe(w http.ResponseWriter, r *http.Request) {
1435 xid := mux.Vars(r)["xid"]
1436 w.Header().Set("Cache-Control", "max-age="+somedays())
1437 http.ServeFile(w, r, "memes/"+xid)
1438}
1439
1440func servefile(w http.ResponseWriter, r *http.Request) {
1441 xid := mux.Vars(r)["xid"]
1442 row := stmtFileData.QueryRow(xid)
1443 var media string
1444 var data []byte
1445 err := row.Scan(&media, &data)
1446 if err != nil {
1447 log.Printf("error loading file: %s", err)
1448 http.NotFound(w, r)
1449 return
1450 }
1451 w.Header().Set("Content-Type", media)
1452 w.Header().Set("X-Content-Type-Options", "nosniff")
1453 w.Header().Set("Cache-Control", "max-age="+somedays())
1454 w.Write(data)
1455}
1456
1457func nomoroboto(w http.ResponseWriter, r *http.Request) {
1458 io.WriteString(w, "User-agent: *\n")
1459 io.WriteString(w, "Disallow: /a\n")
1460 io.WriteString(w, "Disallow: /d\n")
1461 io.WriteString(w, "Disallow: /meme\n")
1462 for _, u := range allusers() {
1463 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1464 }
1465}
1466
1467func serve() {
1468 db := opendatabase()
1469 login.Init(db)
1470
1471 listener, err := openListener()
1472 if err != nil {
1473 log.Fatal(err)
1474 }
1475 go redeliverator()
1476
1477 debug := false
1478 getconfig("debug", &debug)
1479 readviews = templates.Load(debug,
1480 "views/honkpage.html",
1481 "views/honkers.html",
1482 "views/zonkers.html",
1483 "views/combos.html",
1484 "views/honkform.html",
1485 "views/honk.html",
1486 "views/account.html",
1487 "views/about.html",
1488 "views/funzone.html",
1489 "views/login.html",
1490 "views/xzone.html",
1491 "views/header.html",
1492 )
1493 if !debug {
1494 s := "views/style.css"
1495 savedstyleparams[s] = getstyleparam(s)
1496 s = "views/local.css"
1497 savedstyleparams[s] = getstyleparam(s)
1498 }
1499
1500 bitethethumbs()
1501
1502 mux := mux.NewRouter()
1503 mux.Use(login.Checker)
1504
1505 posters := mux.Methods("POST").Subrouter()
1506 getters := mux.Methods("GET").Subrouter()
1507
1508 getters.HandleFunc("/", homepage)
1509 getters.HandleFunc("/front", homepage)
1510 getters.HandleFunc("/robots.txt", nomoroboto)
1511 getters.HandleFunc("/rss", showrss)
1512 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1513 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1514 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1515 posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1516 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1517 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1518 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1519 getters.HandleFunc("/a", avatate)
1520 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1521 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1522 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1523 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1524
1525 getters.HandleFunc("/style.css", servecss)
1526 getters.HandleFunc("/local.css", servecss)
1527 getters.HandleFunc("/about", servehtml)
1528 getters.HandleFunc("/login", servehtml)
1529 posters.HandleFunc("/dologin", login.LoginFunc)
1530 getters.HandleFunc("/logout", login.LogoutFunc)
1531
1532 loggedin := mux.NewRoute().Subrouter()
1533 loggedin.Use(login.Required)
1534 loggedin.HandleFunc("/account", accountpage)
1535 loggedin.HandleFunc("/funzone", showfunzone)
1536 loggedin.HandleFunc("/chpass", dochpass)
1537 loggedin.HandleFunc("/atme", homepage)
1538 loggedin.HandleFunc("/zonkzone", zonkzone)
1539 loggedin.HandleFunc("/xzone", xzone)
1540 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1541 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1542 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1543 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1544 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1545 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1546 loggedin.HandleFunc("/honkers", showhonkers)
1547 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1548 loggedin.HandleFunc("/h", showhonker)
1549 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1550 loggedin.HandleFunc("/c", showcombos)
1551 loggedin.HandleFunc("/t", showconvoy)
1552 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1553
1554 err = http.Serve(listener, mux)
1555 if err != nil {
1556 log.Fatal(err)
1557 }
1558}
1559
1560func cleanupdb(arg string) {
1561 db := opendatabase()
1562 days, err := strconv.Atoi(arg)
1563 if err != nil {
1564 honker := arg
1565 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1566 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1567 doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1568 } else {
1569 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1570 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)
1571 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)
1572 }
1573 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1574 for _, u := range allusers() {
1575 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)
1576 }
1577}
1578
1579var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1580var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1581var stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1582var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1583var stmtFindZonk, stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1584var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1585var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1586var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
1587
1588func preparetodie(db *sql.DB, s string) *sql.Stmt {
1589 stmt, err := db.Prepare(s)
1590 if err != nil {
1591 log.Fatalf("error %s: %s", err, s)
1592 }
1593 return stmt
1594}
1595
1596func prepareStatements(db *sql.DB) {
1597 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")
1598 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1599 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1600 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1601 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1602 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1603
1604 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 "
1605 limit := " order by honkid desc limit 250"
1606 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1607 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1608 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1609 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1610 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)
1611 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1612 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1613 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1614 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1615 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1616
1617 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1618 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1619 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1620 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1621 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1622 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1623 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1624 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1625 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey, options from users where username = ?")
1626 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1627 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1628 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1629 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1630 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1631 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zomain' or wherefore = 'zord' or wherefore = 'zilence')")
1632 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1633 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1634 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1635 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1636 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1637 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
1638 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")
1639}
1640
1641func ElaborateUnitTests() {
1642}
1643
1644func main() {
1645 cmd := "run"
1646 if len(os.Args) > 1 {
1647 cmd = os.Args[1]
1648 }
1649 switch cmd {
1650 case "init":
1651 initdb()
1652 case "upgrade":
1653 upgradedb()
1654 }
1655 db := opendatabase()
1656 dbversion := 0
1657 getconfig("dbversion", &dbversion)
1658 if dbversion != myVersion {
1659 log.Fatal("incorrect database version. run upgrade.")
1660 }
1661 getconfig("servermsg", &serverMsg)
1662 getconfig("servername", &serverName)
1663 getconfig("usersep", &userSep)
1664 getconfig("honksep", &honkSep)
1665 getconfig("dnf", &donotfedafterdark)
1666 prepareStatements(db)
1667 switch cmd {
1668 case "adduser":
1669 adduser()
1670 case "cleanup":
1671 arg := "30"
1672 if len(os.Args) > 2 {
1673 arg = os.Args[2]
1674 }
1675 cleanupdb(arg)
1676 case "ping":
1677 if len(os.Args) < 4 {
1678 fmt.Printf("usage: honk ping from to\n")
1679 return
1680 }
1681 name := os.Args[2]
1682 targ := os.Args[3]
1683 user, err := butwhatabout(name)
1684 if err != nil {
1685 log.Printf("unknown user")
1686 return
1687 }
1688 ping(user, targ)
1689 case "peep":
1690 peeppeep()
1691 case "run":
1692 serve()
1693 case "test":
1694 ElaborateUnitTests()
1695 default:
1696 log.Fatal("unknown command")
1697 }
1698}