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 oonker := xonk.Oonker
816 if oonker == "" {
817 oonker = xonk.Honker
818 }
819 dt := time.Now().UTC()
820 bonk := Honk{
821 UserID: userinfo.UserID,
822 Username: userinfo.Username,
823 What: "bonk",
824 Honker: user.URL,
825 XID: xonk.XID,
826 Date: dt,
827 Donks: xonk.Donks,
828 Convoy: xonk.Convoy,
829 Audience: []string{oonker, thewholeworld},
830 Public: true,
831 }
832
833 aud := strings.Join(bonk.Audience, " ")
834 whofore := 2
835 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
836 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
837 xonk.Precis, oonker)
838 if err != nil {
839 log.Printf("error saving bonk: %s", err)
840 return
841 }
842 bonk.ID, _ = res.LastInsertId()
843 for _, d := range bonk.Donks {
844 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
845 if err != nil {
846 log.Printf("err saving donk: %s", err)
847 return
848 }
849 }
850
851 go honkworldwide(user, &bonk)
852}
853
854func zonkit(w http.ResponseWriter, r *http.Request) {
855 wherefore := r.FormValue("wherefore")
856 what := r.FormValue("what")
857 switch wherefore {
858 case "zonk":
859 case "zonvoy":
860 }
861
862 log.Printf("zonking %s %s", wherefore, what)
863 userinfo := login.GetUserInfo(r)
864 if wherefore == "zonk" {
865 xonk := getxonk(userinfo.UserID, what)
866 if xonk != nil {
867 stmtZonkDonks.Exec(xonk.ID)
868 stmtZonkIt.Exec(userinfo.UserID, what)
869 if xonk.Whofore == 2 || xonk.Whofore == 3 {
870 zonk := Honk{
871 What: "zonk",
872 XID: xonk.XID,
873 Date: time.Now().UTC(),
874 Audience: oneofakind(xonk.Audience),
875 }
876
877 user, _ := butwhatabout(userinfo.Username)
878 log.Printf("announcing deleted honk: %s", what)
879 go honkworldwide(user, &zonk)
880 }
881 }
882 }
883 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
884 if err != nil {
885 log.Printf("error saving zonker: %s", err)
886 return
887 }
888}
889
890func savehonk(w http.ResponseWriter, r *http.Request) {
891 rid := r.FormValue("rid")
892 noise := r.FormValue("noise")
893
894 userinfo := login.GetUserInfo(r)
895 user, _ := butwhatabout(userinfo.Username)
896
897 dt := time.Now().UTC()
898 xid := fmt.Sprintf("https://%s/u/%s/h/%s", serverName, userinfo.Username, xfiltrate())
899 what := "honk"
900 if rid != "" {
901 what = "tonk"
902 }
903 honk := Honk{
904 UserID: userinfo.UserID,
905 Username: userinfo.Username,
906 What: "honk",
907 Honker: user.URL,
908 XID: xid,
909 Date: dt,
910 }
911 if strings.HasPrefix(noise, "DZ:") {
912 idx := strings.Index(noise, "\n")
913 if idx == -1 {
914 honk.Precis = noise
915 noise = ""
916 } else {
917 honk.Precis = noise[:idx]
918 noise = noise[idx+1:]
919 }
920 }
921 noise = hooterize(noise)
922 noise = strings.TrimSpace(noise)
923 honk.Precis = strings.TrimSpace(honk.Precis)
924
925 var convoy string
926 if rid != "" {
927 xonk := getxonk(userinfo.UserID, rid)
928 if xonk != nil {
929 if xonk.Public {
930 honk.Audience = append(honk.Audience, xonk.Audience...)
931 }
932 convoy = xonk.Convoy
933 } else {
934 xonkaud, c := whosthere(rid)
935 honk.Audience = append(honk.Audience, xonkaud...)
936 convoy = c
937 }
938 for i, a := range honk.Audience {
939 if a == thewholeworld {
940 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
941 break
942 }
943 }
944 honk.RID = rid
945 } else {
946 honk.Audience = []string{thewholeworld}
947 }
948 if noise != "" && noise[0] == '@' {
949 honk.Audience = append(grapevine(noise), honk.Audience...)
950 } else {
951 honk.Audience = append(honk.Audience, grapevine(noise)...)
952 }
953 if convoy == "" {
954 convoy = "data:,electrichonkytonk-" + xfiltrate()
955 }
956 butnottooloud(honk.Audience)
957 honk.Audience = oneofakind(honk.Audience)
958 if len(honk.Audience) == 0 {
959 log.Printf("honk to nowhere")
960 http.Error(w, "honk to nowhere...", http.StatusNotFound)
961 return
962 }
963 honk.Public = !keepitquiet(honk.Audience)
964 noise = obfusbreak(noise)
965 honk.Noise = noise
966 honk.Convoy = convoy
967
968 file, filehdr, err := r.FormFile("donk")
969 if err == nil {
970 var buf bytes.Buffer
971 io.Copy(&buf, file)
972 file.Close()
973 data := buf.Bytes()
974 xid := xfiltrate()
975 var media, name string
976 img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
977 if err == nil {
978 data = img.Data
979 format := img.Format
980 media = "image/" + format
981 if format == "jpeg" {
982 format = "jpg"
983 }
984 name = xid + "." + format
985 xid = name
986 } else {
987 maxsize := 100000
988 if len(data) > maxsize {
989 log.Printf("bad image: %s too much text: %d", err, len(data))
990 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
991 return
992 }
993 for i := 0; i < len(data); i++ {
994 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
995 log.Printf("bad image: %s not text: %d", err, data[i])
996 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
997 return
998 }
999 }
1000 media = "text/plain"
1001 name = filehdr.Filename
1002 if name == "" {
1003 name = xid + ".txt"
1004 }
1005 xid += ".txt"
1006 }
1007 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1008 res, err := stmtSaveFile.Exec(xid, name, url, media, 1, data)
1009 if err != nil {
1010 log.Printf("unable to save image: %s", err)
1011 return
1012 }
1013 var d Donk
1014 d.FileID, _ = res.LastInsertId()
1015 d.XID = name
1016 d.Name = name
1017 d.Media = media
1018 d.URL = url
1019 d.Local = true
1020 honk.Donks = append(honk.Donks, &d)
1021 }
1022 herd := herdofemus(honk.Noise)
1023 for _, e := range herd {
1024 donk := savedonk(e.ID, e.Name, "image/png", true)
1025 if donk != nil {
1026 donk.Name = e.Name
1027 honk.Donks = append(honk.Donks, donk)
1028 }
1029 }
1030 honk.Donks = append(honk.Donks, memetics(honk.Noise)...)
1031
1032 aud := strings.Join(honk.Audience, " ")
1033 whofore := 2
1034 if !honk.Public {
1035 whofore = 3
1036 }
1037 if r.FormValue("preview") == "preview" {
1038 honks := []*Honk{&honk}
1039 reverbolate(honks)
1040 templinfo := getInfo(r)
1041 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1042 templinfo["Honks"] = honks
1043 templinfo["Noise"] = r.FormValue("noise")
1044 templinfo["ServerMessage"] = "honk preview"
1045 err := readviews.Execute(w, "honkpage.html", templinfo)
1046 if err != nil {
1047 log.Print(err)
1048 }
1049 return
1050 }
1051 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
1052 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
1053 if err != nil {
1054 log.Printf("error saving honk: %s", err)
1055 return
1056 }
1057 honk.ID, _ = res.LastInsertId()
1058 for _, d := range honk.Donks {
1059 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
1060 if err != nil {
1061 log.Printf("err saving donk: %s", err)
1062 return
1063 }
1064 }
1065
1066 go honkworldwide(user, &honk)
1067
1068 http.Redirect(w, r, "/", http.StatusSeeOther)
1069}
1070
1071func showhonkers(w http.ResponseWriter, r *http.Request) {
1072 userinfo := login.GetUserInfo(r)
1073 templinfo := getInfo(r)
1074 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1075 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1076 err := readviews.Execute(w, "honkers.html", templinfo)
1077 if err != nil {
1078 log.Print(err)
1079 }
1080}
1081
1082func showcombos(w http.ResponseWriter, r *http.Request) {
1083 userinfo := login.GetUserInfo(r)
1084 templinfo := getInfo(r)
1085 honkers := gethonkers(userinfo.UserID)
1086 var combos []string
1087 for _, h := range honkers {
1088 combos = append(combos, h.Combos...)
1089 }
1090 for i, c := range combos {
1091 if c == "-" {
1092 combos[i] = ""
1093 }
1094 }
1095 combos = oneofakind(combos)
1096 sort.Strings(combos)
1097 templinfo["Combos"] = combos
1098 err := readviews.Execute(w, "combos.html", templinfo)
1099 if err != nil {
1100 log.Print(err)
1101 }
1102}
1103
1104func savehonker(w http.ResponseWriter, r *http.Request) {
1105 u := login.GetUserInfo(r)
1106 name := r.FormValue("name")
1107 url := r.FormValue("url")
1108 peep := r.FormValue("peep")
1109 combos := r.FormValue("combos")
1110 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1111
1112 if honkerid > 0 {
1113 goodbye := r.FormValue("goodbye")
1114 if goodbye == "F" {
1115 db := opendatabase()
1116 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1117 honkerid, u.UserID)
1118 var xid string
1119 err := row.Scan(&xid)
1120 if err != nil {
1121 log.Printf("can't get honker xid: %s", err)
1122 return
1123 }
1124 log.Printf("unsubscribing from %s", xid)
1125 user, _ := butwhatabout(u.Username)
1126 go itakeitallback(user, xid)
1127 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1128 if err != nil {
1129 log.Printf("error updating honker: %s", err)
1130 return
1131 }
1132
1133 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1134 return
1135 }
1136 combos = " " + strings.TrimSpace(combos) + " "
1137 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1138 if err != nil {
1139 log.Printf("update honker err: %s", err)
1140 return
1141 }
1142 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1143 }
1144
1145 flavor := "presub"
1146 if peep == "peep" {
1147 flavor = "peep"
1148 }
1149 url = investigate(url)
1150 if url == "" {
1151 return
1152 }
1153 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1154 if err != nil {
1155 log.Print(err)
1156 return
1157 }
1158 if flavor == "presub" {
1159 user, _ := butwhatabout(u.Username)
1160 go subsub(user, url)
1161 }
1162 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1163}
1164
1165type Zonker struct {
1166 ID int64
1167 Name string
1168 Wherefore string
1169}
1170
1171func zonkzone(w http.ResponseWriter, r *http.Request) {
1172 userinfo := login.GetUserInfo(r)
1173 rows, err := stmtGetZonkers.Query(userinfo.UserID)
1174 if err != nil {
1175 log.Printf("err: %s", err)
1176 return
1177 }
1178 defer rows.Close()
1179 var zonkers []Zonker
1180 for rows.Next() {
1181 var z Zonker
1182 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1183 zonkers = append(zonkers, z)
1184 }
1185 sort.Slice(zonkers, func(i, j int) bool {
1186 w1 := zonkers[i].Wherefore
1187 w2 := zonkers[j].Wherefore
1188 if w1 == w2 {
1189 return zonkers[i].Name < zonkers[j].Name
1190 }
1191 if w1 == "zonvoy" {
1192 w1 = "zzzzzzz"
1193 }
1194 if w2 == "zonvoy" {
1195 w2 = "zzzzzzz"
1196 }
1197 return w1 < w2
1198 })
1199
1200 templinfo := getInfo(r)
1201 templinfo["Zonkers"] = zonkers
1202 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1203 err = readviews.Execute(w, "zonkers.html", templinfo)
1204 if err != nil {
1205 log.Print(err)
1206 }
1207}
1208
1209func zonkzonk(w http.ResponseWriter, r *http.Request) {
1210 userinfo := login.GetUserInfo(r)
1211 itsok := r.FormValue("itsok")
1212 if itsok == "iforgiveyou" {
1213 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1214 db := opendatabase()
1215 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1216 userinfo.UserID, zonkerid)
1217 bitethethumbs()
1218 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1219 return
1220 }
1221 wherefore := r.FormValue("wherefore")
1222 name := r.FormValue("name")
1223 if name == "" {
1224 return
1225 }
1226 switch wherefore {
1227 case "zonker":
1228 case "zomain":
1229 case "zonvoy":
1230 case "zord":
1231 default:
1232 return
1233 }
1234 db := opendatabase()
1235 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1236 userinfo.UserID, name, wherefore)
1237 if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" {
1238 bitethethumbs()
1239 }
1240
1241 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1242}
1243
1244func accountpage(w http.ResponseWriter, r *http.Request) {
1245 u := login.GetUserInfo(r)
1246 user, _ := butwhatabout(u.Username)
1247 templinfo := getInfo(r)
1248 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1249 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1250 templinfo["WhatAbout"] = user.About
1251 err := readviews.Execute(w, "account.html", templinfo)
1252 if err != nil {
1253 log.Print(err)
1254 }
1255}
1256
1257func dochpass(w http.ResponseWriter, r *http.Request) {
1258 err := login.ChangePassword(w, r)
1259 if err != nil {
1260 log.Printf("error changing password: %s", err)
1261 }
1262 http.Redirect(w, r, "/account", http.StatusSeeOther)
1263}
1264
1265func fingerlicker(w http.ResponseWriter, r *http.Request) {
1266 orig := r.FormValue("resource")
1267
1268 log.Printf("finger lick: %s", orig)
1269
1270 if strings.HasPrefix(orig, "acct:") {
1271 orig = orig[5:]
1272 }
1273
1274 name := orig
1275 idx := strings.LastIndexByte(name, '/')
1276 if idx != -1 {
1277 name = name[idx+1:]
1278 if "https://"+serverName+"/u/"+name != orig {
1279 log.Printf("foreign request rejected")
1280 name = ""
1281 }
1282 } else {
1283 idx = strings.IndexByte(name, '@')
1284 if idx != -1 {
1285 name = name[:idx]
1286 if name+"@"+serverName != orig {
1287 log.Printf("foreign request rejected")
1288 name = ""
1289 }
1290 }
1291 }
1292 user, err := butwhatabout(name)
1293 if err != nil {
1294 http.NotFound(w, r)
1295 return
1296 }
1297
1298 j := junk.New()
1299 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1300 j["aliases"] = []string{user.URL}
1301 var links []map[string]interface{}
1302 l := junk.New()
1303 l["rel"] = "self"
1304 l["type"] = `application/activity+json`
1305 l["href"] = user.URL
1306 links = append(links, l)
1307 j["links"] = links
1308
1309 w.Header().Set("Cache-Control", "max-age=3600")
1310 w.Header().Set("Content-Type", "application/jrd+json")
1311 j.Write(w)
1312}
1313
1314func somedays() string {
1315 secs := 432000 + notrand.Int63n(432000)
1316 return fmt.Sprintf("%d", secs)
1317}
1318
1319func avatate(w http.ResponseWriter, r *http.Request) {
1320 n := r.FormValue("a")
1321 a := avatar(n)
1322 w.Header().Set("Cache-Control", "max-age="+somedays())
1323 w.Write(a)
1324}
1325
1326func servecss(w http.ResponseWriter, r *http.Request) {
1327 w.Header().Set("Cache-Control", "max-age=7776000")
1328 http.ServeFile(w, r, "views"+r.URL.Path)
1329}
1330func servehtml(w http.ResponseWriter, r *http.Request) {
1331 templinfo := getInfo(r)
1332 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1333 if err != nil {
1334 log.Print(err)
1335 }
1336}
1337func serveemu(w http.ResponseWriter, r *http.Request) {
1338 xid := mux.Vars(r)["xid"]
1339 w.Header().Set("Cache-Control", "max-age="+somedays())
1340 http.ServeFile(w, r, "emus/"+xid)
1341}
1342func servememe(w http.ResponseWriter, r *http.Request) {
1343 xid := mux.Vars(r)["xid"]
1344 w.Header().Set("Cache-Control", "max-age="+somedays())
1345 http.ServeFile(w, r, "memes/"+xid)
1346}
1347
1348func servefile(w http.ResponseWriter, r *http.Request) {
1349 xid := mux.Vars(r)["xid"]
1350 row := stmtFileData.QueryRow(xid)
1351 var media string
1352 var data []byte
1353 err := row.Scan(&media, &data)
1354 if err != nil {
1355 log.Printf("error loading file: %s", err)
1356 http.NotFound(w, r)
1357 return
1358 }
1359 w.Header().Set("Content-Type", media)
1360 w.Header().Set("X-Content-Type-Options", "nosniff")
1361 w.Header().Set("Cache-Control", "max-age="+somedays())
1362 w.Write(data)
1363}
1364
1365func nomoroboto(w http.ResponseWriter, r *http.Request) {
1366 io.WriteString(w, "User-agent: *\n")
1367 io.WriteString(w, "Disallow: /t\n")
1368 for _, u := range allusers() {
1369 fmt.Fprintf(w, "Disallow: /u/%s/h/\n", u.Username)
1370 }
1371}
1372
1373func serve() {
1374 db := opendatabase()
1375 login.Init(db)
1376
1377 listener, err := openListener()
1378 if err != nil {
1379 log.Fatal(err)
1380 }
1381 go redeliverator()
1382
1383 debug := false
1384 getconfig("debug", &debug)
1385 readviews = templates.Load(debug,
1386 "views/honkpage.html",
1387 "views/honkers.html",
1388 "views/zonkers.html",
1389 "views/combos.html",
1390 "views/honkform.html",
1391 "views/honk.html",
1392 "views/account.html",
1393 "views/about.html",
1394 "views/funzone.html",
1395 "views/login.html",
1396 "views/xzone.html",
1397 "views/header.html",
1398 )
1399 if !debug {
1400 s := "views/style.css"
1401 savedstyleparams[s] = getstyleparam(s)
1402 s = "views/local.css"
1403 savedstyleparams[s] = getstyleparam(s)
1404 }
1405
1406 bitethethumbs()
1407
1408 mux := mux.NewRouter()
1409 mux.Use(login.Checker)
1410
1411 posters := mux.Methods("POST").Subrouter()
1412 getters := mux.Methods("GET").Subrouter()
1413
1414 getters.HandleFunc("/", homepage)
1415 getters.HandleFunc("/front", homepage)
1416 getters.HandleFunc("/robots.txt", nomoroboto)
1417 getters.HandleFunc("/rss", showrss)
1418 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1419 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1420 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1421 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1422 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1423 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1424 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1425 getters.HandleFunc("/a", avatate)
1426 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1427 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1428 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1429 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1430
1431 getters.HandleFunc("/style.css", servecss)
1432 getters.HandleFunc("/local.css", servecss)
1433 getters.HandleFunc("/about", servehtml)
1434 getters.HandleFunc("/login", servehtml)
1435 posters.HandleFunc("/dologin", login.LoginFunc)
1436 getters.HandleFunc("/logout", login.LogoutFunc)
1437
1438 loggedin := mux.NewRoute().Subrouter()
1439 loggedin.Use(login.Required)
1440 loggedin.HandleFunc("/account", accountpage)
1441 loggedin.HandleFunc("/funzone", showfunzone)
1442 loggedin.HandleFunc("/chpass", dochpass)
1443 loggedin.HandleFunc("/atme", homepage)
1444 loggedin.HandleFunc("/zonkzone", zonkzone)
1445 loggedin.HandleFunc("/xzone", xzone)
1446 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1447 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1448 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1449 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1450 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1451 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1452 loggedin.HandleFunc("/honkers", showhonkers)
1453 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1454 loggedin.HandleFunc("/h", showhonker)
1455 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1456 loggedin.HandleFunc("/c", showcombos)
1457 loggedin.HandleFunc("/t", showconvoy)
1458 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1459
1460 err = http.Serve(listener, mux)
1461 if err != nil {
1462 log.Fatal(err)
1463 }
1464}
1465
1466func cleanupdb(days int) {
1467 db := opendatabase()
1468 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1469 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)
1470 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)
1471 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1472}
1473
1474func reducedb(honker string) {
1475 db := opendatabase()
1476 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1477 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1478 doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1479 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1480}
1481
1482var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1483var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1484var stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1485var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1486var stmtFindZonk, stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1487var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1488var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1489var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
1490
1491func preparetodie(db *sql.DB, s string) *sql.Stmt {
1492 stmt, err := db.Prepare(s)
1493 if err != nil {
1494 log.Fatalf("error %s: %s", err, s)
1495 }
1496 return stmt
1497}
1498
1499func prepareStatements(db *sql.DB) {
1500 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")
1501 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1502 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1503 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1504 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1505 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1506
1507 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 "
1508 limit := " order by honkid desc limit 250"
1509 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1510 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1511 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1512 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1513 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)
1514 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1515 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1516 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and honker = ?"+butnotthose+limit)
1517 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1518 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or whofore = 2) and convoy = ?"+limit)
1519
1520 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1521 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1522 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1523 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1524 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1525 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1526 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1527 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1528 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1529 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1530 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1531 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1532 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1533 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1534 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zomain' or wherefore = 'zord')")
1535 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1536 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1537 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1538 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1539 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1540 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
1541 stmtRecentHonkers = preparetodie(db, "select distinct(honker) from honks where userid = ? order by honkid desc limit 100")
1542}
1543
1544func ElaborateUnitTests() {
1545}
1546
1547func main() {
1548 var err error
1549 cmd := "run"
1550 if len(os.Args) > 1 {
1551 cmd = os.Args[1]
1552 }
1553 switch cmd {
1554 case "init":
1555 initdb()
1556 case "upgrade":
1557 upgradedb()
1558 }
1559 db := opendatabase()
1560 dbversion := 0
1561 getconfig("dbversion", &dbversion)
1562 if dbversion != myVersion {
1563 log.Fatal("incorrect database version. run upgrade.")
1564 }
1565 getconfig("servermsg", &serverMsg)
1566 getconfig("servername", &serverName)
1567 getconfig("dnf", &donotfedafterdark)
1568 prepareStatements(db)
1569 switch cmd {
1570 case "adduser":
1571 adduser()
1572 case "cleanup":
1573 days := 30
1574 if len(os.Args) > 2 {
1575 days, err = strconv.Atoi(os.Args[2])
1576 if err != nil {
1577 log.Fatal(err)
1578 }
1579 }
1580 cleanupdb(days)
1581 case "reduce":
1582 if len(os.Args) < 3 {
1583 log.Fatal("need a honker name")
1584 }
1585 reducedb(os.Args[2])
1586 case "ping":
1587 if len(os.Args) < 4 {
1588 fmt.Printf("usage: honk ping from to\n")
1589 return
1590 }
1591 name := os.Args[2]
1592 targ := os.Args[3]
1593 user, err := butwhatabout(name)
1594 if err != nil {
1595 log.Printf("unknown user")
1596 return
1597 }
1598 ping(user, targ)
1599 case "peep":
1600 peeppeep()
1601 case "run":
1602 serve()
1603 case "test":
1604 ElaborateUnitTests()
1605 default:
1606 log.Fatal("unknown command")
1607 }
1608}