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 templinfo := getInfo(r)
1116 templinfo["Zonkers"] = zonkers
1117 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1118 err = readviews.Execute(w, "zonkers.html", templinfo)
1119 if err != nil {
1120 log.Print(err)
1121 }
1122}
1123
1124func zonkzonk(w http.ResponseWriter, r *http.Request) {
1125 userinfo := login.GetUserInfo(r)
1126 itsok := r.FormValue("itsok")
1127 if itsok == "iforgiveyou" {
1128 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1129 db := opendatabase()
1130 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1131 userinfo.UserID, zonkerid)
1132 bitethethumbs()
1133 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1134 return
1135 }
1136 wherefore := r.FormValue("wherefore")
1137 name := r.FormValue("name")
1138 if name == "" {
1139 return
1140 }
1141 switch wherefore {
1142 case "zonker":
1143 case "zurl":
1144 case "zonvoy":
1145 case "zword":
1146 default:
1147 return
1148 }
1149 db := opendatabase()
1150 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1151 userinfo.UserID, name, wherefore)
1152 if wherefore == "zonker" || wherefore == "zurl" || wherefore == "zword" {
1153 bitethethumbs()
1154 }
1155
1156 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1157}
1158
1159func accountpage(w http.ResponseWriter, r *http.Request) {
1160 u := login.GetUserInfo(r)
1161 user, _ := butwhatabout(u.Username)
1162 templinfo := getInfo(r)
1163 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1164 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1165 templinfo["WhatAbout"] = user.About
1166 err := readviews.Execute(w, "account.html", templinfo)
1167 if err != nil {
1168 log.Print(err)
1169 }
1170}
1171
1172func dochpass(w http.ResponseWriter, r *http.Request) {
1173 err := login.ChangePassword(w, r)
1174 if err != nil {
1175 log.Printf("error changing password: %s", err)
1176 }
1177 http.Redirect(w, r, "/account", http.StatusSeeOther)
1178}
1179
1180func fingerlicker(w http.ResponseWriter, r *http.Request) {
1181 orig := r.FormValue("resource")
1182
1183 log.Printf("finger lick: %s", orig)
1184
1185 if strings.HasPrefix(orig, "acct:") {
1186 orig = orig[5:]
1187 }
1188
1189 name := orig
1190 idx := strings.LastIndexByte(name, '/')
1191 if idx != -1 {
1192 name = name[idx+1:]
1193 if "https://"+serverName+"/u/"+name != orig {
1194 log.Printf("foreign request rejected")
1195 name = ""
1196 }
1197 } else {
1198 idx = strings.IndexByte(name, '@')
1199 if idx != -1 {
1200 name = name[:idx]
1201 if name+"@"+serverName != orig {
1202 log.Printf("foreign request rejected")
1203 name = ""
1204 }
1205 }
1206 }
1207 user, err := butwhatabout(name)
1208 if err != nil {
1209 http.NotFound(w, r)
1210 return
1211 }
1212
1213 j := junk.New()
1214 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1215 j["aliases"] = []string{user.URL}
1216 var links []map[string]interface{}
1217 l := junk.New()
1218 l["rel"] = "self"
1219 l["type"] = `application/activity+json`
1220 l["href"] = user.URL
1221 links = append(links, l)
1222 j["links"] = links
1223
1224 w.Header().Set("Cache-Control", "max-age=3600")
1225 w.Header().Set("Content-Type", "application/jrd+json")
1226 j.Write(w)
1227}
1228
1229func somedays() string {
1230 secs := 432000 + notrand.Int63n(432000)
1231 return fmt.Sprintf("%d", secs)
1232}
1233
1234func avatate(w http.ResponseWriter, r *http.Request) {
1235 n := r.FormValue("a")
1236 a := avatar(n)
1237 w.Header().Set("Cache-Control", "max-age="+somedays())
1238 w.Write(a)
1239}
1240
1241func servecss(w http.ResponseWriter, r *http.Request) {
1242 w.Header().Set("Cache-Control", "max-age=7776000")
1243 http.ServeFile(w, r, "views"+r.URL.Path)
1244}
1245func servehtml(w http.ResponseWriter, r *http.Request) {
1246 templinfo := getInfo(r)
1247 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1248 if err != nil {
1249 log.Print(err)
1250 }
1251}
1252func serveemu(w http.ResponseWriter, r *http.Request) {
1253 xid := mux.Vars(r)["xid"]
1254 w.Header().Set("Cache-Control", "max-age="+somedays())
1255 http.ServeFile(w, r, "emus/"+xid)
1256}
1257func servememe(w http.ResponseWriter, r *http.Request) {
1258 xid := mux.Vars(r)["xid"]
1259 w.Header().Set("Cache-Control", "max-age="+somedays())
1260 http.ServeFile(w, r, "memes/"+xid)
1261}
1262
1263func servefile(w http.ResponseWriter, r *http.Request) {
1264 xid := mux.Vars(r)["xid"]
1265 row := stmtFileData.QueryRow(xid)
1266 var media string
1267 var data []byte
1268 err := row.Scan(&media, &data)
1269 if err != nil {
1270 log.Printf("error loading file: %s", err)
1271 http.NotFound(w, r)
1272 return
1273 }
1274 w.Header().Set("Content-Type", media)
1275 w.Header().Set("X-Content-Type-Options", "nosniff")
1276 w.Header().Set("Cache-Control", "max-age="+somedays())
1277 w.Write(data)
1278}
1279
1280func serve() {
1281 db := opendatabase()
1282 login.Init(db)
1283
1284 listener, err := openListener()
1285 if err != nil {
1286 log.Fatal(err)
1287 }
1288 go redeliverator()
1289
1290 debug := false
1291 getconfig("debug", &debug)
1292 readviews = templates.Load(debug,
1293 "views/honkpage.html",
1294 "views/honkers.html",
1295 "views/zonkers.html",
1296 "views/combos.html",
1297 "views/honkform.html",
1298 "views/honk.html",
1299 "views/account.html",
1300 "views/about.html",
1301 "views/login.html",
1302 "views/xzone.html",
1303 "views/header.html",
1304 )
1305 if !debug {
1306 s := "views/style.css"
1307 savedstyleparams[s] = getstyleparam(s)
1308 s = "views/local.css"
1309 savedstyleparams[s] = getstyleparam(s)
1310 }
1311
1312 bitethethumbs()
1313
1314 mux := mux.NewRouter()
1315 mux.Use(login.Checker)
1316
1317 posters := mux.Methods("POST").Subrouter()
1318 getters := mux.Methods("GET").Subrouter()
1319
1320 getters.HandleFunc("/", homepage)
1321 getters.HandleFunc("/rss", showrss)
1322 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1323 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1324 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1325 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1326 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1327 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1328 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1329 getters.HandleFunc("/a", avatate)
1330 getters.HandleFunc("/t", showconvoy)
1331 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1332 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1333 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1334 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1335
1336 getters.HandleFunc("/style.css", servecss)
1337 getters.HandleFunc("/local.css", servecss)
1338 getters.HandleFunc("/about", servehtml)
1339 getters.HandleFunc("/login", servehtml)
1340 posters.HandleFunc("/dologin", login.LoginFunc)
1341 getters.HandleFunc("/logout", login.LogoutFunc)
1342
1343 loggedin := mux.NewRoute().Subrouter()
1344 loggedin.Use(login.Required)
1345 loggedin.HandleFunc("/account", accountpage)
1346 loggedin.HandleFunc("/chpass", dochpass)
1347 loggedin.HandleFunc("/atme", homepage)
1348 loggedin.HandleFunc("/zonkzone", zonkzone)
1349 loggedin.HandleFunc("/xzone", xzone)
1350 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1351 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1352 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1353 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1354 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1355 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1356 loggedin.HandleFunc("/honkers", showhonkers)
1357 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1358 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1359 loggedin.HandleFunc("/c", showcombos)
1360 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1361
1362 err = http.Serve(listener, mux)
1363 if err != nil {
1364 log.Fatal(err)
1365 }
1366}
1367
1368func cleanupdb(days int) {
1369 db := opendatabase()
1370 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1371 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)
1372 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)
1373 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1374}
1375
1376func reducedb(honker string) {
1377 db := opendatabase()
1378 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1379 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1380 doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1381 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1382}
1383
1384var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1385var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1386var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1387var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1388var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1389var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1390var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1391var stmtGetXonker, stmtSaveXonker *sql.Stmt
1392
1393func preparetodie(db *sql.DB, s string) *sql.Stmt {
1394 stmt, err := db.Prepare(s)
1395 if err != nil {
1396 log.Fatalf("error %s: %s", err, s)
1397 }
1398 return stmt
1399}
1400
1401func prepareStatements(db *sql.DB) {
1402 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")
1403 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1404 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1405 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1406 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1407 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1408
1409 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 "
1410 limit := " order by honkid desc limit 250"
1411 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1412 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1413 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1414 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1415 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)
1416 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1417 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1418 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1419 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or whofore = 2) and convoy = ?"+limit)
1420
1421 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1422 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1423 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1424 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1425 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1426 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1427 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1428 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1429 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1430 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1431 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1432 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1433 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1434 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1435 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl' or wherefore = 'zword')")
1436 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1437 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1438 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1439}
1440
1441func ElaborateUnitTests() {
1442}
1443
1444func main() {
1445 var err error
1446 cmd := "run"
1447 if len(os.Args) > 1 {
1448 cmd = os.Args[1]
1449 }
1450 switch cmd {
1451 case "init":
1452 initdb()
1453 case "upgrade":
1454 upgradedb()
1455 }
1456 db := opendatabase()
1457 dbversion := 0
1458 getconfig("dbversion", &dbversion)
1459 if dbversion != myVersion {
1460 log.Fatal("incorrect database version. run upgrade.")
1461 }
1462 getconfig("servermsg", &serverMsg)
1463 getconfig("servername", &serverName)
1464 prepareStatements(db)
1465 switch cmd {
1466 case "adduser":
1467 adduser()
1468 case "cleanup":
1469 days := 30
1470 if len(os.Args) > 2 {
1471 days, err = strconv.Atoi(os.Args[2])
1472 if err != nil {
1473 log.Fatal(err)
1474 }
1475 }
1476 cleanupdb(days)
1477 case "reduce":
1478 if len(os.Args) < 3 {
1479 log.Fatal("need a honker name")
1480 }
1481 reducedb(os.Args[2])
1482 case "ping":
1483 if len(os.Args) < 4 {
1484 fmt.Printf("usage: honk ping from to\n")
1485 return
1486 }
1487 name := os.Args[2]
1488 targ := os.Args[3]
1489 user, err := butwhatabout(name)
1490 if err != nil {
1491 log.Printf("unknown user")
1492 return
1493 }
1494 ping(user, targ)
1495 case "peep":
1496 peeppeep()
1497 case "run":
1498 serve()
1499 case "test":
1500 ElaborateUnitTests()
1501 default:
1502 log.Fatal("unknown command")
1503 }
1504}