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