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