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