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