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