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 }
718
719 oonker := xonk.Oonker
720 if oonker == "" {
721 oonker = xonk.Honker
722 }
723 aud := strings.Join(bonk.Audience, " ")
724 whofore := 2
725 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
726 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
727 xonk.Precis, oonker)
728 if err != nil {
729 log.Printf("error saving bonk: %s", err)
730 return
731 }
732 bonk.ID, _ = res.LastInsertId()
733 for _, d := range bonk.Donks {
734 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
735 if err != nil {
736 log.Printf("err saving donk: %s", err)
737 return
738 }
739 }
740
741 go honkworldwide(user, &bonk)
742}
743
744func zonkit(w http.ResponseWriter, r *http.Request) {
745 wherefore := r.FormValue("wherefore")
746 var what string
747 switch wherefore {
748 case "this honk":
749 what = r.FormValue("honk")
750 wherefore = "zonk"
751 case "this honker":
752 what = r.FormValue("honker")
753 wherefore = "zonker"
754 case "this convoy":
755 what = r.FormValue("convoy")
756 wherefore = "zonvoy"
757 }
758 if what == "" {
759 return
760 }
761
762 log.Printf("zonking %s %s", wherefore, what)
763 userinfo := login.GetUserInfo(r)
764 if wherefore == "zonk" {
765 xonk := getxonk(userinfo.UserID, what)
766 if xonk != nil {
767 stmtZonkDonks.Exec(xonk.ID)
768 stmtZonkIt.Exec(userinfo.UserID, what)
769 if xonk.Whofore == 2 || xonk.Whofore == 3 {
770 zonk := Honk{
771 What: "zonk",
772 XID: xonk.XID,
773 Date: time.Now().UTC(),
774 Audience: oneofakind(xonk.Audience),
775 }
776
777 user, _ := butwhatabout(userinfo.Username)
778 log.Printf("announcing deleted honk: %s", what)
779 go honkworldwide(user, &zonk)
780 }
781 }
782 } else {
783 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
784 if err != nil {
785 log.Printf("error saving zonker: %s", err)
786 return
787 }
788 }
789}
790
791func savehonk(w http.ResponseWriter, r *http.Request) {
792 rid := r.FormValue("rid")
793 noise := r.FormValue("noise")
794
795 userinfo := login.GetUserInfo(r)
796 user, _ := butwhatabout(userinfo.Username)
797
798 dt := time.Now().UTC()
799 xid := fmt.Sprintf("https://%s/u/%s/h/%s", serverName, userinfo.Username, xfiltrate())
800 what := "honk"
801 if rid != "" {
802 what = "tonk"
803 }
804 honk := Honk{
805 UserID: userinfo.UserID,
806 Username: userinfo.Username,
807 What: "honk",
808 Honker: user.URL,
809 XID: xid,
810 Date: dt,
811 }
812 if strings.HasPrefix(noise, "DZ:") {
813 idx := strings.Index(noise, "\n")
814 if idx == -1 {
815 honk.Precis = noise
816 noise = ""
817 } else {
818 honk.Precis = noise[:idx]
819 noise = noise[idx+1:]
820 }
821 }
822 noise = strings.TrimSpace(noise)
823 honk.Precis = strings.TrimSpace(honk.Precis)
824
825 var convoy string
826 if rid != "" {
827 xonk := getxonk(userinfo.UserID, rid)
828 if xonk != nil {
829 if xonk.Public {
830 honk.Audience = append(honk.Audience, xonk.Audience...)
831 }
832 convoy = xonk.Convoy
833 } else {
834 xonkaud, c := whosthere(rid)
835 honk.Audience = append(honk.Audience, xonkaud...)
836 convoy = c
837 }
838 for i, a := range honk.Audience {
839 if a == thewholeworld {
840 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
841 break
842 }
843 }
844 honk.RID = rid
845 } else {
846 honk.Audience = []string{thewholeworld}
847 }
848 if noise != "" && noise[0] == '@' {
849 honk.Audience = append(grapevine(noise), honk.Audience...)
850 } else {
851 honk.Audience = append(honk.Audience, grapevine(noise)...)
852 }
853 if convoy == "" {
854 convoy = "data:,electrichonkytonk-" + xfiltrate()
855 }
856 butnottooloud(honk.Audience)
857 honk.Audience = oneofakind(honk.Audience)
858 if len(honk.Audience) == 0 {
859 log.Printf("honk to nowhere")
860 return
861 }
862 honk.Public = !keepitquiet(honk.Audience)
863 noise = obfusbreak(noise)
864 honk.Noise = noise
865 honk.Convoy = convoy
866
867 file, filehdr, err := r.FormFile("donk")
868 if err == nil {
869 var buf bytes.Buffer
870 io.Copy(&buf, file)
871 file.Close()
872 data := buf.Bytes()
873 xid := xfiltrate()
874 var media, name string
875 img, err := image.Vacuum(&buf)
876 if err == nil {
877 data = img.Data
878 format := img.Format
879 media = "image/" + format
880 if format == "jpeg" {
881 format = "jpg"
882 }
883 name = xid + "." + format
884 xid = name
885 } else {
886 maxsize := 100000
887 if len(data) > maxsize {
888 log.Printf("bad image: %s too much text: %d", err, len(data))
889 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
890 return
891 }
892 for i := 0; i < len(data); i++ {
893 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
894 log.Printf("bad image: %s not text: %d", err, data[i])
895 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
896 return
897 }
898 }
899 media = "text/plain"
900 name = filehdr.Filename
901 if name == "" {
902 name = xid + ".txt"
903 }
904 xid += ".txt"
905 }
906 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
907 res, err := stmtSaveFile.Exec(xid, name, url, media, 1, data)
908 if err != nil {
909 log.Printf("unable to save image: %s", err)
910 return
911 }
912 var d Donk
913 d.FileID, _ = res.LastInsertId()
914 d.XID = name
915 d.Name = name
916 d.Media = media
917 d.URL = url
918 honk.Donks = append(honk.Donks, &d)
919 }
920 herd := herdofemus(honk.Noise)
921 for _, e := range herd {
922 donk := savedonk(e.ID, e.Name, "image/png", true)
923 if donk != nil {
924 donk.Name = e.Name
925 honk.Donks = append(honk.Donks, donk)
926 }
927 }
928 honk.Donks = append(honk.Donks, memetics(honk.Noise)...)
929
930 aud := strings.Join(honk.Audience, " ")
931 whofore := 2
932 if !honk.Public {
933 whofore = 3
934 }
935 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
936 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
937 if err != nil {
938 log.Printf("error saving honk: %s", err)
939 return
940 }
941 honk.ID, _ = res.LastInsertId()
942 for _, d := range honk.Donks {
943 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
944 if err != nil {
945 log.Printf("err saving donk: %s", err)
946 return
947 }
948 }
949
950 go honkworldwide(user, &honk)
951
952 http.Redirect(w, r, "/", http.StatusSeeOther)
953}
954
955func showhonkers(w http.ResponseWriter, r *http.Request) {
956 userinfo := login.GetUserInfo(r)
957 templinfo := getInfo(r)
958 templinfo["Honkers"] = gethonkers(userinfo.UserID)
959 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
960 err := readviews.Execute(w, "honkers.html", templinfo)
961 if err != nil {
962 log.Print(err)
963 }
964}
965
966func showcombos(w http.ResponseWriter, r *http.Request) {
967 userinfo := login.GetUserInfo(r)
968 templinfo := getInfo(r)
969 honkers := gethonkers(userinfo.UserID)
970 var combos []string
971 for _, h := range honkers {
972 combos = append(combos, h.Combos...)
973 }
974 for i, c := range combos {
975 if c == "-" {
976 combos[i] = ""
977 }
978 }
979 combos = oneofakind(combos)
980 sort.Strings(combos)
981 templinfo["Combos"] = combos
982 err := readviews.Execute(w, "combos.html", templinfo)
983 if err != nil {
984 log.Print(err)
985 }
986}
987
988func savehonker(w http.ResponseWriter, r *http.Request) {
989 u := login.GetUserInfo(r)
990 name := r.FormValue("name")
991 url := r.FormValue("url")
992 peep := r.FormValue("peep")
993 combos := r.FormValue("combos")
994 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
995
996 if honkerid > 0 {
997 goodbye := r.FormValue("goodbye")
998 if goodbye == "F" {
999 db := opendatabase()
1000 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1001 honkerid, u.UserID)
1002 var xid string
1003 err := row.Scan(&xid)
1004 if err != nil {
1005 log.Printf("can't get honker xid: %s", err)
1006 return
1007 }
1008 log.Printf("unsubscribing from %s", xid)
1009 user, _ := butwhatabout(u.Username)
1010 err = itakeitallback(user, xid)
1011 if err != nil {
1012 log.Printf("can't take it back: %s", err)
1013 } else {
1014 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1015 if err != nil {
1016 log.Printf("error updating honker: %s", err)
1017 return
1018 }
1019 }
1020
1021 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1022 return
1023 }
1024 combos = " " + strings.TrimSpace(combos) + " "
1025 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1026 if err != nil {
1027 log.Printf("update honker err: %s", err)
1028 return
1029 }
1030 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1031 }
1032
1033 flavor := "presub"
1034 if peep == "peep" {
1035 flavor = "peep"
1036 }
1037 if url == "" {
1038 return
1039 }
1040 if url[0] == '@' {
1041 url = gofish(url)
1042 }
1043 if url == "" {
1044 return
1045 }
1046 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1047 if err != nil {
1048 log.Print(err)
1049 return
1050 }
1051 if flavor == "presub" {
1052 user, _ := butwhatabout(u.Username)
1053 go subsub(user, url)
1054 }
1055 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1056}
1057
1058type Zonker struct {
1059 ID int64
1060 Name string
1061 Wherefore string
1062}
1063
1064func zonkzone(w http.ResponseWriter, r *http.Request) {
1065 db := opendatabase()
1066 userinfo := login.GetUserInfo(r)
1067 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1068 if err != nil {
1069 log.Printf("err: %s", err)
1070 return
1071 }
1072 var zonkers []Zonker
1073 for rows.Next() {
1074 var z Zonker
1075 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1076 zonkers = append(zonkers, z)
1077 }
1078 templinfo := getInfo(r)
1079 templinfo["Zonkers"] = zonkers
1080 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1081 err = readviews.Execute(w, "zonkers.html", templinfo)
1082 if err != nil {
1083 log.Print(err)
1084 }
1085}
1086
1087func zonkzonk(w http.ResponseWriter, r *http.Request) {
1088 userinfo := login.GetUserInfo(r)
1089 itsok := r.FormValue("itsok")
1090 if itsok == "iforgiveyou" {
1091 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1092 db := opendatabase()
1093 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1094 userinfo.UserID, zonkerid)
1095 bitethethumbs()
1096 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1097 return
1098 }
1099 wherefore := r.FormValue("wherefore")
1100 name := r.FormValue("name")
1101 if name == "" {
1102 return
1103 }
1104 switch wherefore {
1105 case "zonker":
1106 case "zurl":
1107 case "zonvoy":
1108 case "zword":
1109 default:
1110 return
1111 }
1112 db := opendatabase()
1113 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1114 userinfo.UserID, name, wherefore)
1115 if wherefore == "zonker" || wherefore == "zurl" || wherefore == "zword" {
1116 bitethethumbs()
1117 }
1118
1119 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1120}
1121
1122func accountpage(w http.ResponseWriter, r *http.Request) {
1123 u := login.GetUserInfo(r)
1124 user, _ := butwhatabout(u.Username)
1125 templinfo := getInfo(r)
1126 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1127 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1128 templinfo["WhatAbout"] = user.About
1129 err := readviews.Execute(w, "account.html", templinfo)
1130 if err != nil {
1131 log.Print(err)
1132 }
1133}
1134
1135func dochpass(w http.ResponseWriter, r *http.Request) {
1136 err := login.ChangePassword(w, r)
1137 if err != nil {
1138 log.Printf("error changing password: %s", err)
1139 }
1140 http.Redirect(w, r, "/account", http.StatusSeeOther)
1141}
1142
1143func fingerlicker(w http.ResponseWriter, r *http.Request) {
1144 orig := r.FormValue("resource")
1145
1146 log.Printf("finger lick: %s", orig)
1147
1148 if strings.HasPrefix(orig, "acct:") {
1149 orig = orig[5:]
1150 }
1151
1152 name := orig
1153 idx := strings.LastIndexByte(name, '/')
1154 if idx != -1 {
1155 name = name[idx+1:]
1156 if "https://"+serverName+"/u/"+name != orig {
1157 log.Printf("foreign request rejected")
1158 name = ""
1159 }
1160 } else {
1161 idx = strings.IndexByte(name, '@')
1162 if idx != -1 {
1163 name = name[:idx]
1164 if name+"@"+serverName != orig {
1165 log.Printf("foreign request rejected")
1166 name = ""
1167 }
1168 }
1169 }
1170 user, err := butwhatabout(name)
1171 if err != nil {
1172 http.NotFound(w, r)
1173 return
1174 }
1175
1176 j := NewJunk()
1177 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1178 j["aliases"] = []string{user.URL}
1179 var links []map[string]interface{}
1180 l := NewJunk()
1181 l["rel"] = "self"
1182 l["type"] = `application/activity+json`
1183 l["href"] = user.URL
1184 links = append(links, l)
1185 j["links"] = links
1186
1187 w.Header().Set("Cache-Control", "max-age=3600")
1188 w.Header().Set("Content-Type", "application/jrd+json")
1189 WriteJunk(w, j)
1190}
1191
1192func somedays() string {
1193 secs := 432000 + notrand.Int63n(432000)
1194 return fmt.Sprintf("%d", secs)
1195}
1196
1197func avatate(w http.ResponseWriter, r *http.Request) {
1198 n := r.FormValue("a")
1199 a := avatar(n)
1200 w.Header().Set("Cache-Control", "max-age="+somedays())
1201 w.Write(a)
1202}
1203
1204func servecss(w http.ResponseWriter, r *http.Request) {
1205 w.Header().Set("Cache-Control", "max-age=7776000")
1206 http.ServeFile(w, r, "views"+r.URL.Path)
1207}
1208func servehtml(w http.ResponseWriter, r *http.Request) {
1209 templinfo := getInfo(r)
1210 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1211 if err != nil {
1212 log.Print(err)
1213 }
1214}
1215func serveemu(w http.ResponseWriter, r *http.Request) {
1216 xid := mux.Vars(r)["xid"]
1217 w.Header().Set("Cache-Control", "max-age="+somedays())
1218 http.ServeFile(w, r, "emus/"+xid)
1219}
1220func servememe(w http.ResponseWriter, r *http.Request) {
1221 xid := mux.Vars(r)["xid"]
1222 w.Header().Set("Cache-Control", "max-age="+somedays())
1223 http.ServeFile(w, r, "memes/"+xid)
1224}
1225
1226func servefile(w http.ResponseWriter, r *http.Request) {
1227 xid := mux.Vars(r)["xid"]
1228 row := stmtFileData.QueryRow(xid)
1229 var media string
1230 var data []byte
1231 err := row.Scan(&media, &data)
1232 if err != nil {
1233 log.Printf("error loading file: %s", err)
1234 http.NotFound(w, r)
1235 return
1236 }
1237 w.Header().Set("Content-Type", media)
1238 w.Header().Set("X-Content-Type-Options", "nosniff")
1239 w.Header().Set("Cache-Control", "max-age="+somedays())
1240 w.Write(data)
1241}
1242
1243func serve() {
1244 db := opendatabase()
1245 login.Init(db)
1246
1247 listener, err := openListener()
1248 if err != nil {
1249 log.Fatal(err)
1250 }
1251 go redeliverator()
1252
1253 debug := false
1254 getconfig("debug", &debug)
1255 readviews = templates.Load(debug,
1256 "views/honkpage.html",
1257 "views/honkers.html",
1258 "views/zonkers.html",
1259 "views/combos.html",
1260 "views/honkform.html",
1261 "views/honk.html",
1262 "views/account.html",
1263 "views/login.html",
1264 "views/header.html",
1265 )
1266 if !debug {
1267 s := "views/style.css"
1268 savedstyleparams[s] = getstyleparam(s)
1269 s = "views/local.css"
1270 savedstyleparams[s] = getstyleparam(s)
1271 }
1272
1273 bitethethumbs()
1274
1275 mux := mux.NewRouter()
1276 mux.Use(login.Checker)
1277
1278 posters := mux.Methods("POST").Subrouter()
1279 getters := mux.Methods("GET").Subrouter()
1280
1281 getters.HandleFunc("/", homepage)
1282 getters.HandleFunc("/rss", showrss)
1283 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1284 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1285 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1286 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1287 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1288 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1289 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1290 getters.HandleFunc("/a", avatate)
1291 getters.HandleFunc("/t", showconvoy)
1292 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1293 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1294 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1295 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1296
1297 getters.HandleFunc("/style.css", servecss)
1298 getters.HandleFunc("/local.css", servecss)
1299 getters.HandleFunc("/login", servehtml)
1300 posters.HandleFunc("/dologin", login.LoginFunc)
1301 getters.HandleFunc("/logout", login.LogoutFunc)
1302
1303 loggedin := mux.NewRoute().Subrouter()
1304 loggedin.Use(login.Required)
1305 loggedin.HandleFunc("/account", accountpage)
1306 loggedin.HandleFunc("/chpass", dochpass)
1307 loggedin.HandleFunc("/atme", homepage)
1308 loggedin.HandleFunc("/zonkzone", zonkzone)
1309 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1310 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1311 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1312 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1313 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1314 loggedin.HandleFunc("/honkers", showhonkers)
1315 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1316 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1317 loggedin.HandleFunc("/c", showcombos)
1318 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1319
1320 err = http.Serve(listener, mux)
1321 if err != nil {
1322 log.Fatal(err)
1323 }
1324}
1325
1326func cleanupdb() {
1327 db := opendatabase()
1328 rows, _ := db.Query("select userid, name from zonkers where wherefore = 'zonvoy'")
1329 deadthreads := make(map[int64][]string)
1330 for rows.Next() {
1331 var userid int64
1332 var name string
1333 rows.Scan(&userid, &name)
1334 deadthreads[userid] = append(deadthreads[userid], name)
1335 }
1336 rows.Close()
1337 for userid, threads := range deadthreads {
1338 for _, t := range threads {
1339 doordie(db, "delete from donks where honkid in (select honkid from honks where userid = ? and convoy = ?)", userid, t)
1340 doordie(db, "delete from honks where userid = ? and convoy = ?", userid, t)
1341 }
1342 }
1343 expdate := time.Now().UTC().Add(-30 * 24 * time.Hour).Format(dbtimeformat)
1344 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0)", expdate)
1345 doordie(db, "delete from honks where dt < ? and whofore = 0", expdate)
1346 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1347}
1348
1349var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1350var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1351var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1352var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1353var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1354var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1355var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1356var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1357
1358func preparetodie(db *sql.DB, s string) *sql.Stmt {
1359 stmt, err := db.Prepare(s)
1360 if err != nil {
1361 log.Fatalf("error %s: %s", err, s)
1362 }
1363 return stmt
1364}
1365
1366func prepareStatements(db *sql.DB) {
1367 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")
1368 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1369 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1370 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1371 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1372 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1373
1374 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 "
1375 limit := " order by honkid desc limit 250"
1376 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1377 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1378 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1379 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1380 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)
1381 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1382 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1383 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1384 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or whofore = 2) and convoy = ?"+limit)
1385
1386 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1387 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1388 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1389 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1390 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1391 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1392 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1393 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1394 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1395 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1396 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1397 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1398 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1399 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1400 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl' or wherefore = 'zword')")
1401 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1402 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1403 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1404}
1405
1406func ElaborateUnitTests() {
1407}
1408
1409func main() {
1410 cmd := "run"
1411 if len(os.Args) > 1 {
1412 cmd = os.Args[1]
1413 }
1414 switch cmd {
1415 case "init":
1416 initdb()
1417 case "upgrade":
1418 upgradedb()
1419 }
1420 db := opendatabase()
1421 dbversion := 0
1422 getconfig("dbversion", &dbversion)
1423 if dbversion != myVersion {
1424 log.Fatal("incorrect database version. run upgrade.")
1425 }
1426 getconfig("servername", &serverName)
1427 prepareStatements(db)
1428 switch cmd {
1429 case "adduser":
1430 adduser()
1431 case "cleanup":
1432 cleanupdb()
1433 case "ping":
1434 if len(os.Args) < 4 {
1435 fmt.Printf("usage: honk ping from to\n")
1436 return
1437 }
1438 name := os.Args[2]
1439 targ := os.Args[3]
1440 user, err := butwhatabout(name)
1441 if err != nil {
1442 log.Printf("unknown user")
1443 return
1444 }
1445 ping(user, targ)
1446 case "peep":
1447 peeppeep()
1448 case "run":
1449 serve()
1450 case "test":
1451 ElaborateUnitTests()
1452 default:
1453 log.Fatal("unknown command")
1454 }
1455}