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