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