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