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