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