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