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