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