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