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