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("Content-Type", theonetruename)
468 j.Write(w)
469}
470
471func emptiness(w http.ResponseWriter, r *http.Request) {
472 name := mux.Vars(r)["name"]
473 user, err := butwhatabout(name)
474 if err != nil {
475 http.NotFound(w, r)
476 return
477 }
478 colname := "/followers"
479 if strings.HasSuffix(r.URL.Path, "/following") {
480 colname = "/following"
481 }
482 j := junk.New()
483 j["@context"] = itiswhatitis
484 j["id"] = user.URL + colname
485 j["type"] = "OrderedCollection"
486 j["totalItems"] = 0
487 j["orderedItems"] = []interface{}{}
488
489 w.Header().Set("Content-Type", theonetruename)
490 j.Write(w)
491}
492
493func showuser(w http.ResponseWriter, r *http.Request) {
494 name := mux.Vars(r)["name"]
495 user, err := butwhatabout(name)
496 if err != nil {
497 http.NotFound(w, r)
498 return
499 }
500 if friendorfoe(r.Header.Get("Accept")) {
501 j := asjonker(user)
502 w.Header().Set("Content-Type", theonetruename)
503 j.Write(w)
504 return
505 }
506 u := login.GetUserInfo(r)
507 honks := gethonksbyuser(name, u != nil && u.Username == name)
508 honkpage(w, r, u, user, honks, "")
509}
510
511func showhonker(w http.ResponseWriter, r *http.Request) {
512 u := login.GetUserInfo(r)
513 name := mux.Vars(r)["name"]
514 var honks []*Honk
515 if name == "" {
516 name = r.FormValue("xid")
517 honks = gethonksbyxonker(u.UserID, name)
518 } else {
519 honks = gethonksbyhonker(u.UserID, name)
520 }
521 honkpage(w, r, u, nil, honks, "honks by honker: "+name)
522}
523
524func showcombo(w http.ResponseWriter, r *http.Request) {
525 name := mux.Vars(r)["name"]
526 u := login.GetUserInfo(r)
527 honks := gethonksbycombo(u.UserID, name)
528 honks = osmosis(honks, u.UserID)
529 honkpage(w, r, u, nil, honks, "honks by combo: "+name)
530}
531func showconvoy(w http.ResponseWriter, r *http.Request) {
532 c := r.FormValue("c")
533 var userid int64 = -1
534 u := login.GetUserInfo(r)
535 if u != nil {
536 userid = u.UserID
537 }
538 honks := gethonksbyconvoy(userid, c)
539 honkpage(w, r, u, nil, honks, "honks in convoy: "+c)
540}
541
542func showhonk(w http.ResponseWriter, r *http.Request) {
543 name := mux.Vars(r)["name"]
544 user, err := butwhatabout(name)
545 if err != nil {
546 http.NotFound(w, r)
547 return
548 }
549 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
550 h := getxonk(user.ID, xid)
551 if h == nil || !h.Public {
552 http.NotFound(w, r)
553 return
554 }
555 if friendorfoe(r.Header.Get("Accept")) {
556 donksforhonks([]*Honk{h})
557 _, j := jonkjonk(user, h)
558 j["@context"] = itiswhatitis
559 w.Header().Set("Content-Type", theonetruename)
560 j.Write(w)
561 return
562 }
563 honks := gethonksbyconvoy(-1, h.Convoy)
564 u := login.GetUserInfo(r)
565 honkpage(w, r, u, nil, honks, "one honk maybe more")
566}
567
568func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
569 honks []*Honk, infomsg string) {
570 reverbolate(honks)
571 templinfo := getInfo(r)
572 if u != nil {
573 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
574 }
575 if u == nil {
576 w.Header().Set("Cache-Control", "max-age=60")
577 }
578 if user != nil {
579 filt := htfilter.New()
580 templinfo["Name"] = user.Name
581 whatabout := user.About
582 whatabout = obfusbreak(user.About)
583 templinfo["WhatAbout"], _ = filt.String(whatabout)
584 }
585 templinfo["Honks"] = honks
586 templinfo["ServerMessage"] = infomsg
587 err := readviews.Execute(w, "honkpage.html", templinfo)
588 if err != nil {
589 log.Print(err)
590 }
591}
592
593func saveuser(w http.ResponseWriter, r *http.Request) {
594 whatabout := r.FormValue("whatabout")
595 u := login.GetUserInfo(r)
596 db := opendatabase()
597 _, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
598 if err != nil {
599 log.Printf("error bouting what: %s", err)
600 }
601
602 http.Redirect(w, r, "/account", http.StatusSeeOther)
603}
604
605func gethonkers(userid int64) []*Honker {
606 rows, err := stmtHonkers.Query(userid)
607 if err != nil {
608 log.Printf("error querying honkers: %s", err)
609 return nil
610 }
611 defer rows.Close()
612 var honkers []*Honker
613 for rows.Next() {
614 var f Honker
615 var combos string
616 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
617 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
618 if err != nil {
619 log.Printf("error scanning honker: %s", err)
620 return nil
621 }
622 honkers = append(honkers, &f)
623 }
624 return honkers
625}
626
627func getdubs(userid int64) []*Honker {
628 rows, err := stmtDubbers.Query(userid)
629 if err != nil {
630 log.Printf("error querying dubs: %s", err)
631 return nil
632 }
633 defer rows.Close()
634 var honkers []*Honker
635 for rows.Next() {
636 var f Honker
637 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
638 if err != nil {
639 log.Printf("error scanning honker: %s", err)
640 return nil
641 }
642 honkers = append(honkers, &f)
643 }
644 return honkers
645}
646
647func allusers() []login.UserInfo {
648 var users []login.UserInfo
649 rows, _ := opendatabase().Query("select userid, username from users")
650 defer rows.Close()
651 for rows.Next() {
652 var u login.UserInfo
653 rows.Scan(&u.UserID, &u.Username)
654 users = append(users, u)
655 }
656 return users
657}
658
659func getxonk(userid int64, xid string) *Honk {
660 h := new(Honk)
661 var dt, aud string
662 row := stmtOneXonk.QueryRow(userid, xid)
663 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
664 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore)
665 if err != nil {
666 if err != sql.ErrNoRows {
667 log.Printf("error scanning xonk: %s", err)
668 }
669 return nil
670 }
671 h.Date, _ = time.Parse(dbtimeformat, dt)
672 h.Audience = strings.Split(aud, " ")
673 h.Public = !keepitquiet(h.Audience)
674 return h
675}
676
677func getpublichonks() []*Honk {
678 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
679 rows, err := stmtPublicHonks.Query(dt)
680 return getsomehonks(rows, err)
681}
682func gethonksbyuser(name string, includeprivate bool) []*Honk {
683 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
684 whofore := 2
685 if includeprivate {
686 whofore = 3
687 }
688 rows, err := stmtUserHonks.Query(whofore, name, dt)
689 return getsomehonks(rows, err)
690}
691func gethonksforuser(userid int64) []*Honk {
692 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
693 rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
694 return getsomehonks(rows, err)
695}
696func gethonksforme(userid int64) []*Honk {
697 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
698 rows, err := stmtHonksForMe.Query(userid, dt, userid)
699 return getsomehonks(rows, err)
700}
701func gethonksbyhonker(userid int64, honker string) []*Honk {
702 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
703 return getsomehonks(rows, err)
704}
705func gethonksbyxonker(userid int64, xonker string) []*Honk {
706 rows, err := stmtHonksByXonker.Query(userid, xonker, userid)
707 return getsomehonks(rows, err)
708}
709func gethonksbycombo(userid int64, combo string) []*Honk {
710 combo = "% " + combo + " %"
711 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
712 return getsomehonks(rows, err)
713}
714func gethonksbyconvoy(userid int64, convoy string) []*Honk {
715 rows, err := stmtHonksByConvoy.Query(userid, convoy)
716 honks := getsomehonks(rows, err)
717 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
718 honks[i], honks[j] = honks[j], honks[i]
719 }
720 return honks
721}
722
723func getsomehonks(rows *sql.Rows, err error) []*Honk {
724 if err != nil {
725 log.Printf("error querying honks: %s", err)
726 return nil
727 }
728 defer rows.Close()
729 var honks []*Honk
730 for rows.Next() {
731 var h Honk
732 var dt, aud string
733 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker,
734 &h.XID, &h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore)
735 if err != nil {
736 log.Printf("error scanning honks: %s", err)
737 return nil
738 }
739 h.Date, _ = time.Parse(dbtimeformat, dt)
740 h.Audience = strings.Split(aud, " ")
741 h.Public = !keepitquiet(h.Audience)
742 honks = append(honks, &h)
743 }
744 rows.Close()
745 donksforhonks(honks)
746 return honks
747}
748
749func donksforhonks(honks []*Honk) {
750 db := opendatabase()
751 var ids []string
752 hmap := make(map[int64]*Honk)
753 for _, h := range honks {
754 ids = append(ids, fmt.Sprintf("%d", h.ID))
755 hmap[h.ID] = h
756 }
757 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, ","))
758 rows, err := db.Query(q)
759 if err != nil {
760 log.Printf("error querying donks: %s", err)
761 return
762 }
763 defer rows.Close()
764 for rows.Next() {
765 var hid int64
766 var d Donk
767 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media, &d.Local)
768 if err != nil {
769 log.Printf("error scanning donk: %s", err)
770 continue
771 }
772 h := hmap[hid]
773 h.Donks = append(h.Donks, &d)
774 }
775}
776
777func savebonk(w http.ResponseWriter, r *http.Request) {
778 xid := r.FormValue("xid")
779 userinfo := login.GetUserInfo(r)
780 user, _ := butwhatabout(userinfo.Username)
781
782 log.Printf("bonking %s", xid)
783
784 xonk := getxonk(userinfo.UserID, xid)
785 if xonk == nil {
786 return
787 }
788 if !xonk.Public {
789 return
790 }
791 donksforhonks([]*Honk{xonk})
792
793 dt := time.Now().UTC()
794 bonk := Honk{
795 UserID: userinfo.UserID,
796 Username: userinfo.Username,
797 What: "bonk",
798 Honker: user.URL,
799 XID: xonk.XID,
800 Date: dt,
801 Donks: xonk.Donks,
802 Audience: []string{thewholeworld},
803 Public: true,
804 }
805
806 oonker := xonk.Oonker
807 if oonker == "" {
808 oonker = xonk.Honker
809 }
810 aud := strings.Join(bonk.Audience, " ")
811 whofore := 2
812 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
813 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
814 xonk.Precis, oonker)
815 if err != nil {
816 log.Printf("error saving bonk: %s", err)
817 return
818 }
819 bonk.ID, _ = res.LastInsertId()
820 for _, d := range bonk.Donks {
821 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
822 if err != nil {
823 log.Printf("err saving donk: %s", err)
824 return
825 }
826 }
827
828 go honkworldwide(user, &bonk)
829}
830
831func zonkit(w http.ResponseWriter, r *http.Request) {
832 wherefore := r.FormValue("wherefore")
833 what := r.FormValue("what")
834 switch wherefore {
835 case "zonk":
836 case "zonvoy":
837 }
838
839 log.Printf("zonking %s %s", wherefore, what)
840 userinfo := login.GetUserInfo(r)
841 if wherefore == "zonk" {
842 xonk := getxonk(userinfo.UserID, what)
843 if xonk != nil {
844 stmtZonkDonks.Exec(xonk.ID)
845 stmtZonkIt.Exec(userinfo.UserID, what)
846 if xonk.Whofore == 2 || xonk.Whofore == 3 {
847 zonk := Honk{
848 What: "zonk",
849 XID: xonk.XID,
850 Date: time.Now().UTC(),
851 Audience: oneofakind(xonk.Audience),
852 }
853
854 user, _ := butwhatabout(userinfo.Username)
855 log.Printf("announcing deleted honk: %s", what)
856 go honkworldwide(user, &zonk)
857 }
858 }
859 }
860 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
861 if err != nil {
862 log.Printf("error saving zonker: %s", err)
863 return
864 }
865}
866
867func savehonk(w http.ResponseWriter, r *http.Request) {
868 rid := r.FormValue("rid")
869 noise := r.FormValue("noise")
870
871 userinfo := login.GetUserInfo(r)
872 user, _ := butwhatabout(userinfo.Username)
873
874 dt := time.Now().UTC()
875 xid := fmt.Sprintf("https://%s/u/%s/h/%s", serverName, userinfo.Username, xfiltrate())
876 what := "honk"
877 if rid != "" {
878 what = "tonk"
879 }
880 honk := Honk{
881 UserID: userinfo.UserID,
882 Username: userinfo.Username,
883 What: "honk",
884 Honker: user.URL,
885 XID: xid,
886 Date: dt,
887 }
888 if strings.HasPrefix(noise, "DZ:") {
889 idx := strings.Index(noise, "\n")
890 if idx == -1 {
891 honk.Precis = noise
892 noise = ""
893 } else {
894 honk.Precis = noise[:idx]
895 noise = noise[idx+1:]
896 }
897 }
898 noise = hooterize(noise)
899 noise = strings.TrimSpace(noise)
900 honk.Precis = strings.TrimSpace(honk.Precis)
901
902 var convoy string
903 if rid != "" {
904 xonk := getxonk(userinfo.UserID, rid)
905 if xonk != nil {
906 if xonk.Public {
907 honk.Audience = append(honk.Audience, xonk.Audience...)
908 }
909 convoy = xonk.Convoy
910 } else {
911 xonkaud, c := whosthere(rid)
912 honk.Audience = append(honk.Audience, xonkaud...)
913 convoy = c
914 }
915 for i, a := range honk.Audience {
916 if a == thewholeworld {
917 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
918 break
919 }
920 }
921 honk.RID = rid
922 } else {
923 honk.Audience = []string{thewholeworld}
924 }
925 if noise != "" && noise[0] == '@' {
926 honk.Audience = append(grapevine(noise), honk.Audience...)
927 } else {
928 honk.Audience = append(honk.Audience, grapevine(noise)...)
929 }
930 if convoy == "" {
931 convoy = "data:,electrichonkytonk-" + xfiltrate()
932 }
933 butnottooloud(honk.Audience)
934 honk.Audience = oneofakind(honk.Audience)
935 if len(honk.Audience) == 0 {
936 log.Printf("honk to nowhere")
937 return
938 }
939 honk.Public = !keepitquiet(honk.Audience)
940 noise = obfusbreak(noise)
941 honk.Noise = noise
942 honk.Convoy = convoy
943
944 file, filehdr, err := r.FormFile("donk")
945 if err == nil {
946 var buf bytes.Buffer
947 io.Copy(&buf, file)
948 file.Close()
949 data := buf.Bytes()
950 xid := xfiltrate()
951 var media, name string
952 img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
953 if err == nil {
954 data = img.Data
955 format := img.Format
956 media = "image/" + format
957 if format == "jpeg" {
958 format = "jpg"
959 }
960 name = xid + "." + format
961 xid = name
962 } else {
963 maxsize := 100000
964 if len(data) > maxsize {
965 log.Printf("bad image: %s too much text: %d", err, len(data))
966 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
967 return
968 }
969 for i := 0; i < len(data); i++ {
970 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
971 log.Printf("bad image: %s not text: %d", err, data[i])
972 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
973 return
974 }
975 }
976 media = "text/plain"
977 name = filehdr.Filename
978 if name == "" {
979 name = xid + ".txt"
980 }
981 xid += ".txt"
982 }
983 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
984 res, err := stmtSaveFile.Exec(xid, name, url, media, 1, data)
985 if err != nil {
986 log.Printf("unable to save image: %s", err)
987 return
988 }
989 var d Donk
990 d.FileID, _ = res.LastInsertId()
991 d.XID = name
992 d.Name = name
993 d.Media = media
994 d.URL = url
995 d.Local = true
996 honk.Donks = append(honk.Donks, &d)
997 }
998 herd := herdofemus(honk.Noise)
999 for _, e := range herd {
1000 donk := savedonk(e.ID, e.Name, "image/png", true)
1001 if donk != nil {
1002 donk.Name = e.Name
1003 honk.Donks = append(honk.Donks, donk)
1004 }
1005 }
1006 honk.Donks = append(honk.Donks, memetics(honk.Noise)...)
1007
1008 aud := strings.Join(honk.Audience, " ")
1009 whofore := 2
1010 if !honk.Public {
1011 whofore = 3
1012 }
1013 if r.FormValue("preview") == "preview" {
1014 honks := []*Honk{&honk}
1015 reverbolate(honks)
1016 templinfo := getInfo(r)
1017 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1018 templinfo["Honks"] = honks
1019 templinfo["Noise"] = r.FormValue("noise")
1020 templinfo["ServerMessage"] = "honk preview"
1021 err := readviews.Execute(w, "honkpage.html", templinfo)
1022 if err != nil {
1023 log.Print(err)
1024 }
1025 return
1026 }
1027 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
1028 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
1029 if err != nil {
1030 log.Printf("error saving honk: %s", err)
1031 return
1032 }
1033 honk.ID, _ = res.LastInsertId()
1034 for _, d := range honk.Donks {
1035 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
1036 if err != nil {
1037 log.Printf("err saving donk: %s", err)
1038 return
1039 }
1040 }
1041
1042 go honkworldwide(user, &honk)
1043
1044 http.Redirect(w, r, "/", http.StatusSeeOther)
1045}
1046
1047func showhonkers(w http.ResponseWriter, r *http.Request) {
1048 userinfo := login.GetUserInfo(r)
1049 templinfo := getInfo(r)
1050 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1051 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1052 err := readviews.Execute(w, "honkers.html", templinfo)
1053 if err != nil {
1054 log.Print(err)
1055 }
1056}
1057
1058func showcombos(w http.ResponseWriter, r *http.Request) {
1059 userinfo := login.GetUserInfo(r)
1060 templinfo := getInfo(r)
1061 honkers := gethonkers(userinfo.UserID)
1062 var combos []string
1063 for _, h := range honkers {
1064 combos = append(combos, h.Combos...)
1065 }
1066 for i, c := range combos {
1067 if c == "-" {
1068 combos[i] = ""
1069 }
1070 }
1071 combos = oneofakind(combos)
1072 sort.Strings(combos)
1073 templinfo["Combos"] = combos
1074 err := readviews.Execute(w, "combos.html", templinfo)
1075 if err != nil {
1076 log.Print(err)
1077 }
1078}
1079
1080func savehonker(w http.ResponseWriter, r *http.Request) {
1081 u := login.GetUserInfo(r)
1082 name := r.FormValue("name")
1083 url := r.FormValue("url")
1084 peep := r.FormValue("peep")
1085 combos := r.FormValue("combos")
1086 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1087
1088 if honkerid > 0 {
1089 goodbye := r.FormValue("goodbye")
1090 if goodbye == "F" {
1091 db := opendatabase()
1092 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1093 honkerid, u.UserID)
1094 var xid string
1095 err := row.Scan(&xid)
1096 if err != nil {
1097 log.Printf("can't get honker xid: %s", err)
1098 return
1099 }
1100 log.Printf("unsubscribing from %s", xid)
1101 user, _ := butwhatabout(u.Username)
1102 go itakeitallback(user, xid)
1103 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1104 if err != nil {
1105 log.Printf("error updating honker: %s", err)
1106 return
1107 }
1108
1109 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1110 return
1111 }
1112 combos = " " + strings.TrimSpace(combos) + " "
1113 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1114 if err != nil {
1115 log.Printf("update honker err: %s", err)
1116 return
1117 }
1118 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1119 }
1120
1121 flavor := "presub"
1122 if peep == "peep" {
1123 flavor = "peep"
1124 }
1125 url = investigate(url)
1126 if url == "" {
1127 return
1128 }
1129 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1130 if err != nil {
1131 log.Print(err)
1132 return
1133 }
1134 if flavor == "presub" {
1135 user, _ := butwhatabout(u.Username)
1136 go subsub(user, url)
1137 }
1138 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1139}
1140
1141type Zonker struct {
1142 ID int64
1143 Name string
1144 Wherefore string
1145}
1146
1147func zonkzone(w http.ResponseWriter, r *http.Request) {
1148 userinfo := login.GetUserInfo(r)
1149 rows, err := stmtGetZonkers.Query(userinfo.UserID)
1150 if err != nil {
1151 log.Printf("err: %s", err)
1152 return
1153 }
1154 defer rows.Close()
1155 var zonkers []Zonker
1156 for rows.Next() {
1157 var z Zonker
1158 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1159 zonkers = append(zonkers, z)
1160 }
1161 sort.Slice(zonkers, func(i, j int) bool {
1162 w1 := zonkers[i].Wherefore
1163 w2 := zonkers[j].Wherefore
1164 if w1 == w2 {
1165 return zonkers[i].Name < zonkers[j].Name
1166 }
1167 if w1 == "zonvoy" {
1168 w1 = "zzzzzzz"
1169 }
1170 if w2 == "zonvoy" {
1171 w2 = "zzzzzzz"
1172 }
1173 return w1 < w2
1174 })
1175
1176 templinfo := getInfo(r)
1177 templinfo["Zonkers"] = zonkers
1178 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1179 err = readviews.Execute(w, "zonkers.html", templinfo)
1180 if err != nil {
1181 log.Print(err)
1182 }
1183}
1184
1185func zonkzonk(w http.ResponseWriter, r *http.Request) {
1186 userinfo := login.GetUserInfo(r)
1187 itsok := r.FormValue("itsok")
1188 if itsok == "iforgiveyou" {
1189 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1190 db := opendatabase()
1191 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1192 userinfo.UserID, zonkerid)
1193 bitethethumbs()
1194 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1195 return
1196 }
1197 wherefore := r.FormValue("wherefore")
1198 name := r.FormValue("name")
1199 if name == "" {
1200 return
1201 }
1202 switch wherefore {
1203 case "zonker":
1204 case "zomain":
1205 case "zonvoy":
1206 case "zord":
1207 default:
1208 return
1209 }
1210 db := opendatabase()
1211 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1212 userinfo.UserID, name, wherefore)
1213 if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" {
1214 bitethethumbs()
1215 }
1216
1217 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1218}
1219
1220func accountpage(w http.ResponseWriter, r *http.Request) {
1221 u := login.GetUserInfo(r)
1222 user, _ := butwhatabout(u.Username)
1223 templinfo := getInfo(r)
1224 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1225 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1226 templinfo["WhatAbout"] = user.About
1227 err := readviews.Execute(w, "account.html", templinfo)
1228 if err != nil {
1229 log.Print(err)
1230 }
1231}
1232
1233func dochpass(w http.ResponseWriter, r *http.Request) {
1234 err := login.ChangePassword(w, r)
1235 if err != nil {
1236 log.Printf("error changing password: %s", err)
1237 }
1238 http.Redirect(w, r, "/account", http.StatusSeeOther)
1239}
1240
1241func fingerlicker(w http.ResponseWriter, r *http.Request) {
1242 orig := r.FormValue("resource")
1243
1244 log.Printf("finger lick: %s", orig)
1245
1246 if strings.HasPrefix(orig, "acct:") {
1247 orig = orig[5:]
1248 }
1249
1250 name := orig
1251 idx := strings.LastIndexByte(name, '/')
1252 if idx != -1 {
1253 name = name[idx+1:]
1254 if "https://"+serverName+"/u/"+name != orig {
1255 log.Printf("foreign request rejected")
1256 name = ""
1257 }
1258 } else {
1259 idx = strings.IndexByte(name, '@')
1260 if idx != -1 {
1261 name = name[:idx]
1262 if name+"@"+serverName != orig {
1263 log.Printf("foreign request rejected")
1264 name = ""
1265 }
1266 }
1267 }
1268 user, err := butwhatabout(name)
1269 if err != nil {
1270 http.NotFound(w, r)
1271 return
1272 }
1273
1274 j := junk.New()
1275 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1276 j["aliases"] = []string{user.URL}
1277 var links []map[string]interface{}
1278 l := junk.New()
1279 l["rel"] = "self"
1280 l["type"] = `application/activity+json`
1281 l["href"] = user.URL
1282 links = append(links, l)
1283 j["links"] = links
1284
1285 w.Header().Set("Cache-Control", "max-age=3600")
1286 w.Header().Set("Content-Type", "application/jrd+json")
1287 j.Write(w)
1288}
1289
1290func somedays() string {
1291 secs := 432000 + notrand.Int63n(432000)
1292 return fmt.Sprintf("%d", secs)
1293}
1294
1295func avatate(w http.ResponseWriter, r *http.Request) {
1296 n := r.FormValue("a")
1297 a := avatar(n)
1298 w.Header().Set("Cache-Control", "max-age="+somedays())
1299 w.Write(a)
1300}
1301
1302func servecss(w http.ResponseWriter, r *http.Request) {
1303 w.Header().Set("Cache-Control", "max-age=7776000")
1304 http.ServeFile(w, r, "views"+r.URL.Path)
1305}
1306func servehtml(w http.ResponseWriter, r *http.Request) {
1307 templinfo := getInfo(r)
1308 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1309 if err != nil {
1310 log.Print(err)
1311 }
1312}
1313func serveemu(w http.ResponseWriter, r *http.Request) {
1314 xid := mux.Vars(r)["xid"]
1315 w.Header().Set("Cache-Control", "max-age="+somedays())
1316 http.ServeFile(w, r, "emus/"+xid)
1317}
1318func servememe(w http.ResponseWriter, r *http.Request) {
1319 xid := mux.Vars(r)["xid"]
1320 w.Header().Set("Cache-Control", "max-age="+somedays())
1321 http.ServeFile(w, r, "memes/"+xid)
1322}
1323
1324func servefile(w http.ResponseWriter, r *http.Request) {
1325 xid := mux.Vars(r)["xid"]
1326 row := stmtFileData.QueryRow(xid)
1327 var media string
1328 var data []byte
1329 err := row.Scan(&media, &data)
1330 if err != nil {
1331 log.Printf("error loading file: %s", err)
1332 http.NotFound(w, r)
1333 return
1334 }
1335 w.Header().Set("Content-Type", media)
1336 w.Header().Set("X-Content-Type-Options", "nosniff")
1337 w.Header().Set("Cache-Control", "max-age="+somedays())
1338 w.Write(data)
1339}
1340
1341func nomoroboto(w http.ResponseWriter, r *http.Request) {
1342 io.WriteString(w, "User-agent: *\n")
1343 io.WriteString(w, "Disallow: /t\n")
1344 for _, u := range allusers() {
1345 fmt.Fprintf(w, "Disallow: /u/%s/h/\n", u.Username)
1346 }
1347}
1348
1349func serve() {
1350 db := opendatabase()
1351 login.Init(db)
1352
1353 listener, err := openListener()
1354 if err != nil {
1355 log.Fatal(err)
1356 }
1357 go redeliverator()
1358
1359 debug := false
1360 getconfig("debug", &debug)
1361 readviews = templates.Load(debug,
1362 "views/honkpage.html",
1363 "views/honkers.html",
1364 "views/zonkers.html",
1365 "views/combos.html",
1366 "views/honkform.html",
1367 "views/honk.html",
1368 "views/account.html",
1369 "views/about.html",
1370 "views/funzone.html",
1371 "views/login.html",
1372 "views/xzone.html",
1373 "views/header.html",
1374 )
1375 if !debug {
1376 s := "views/style.css"
1377 savedstyleparams[s] = getstyleparam(s)
1378 s = "views/local.css"
1379 savedstyleparams[s] = getstyleparam(s)
1380 }
1381
1382 bitethethumbs()
1383
1384 mux := mux.NewRouter()
1385 mux.Use(login.Checker)
1386
1387 posters := mux.Methods("POST").Subrouter()
1388 getters := mux.Methods("GET").Subrouter()
1389
1390 getters.HandleFunc("/", homepage)
1391 getters.HandleFunc("/front", homepage)
1392 getters.HandleFunc("/robots.txt", nomoroboto)
1393 getters.HandleFunc("/rss", showrss)
1394 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1395 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1396 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1397 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1398 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1399 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1400 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1401 getters.HandleFunc("/a", avatate)
1402 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1403 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1404 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1405 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1406
1407 getters.HandleFunc("/style.css", servecss)
1408 getters.HandleFunc("/local.css", servecss)
1409 getters.HandleFunc("/about", servehtml)
1410 getters.HandleFunc("/login", servehtml)
1411 posters.HandleFunc("/dologin", login.LoginFunc)
1412 getters.HandleFunc("/logout", login.LogoutFunc)
1413
1414 loggedin := mux.NewRoute().Subrouter()
1415 loggedin.Use(login.Required)
1416 loggedin.HandleFunc("/account", accountpage)
1417 loggedin.HandleFunc("/funzone", showfunzone)
1418 loggedin.HandleFunc("/chpass", dochpass)
1419 loggedin.HandleFunc("/atme", homepage)
1420 loggedin.HandleFunc("/zonkzone", zonkzone)
1421 loggedin.HandleFunc("/xzone", xzone)
1422 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1423 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1424 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1425 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1426 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1427 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1428 loggedin.HandleFunc("/honkers", showhonkers)
1429 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1430 loggedin.HandleFunc("/h", showhonker)
1431 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1432 loggedin.HandleFunc("/c", showcombos)
1433 loggedin.HandleFunc("/t", showconvoy)
1434 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1435
1436 err = http.Serve(listener, mux)
1437 if err != nil {
1438 log.Fatal(err)
1439 }
1440}
1441
1442func cleanupdb(days int) {
1443 db := opendatabase()
1444 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1445 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)
1446 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)
1447 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1448}
1449
1450func reducedb(honker string) {
1451 db := opendatabase()
1452 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1453 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1454 doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1455 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1456}
1457
1458var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1459var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1460var stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1461var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1462var stmtFindZonk, stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1463var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1464var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1465var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker *sql.Stmt
1466
1467func preparetodie(db *sql.DB, s string) *sql.Stmt {
1468 stmt, err := db.Prepare(s)
1469 if err != nil {
1470 log.Fatalf("error %s: %s", err, s)
1471 }
1472 return stmt
1473}
1474
1475func prepareStatements(db *sql.DB) {
1476 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")
1477 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1478 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1479 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1480 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1481 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1482
1483 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 "
1484 limit := " order by honkid desc limit 250"
1485 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1486 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1487 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1488 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1489 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)
1490 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1491 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1492 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and honker = ?"+butnotthose+limit)
1493 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1494 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or whofore = 2) and convoy = ?"+limit)
1495
1496 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1497 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1498 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1499 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1500 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1501 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1502 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1503 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1504 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1505 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1506 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1507 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1508 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1509 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1510 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zomain' or wherefore = 'zord')")
1511 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1512 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1513 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1514 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1515 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1516 stmtRecentHonkers = preparetodie(db, "select distinct(honker) from honks where userid = ? order by honkid desc limit 100")
1517}
1518
1519func ElaborateUnitTests() {
1520}
1521
1522func main() {
1523 var err error
1524 cmd := "run"
1525 if len(os.Args) > 1 {
1526 cmd = os.Args[1]
1527 }
1528 switch cmd {
1529 case "init":
1530 initdb()
1531 case "upgrade":
1532 upgradedb()
1533 }
1534 db := opendatabase()
1535 dbversion := 0
1536 getconfig("dbversion", &dbversion)
1537 if dbversion != myVersion {
1538 log.Fatal("incorrect database version. run upgrade.")
1539 }
1540 getconfig("servermsg", &serverMsg)
1541 getconfig("servername", &serverName)
1542 prepareStatements(db)
1543 switch cmd {
1544 case "adduser":
1545 adduser()
1546 case "cleanup":
1547 days := 30
1548 if len(os.Args) > 2 {
1549 days, err = strconv.Atoi(os.Args[2])
1550 if err != nil {
1551 log.Fatal(err)
1552 }
1553 }
1554 cleanupdb(days)
1555 case "reduce":
1556 if len(os.Args) < 3 {
1557 log.Fatal("need a honker name")
1558 }
1559 reducedb(os.Args[2])
1560 case "ping":
1561 if len(os.Args) < 4 {
1562 fmt.Printf("usage: honk ping from to\n")
1563 return
1564 }
1565 name := os.Args[2]
1566 targ := os.Args[3]
1567 user, err := butwhatabout(name)
1568 if err != nil {
1569 log.Printf("unknown user")
1570 return
1571 }
1572 ping(user, targ)
1573 case "peep":
1574 peeppeep()
1575 case "run":
1576 serve()
1577 case "test":
1578 ElaborateUnitTests()
1579 default:
1580 log.Fatal("unknown command")
1581 }
1582}