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