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 showhonk(w http.ResponseWriter, r *http.Request) {
457 name := mux.Vars(r)["name"]
458 xid := mux.Vars(r)["xid"]
459 user, err := butwhatabout(name)
460 if err != nil {
461 http.NotFound(w, r)
462 return
463 }
464 h := getxonk(user.ID, xid)
465 if h == nil {
466 http.NotFound(w, r)
467 return
468 }
469 if friendorfoe(r.Header.Get("Accept")) {
470 donksforhonks([]*Honk{h})
471 _, j := jonkjonk(user, h)
472 j["@context"] = itiswhatitis
473 w.Header().Set("Cache-Control", "max-age=3600")
474 w.Header().Set("Content-Type", theonetruename)
475 WriteJunk(w, j)
476 return
477 }
478 honks := gethonksbyconvoy(-1, h.Convoy)
479 for _, hh := range honks {
480 if hh.XID != h.XID {
481 hh.Privacy = "limited"
482 }
483 }
484 u := login.GetUserInfo(r)
485 honkpage(w, r, u, nil, honks, "one honk maybe more")
486}
487
488func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
489 honks []*Honk, infomsg string) {
490 reverbolate(honks)
491 templinfo := getInfo(r)
492 if u != nil {
493 if user != nil && u.Username == user.Name {
494 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
495 }
496 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
497 }
498 if u == nil {
499 w.Header().Set("Cache-Control", "max-age=60")
500 }
501 if user != nil {
502 templinfo["Name"] = user.Name
503 whatabout := user.About
504 templinfo["RawWhatAbout"] = whatabout
505 whatabout = obfusbreak(whatabout)
506 templinfo["WhatAbout"] = cleanstring(whatabout)
507 }
508 templinfo["Honks"] = honks
509 templinfo["ServerMessage"] = infomsg
510 err := readviews.Execute(w, "honkpage.html", templinfo)
511 if err != nil {
512 log.Print(err)
513 }
514}
515
516func saveuser(w http.ResponseWriter, r *http.Request) {
517 whatabout := r.FormValue("whatabout")
518 u := login.GetUserInfo(r)
519 db := opendatabase()
520 _, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
521 if err != nil {
522 log.Printf("error bouting what: %s", err)
523 }
524
525 http.Redirect(w, r, "/u/"+u.Username, http.StatusSeeOther)
526}
527
528func gethonkers(userid int64) []*Honker {
529 rows, err := stmtHonkers.Query(userid)
530 if err != nil {
531 log.Printf("error querying honkers: %s", err)
532 return nil
533 }
534 defer rows.Close()
535 var honkers []*Honker
536 for rows.Next() {
537 var f Honker
538 var combos string
539 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
540 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
541 if err != nil {
542 log.Printf("error scanning honker: %s", err)
543 return nil
544 }
545 honkers = append(honkers, &f)
546 }
547 return honkers
548}
549
550func getdubs(userid int64) []*Honker {
551 rows, err := stmtDubbers.Query(userid)
552 if err != nil {
553 log.Printf("error querying dubs: %s", err)
554 return nil
555 }
556 defer rows.Close()
557 var honkers []*Honker
558 for rows.Next() {
559 var f Honker
560 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
561 if err != nil {
562 log.Printf("error scanning honker: %s", err)
563 return nil
564 }
565 honkers = append(honkers, &f)
566 }
567 return honkers
568}
569
570func getxonk(userid int64, xid string) *Honk {
571 h := new(Honk)
572 var dt, aud string
573 row := stmtOneXonk.QueryRow(userid, xid)
574 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
575 &dt, &h.URL, &aud, &h.Noise, &h.Convoy)
576 if err != nil {
577 if err != sql.ErrNoRows {
578 log.Printf("error scanning xonk: %s", err)
579 }
580 return nil
581 }
582 h.Date, _ = time.Parse(dbtimeformat, dt)
583 h.Audience = strings.Split(aud, " ")
584 return h
585}
586
587func getpublichonks() []*Honk {
588 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
589 rows, err := stmtPublicHonks.Query(dt)
590 return getsomehonks(rows, err)
591}
592func gethonksbyuser(name string) []*Honk {
593 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
594 rows, err := stmtUserHonks.Query(name, dt)
595 return getsomehonks(rows, err)
596}
597func gethonksforuser(userid int64) []*Honk {
598 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
599 rows, err := stmtHonksForUser.Query(userid, dt, userid)
600 return getsomehonks(rows, err)
601}
602func gethonksforme(userid int64) []*Honk {
603 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
604 rows, err := stmtHonksForMe.Query(userid, dt, userid)
605 return getsomehonks(rows, err)
606}
607func gethonksbyhonker(userid int64, honker string) []*Honk {
608 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
609 return getsomehonks(rows, err)
610}
611func gethonksbycombo(userid int64, combo string) []*Honk {
612 combo = "% " + combo + " %"
613 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
614 return getsomehonks(rows, err)
615}
616func gethonksbyconvoy(userid int64, convoy string) []*Honk {
617 rows, err := stmtHonksByConvoy.Query(userid, convoy)
618 honks := getsomehonks(rows, err)
619 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
620 honks[i], honks[j] = honks[j], honks[i]
621 }
622 return honks
623}
624
625func getsomehonks(rows *sql.Rows, err error) []*Honk {
626 if err != nil {
627 log.Printf("error querying honks: %s", err)
628 return nil
629 }
630 defer rows.Close()
631 var honks []*Honk
632 for rows.Next() {
633 var h Honk
634 var dt, aud string
635 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
636 &dt, &h.URL, &aud, &h.Noise, &h.Convoy)
637 if err != nil {
638 log.Printf("error scanning honks: %s", err)
639 return nil
640 }
641 h.Date, _ = time.Parse(dbtimeformat, dt)
642 h.Audience = strings.Split(aud, " ")
643 h.Privacy = "limited"
644 for _, a := range h.Audience {
645 if a == thewholeworld {
646 h.Privacy = ""
647 break
648 }
649 }
650 honks = append(honks, &h)
651 }
652 rows.Close()
653 donksforhonks(honks)
654 return honks
655}
656
657func donksforhonks(honks []*Honk) {
658 db := opendatabase()
659 var ids []string
660 hmap := make(map[int64]*Honk)
661 for _, h := range honks {
662 ids = append(ids, fmt.Sprintf("%d", h.ID))
663 hmap[h.ID] = h
664 }
665 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, ","))
666 rows, err := db.Query(q)
667 if err != nil {
668 log.Printf("error querying donks: %s", err)
669 return
670 }
671 defer rows.Close()
672 for rows.Next() {
673 var hid int64
674 var d Donk
675 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media)
676 if err != nil {
677 log.Printf("error scanning donk: %s", err)
678 continue
679 }
680 h := hmap[hid]
681 h.Donks = append(h.Donks, &d)
682 }
683}
684
685func savebonk(w http.ResponseWriter, r *http.Request) {
686 xid := r.FormValue("xid")
687 userinfo := login.GetUserInfo(r)
688
689 log.Printf("bonking %s", xid)
690
691 xonk := getxonk(userinfo.UserID, xid)
692 if xonk == nil {
693 return
694 }
695 donksforhonks([]*Honk{xonk})
696 if xonk.Honker == "" {
697 xonk.XID = fmt.Sprintf("https://%s/u/%s/h/%s", serverName, xonk.Username, xonk.XID)
698 }
699
700 dt := time.Now().UTC()
701 bonk := Honk{
702 UserID: userinfo.UserID,
703 Username: userinfo.Username,
704 Honker: xonk.Honker,
705 What: "bonk",
706 XID: xonk.XID,
707 Date: dt,
708 Noise: xonk.Noise,
709 Convoy: xonk.Convoy,
710 Donks: xonk.Donks,
711 Audience: []string{thewholeworld},
712 }
713
714 user, _ := butwhatabout(userinfo.Username)
715
716 aud := strings.Join(bonk.Audience, " ")
717 whofore := 0
718 if strings.Contains(aud, user.URL) {
719 whofore = 1
720 }
721 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", "", xid, "",
722 dt.Format(dbtimeformat), "", aud, bonk.Noise, bonk.Convoy, whofore)
723 if err != nil {
724 log.Printf("error saving bonk: %s", err)
725 return
726 }
727 bonk.ID, _ = res.LastInsertId()
728 for _, d := range bonk.Donks {
729 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
730 if err != nil {
731 log.Printf("err saving donk: %s", err)
732 return
733 }
734 }
735
736 go honkworldwide(user, &bonk)
737}
738
739func zonkit(w http.ResponseWriter, r *http.Request) {
740 wherefore := r.FormValue("wherefore")
741 var what string
742 switch wherefore {
743 case "this honk":
744 what = r.FormValue("honk")
745 wherefore = "zonk"
746 case "this honker":
747 what = r.FormValue("honker")
748 wherefore = "zonker"
749 case "this convoy":
750 what = r.FormValue("convoy")
751 wherefore = "zonvoy"
752 }
753 if what == "" {
754 return
755 }
756
757 log.Printf("zonking %s %s", wherefore, what)
758 userinfo := login.GetUserInfo(r)
759 if wherefore == "zonk" {
760 xonk := getxonk(userinfo.UserID, what)
761 if xonk != nil {
762 stmtZonkDonks.Exec(xonk.ID)
763 stmtZonkIt.Exec(userinfo.UserID, what)
764 if xonk.Honker == "" {
765 zonk := Honk{
766 What: "zonk",
767 XID: xonk.XID,
768 Date: time.Now().UTC(),
769 Audience: oneofakind(xonk.Audience),
770 }
771
772 user, _ := butwhatabout(userinfo.Username)
773 log.Printf("announcing deleted honk: %s", what)
774 go honkworldwide(user, &zonk)
775 }
776 }
777 } else {
778 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
779 if err != nil {
780 log.Printf("error saving zonker: %s", err)
781 return
782 }
783 }
784}
785
786func savehonk(w http.ResponseWriter, r *http.Request) {
787 rid := r.FormValue("rid")
788 noise := r.FormValue("noise")
789
790 userinfo := login.GetUserInfo(r)
791
792 dt := time.Now().UTC()
793 xid := xfiltrate()
794 what := "honk"
795 if rid != "" {
796 what = "tonk"
797 }
798 honk := Honk{
799 UserID: userinfo.UserID,
800 Username: userinfo.Username,
801 What: "honk",
802 XID: xid,
803 Date: dt,
804 }
805 if noise != "" && noise[0] == '@' {
806 honk.Audience = append(grapevine(noise), thewholeworld)
807 } else {
808 honk.Audience = prepend(thewholeworld, grapevine(noise))
809 }
810 var convoy string
811 if rid != "" {
812 xonk := getxonk(userinfo.UserID, rid)
813 if xonk != nil {
814 if xonk.Honker == "" {
815 rid = "https://" + serverName + "/u/" + xonk.Username + "/h/" + rid
816 }
817 honk.Audience = append(honk.Audience, xonk.Audience...)
818 convoy = xonk.Convoy
819 } else {
820 xonkaud, c := whosthere(rid)
821 honk.Audience = append(honk.Audience, xonkaud...)
822 convoy = c
823 }
824 honk.RID = rid
825 }
826 if convoy == "" {
827 convoy = "data:,electrichonkytonk-" + xfiltrate()
828 }
829 butnottooloud(honk.Audience)
830 honk.Audience = oneofakind(honk.Audience)
831 noise = obfusbreak(noise)
832 honk.Noise = noise
833 honk.Convoy = convoy
834
835 file, filehdr, err := r.FormFile("donk")
836 if err == nil {
837 var buf bytes.Buffer
838 io.Copy(&buf, file)
839 file.Close()
840 data := buf.Bytes()
841 xid := xfiltrate()
842 var media, name string
843 img, err := image.Vacuum(&buf)
844 if err == nil {
845 data = img.Data
846 format := img.Format
847 media = "image/" + format
848 if format == "jpeg" {
849 format = "jpg"
850 }
851 name = xid + "." + format
852 xid = name
853 } else {
854 maxsize := 100000
855 if len(data) > maxsize {
856 log.Printf("bad image: %s too much text: %d", err, len(data))
857 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
858 return
859 }
860 for i := 0; i < len(data); i++ {
861 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
862 log.Printf("bad image: %s not text: %d", err, data[i])
863 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
864 return
865 }
866 }
867 media = "text/plain"
868 name = filehdr.Filename
869 if name == "" {
870 name = xid + ".txt"
871 }
872 xid += ".txt"
873 }
874 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
875 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
876 if err != nil {
877 log.Printf("unable to save image: %s", err)
878 return
879 }
880 var d Donk
881 d.FileID, _ = res.LastInsertId()
882 d.XID = name
883 d.Name = name
884 d.Media = media
885 d.URL = url
886 honk.Donks = append(honk.Donks, &d)
887 }
888 herd := herdofemus(honk.Noise)
889 for _, e := range herd {
890 donk := savedonk(e.ID, e.Name, "image/png")
891 if donk != nil {
892 donk.Name = e.Name
893 honk.Donks = append(honk.Donks, donk)
894 }
895 }
896
897 user, _ := butwhatabout(userinfo.Username)
898
899 aud := strings.Join(honk.Audience, " ")
900 whofore := 0
901 if strings.Contains(aud, user.URL) {
902 whofore = 1
903 }
904 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
905 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore)
906 if err != nil {
907 log.Printf("error saving honk: %s", err)
908 return
909 }
910 honk.ID, _ = res.LastInsertId()
911 for _, d := range honk.Donks {
912 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
913 if err != nil {
914 log.Printf("err saving donk: %s", err)
915 return
916 }
917 }
918
919 go honkworldwide(user, &honk)
920
921 http.Redirect(w, r, "/", http.StatusSeeOther)
922}
923
924func showhonkers(w http.ResponseWriter, r *http.Request) {
925 userinfo := login.GetUserInfo(r)
926 templinfo := getInfo(r)
927 templinfo["Honkers"] = gethonkers(userinfo.UserID)
928 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
929 err := readviews.Execute(w, "honkers.html", templinfo)
930 if err != nil {
931 log.Print(err)
932 }
933}
934
935func showcombos(w http.ResponseWriter, r *http.Request) {
936 userinfo := login.GetUserInfo(r)
937 templinfo := getInfo(r)
938 honkers := gethonkers(userinfo.UserID)
939 var combos []string
940 for _, h := range honkers {
941 combos = append(combos, h.Combos...)
942 }
943 combos = oneofakind(combos)
944 sort.Strings(combos)
945 templinfo["Combos"] = combos
946 err := readviews.Execute(w, "combos.html", templinfo)
947 if err != nil {
948 log.Print(err)
949 }
950}
951
952func savehonker(w http.ResponseWriter, r *http.Request) {
953 u := login.GetUserInfo(r)
954 name := r.FormValue("name")
955 url := r.FormValue("url")
956 peep := r.FormValue("peep")
957 combos := r.FormValue("combos")
958 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
959
960 if honkerid > 0 {
961 goodbye := r.FormValue("goodbye")
962 if goodbye == "goodbye" {
963 db := opendatabase()
964 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
965 honkerid, u.UserID)
966 var xid string
967 err := row.Scan(&xid)
968 if err != nil {
969 log.Printf("can't get honker xid: %s", err)
970 return
971 }
972 log.Printf("unsubscribing from %s", xid)
973 user, _ := butwhatabout(u.Username)
974 err = itakeitallback(user, xid)
975 if err != nil {
976 log.Printf("can't take it back: %s", err)
977 } else {
978 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
979 if err != nil {
980 log.Printf("error updating honker: %s", err)
981 return
982 }
983 }
984
985 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
986 return
987 }
988 combos = " " + strings.TrimSpace(combos) + " "
989 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
990 if err != nil {
991 log.Printf("update honker err: %s", err)
992 return
993 }
994 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
995 }
996
997 flavor := "presub"
998 if peep == "peep" {
999 flavor = "peep"
1000 }
1001 if url == "" {
1002 return
1003 }
1004 if url[0] == '@' {
1005 url = gofish(url)
1006 }
1007 if url == "" {
1008 return
1009 }
1010 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1011 if err != nil {
1012 log.Print(err)
1013 return
1014 }
1015 if flavor == "presub" {
1016 user, _ := butwhatabout(u.Username)
1017 go subsub(user, url)
1018 }
1019 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1020}
1021
1022type Zonker struct {
1023 ID int64
1024 Name string
1025 Wherefore string
1026}
1027
1028func killzone(w http.ResponseWriter, r *http.Request) {
1029 db := opendatabase()
1030 userinfo := login.GetUserInfo(r)
1031 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1032 if err != nil {
1033 log.Printf("err: %s", err)
1034 return
1035 }
1036 var zonkers []Zonker
1037 for rows.Next() {
1038 var z Zonker
1039 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1040 zonkers = append(zonkers, z)
1041 }
1042 templinfo := getInfo(r)
1043 templinfo["Zonkers"] = zonkers
1044 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1045 err = readviews.Execute(w, "zonkers.html", templinfo)
1046 if err != nil {
1047 log.Print(err)
1048 }
1049}
1050
1051func killitwithfire(w http.ResponseWriter, r *http.Request) {
1052 userinfo := login.GetUserInfo(r)
1053 itsok := r.FormValue("itsok")
1054 if itsok == "iforgiveyou" {
1055 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1056 db := opendatabase()
1057 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1058 userinfo.UserID, zonkerid)
1059 bitethethumbs()
1060 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1061 return
1062 }
1063 wherefore := r.FormValue("wherefore")
1064 name := r.FormValue("name")
1065 if name == "" {
1066 return
1067 }
1068 switch wherefore {
1069 case "zonker":
1070 case "zurl":
1071 case "zonvoy":
1072 default:
1073 return
1074 }
1075 db := opendatabase()
1076 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1077 userinfo.UserID, name, wherefore)
1078 if wherefore == "zonker" || wherefore == "zurl" {
1079 bitethethumbs()
1080 }
1081
1082 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1083}
1084
1085func fingerlicker(w http.ResponseWriter, r *http.Request) {
1086 orig := r.FormValue("resource")
1087
1088 log.Printf("finger lick: %s", orig)
1089
1090 if strings.HasPrefix(orig, "acct:") {
1091 orig = orig[5:]
1092 }
1093
1094 name := orig
1095 idx := strings.LastIndexByte(name, '/')
1096 if idx != -1 {
1097 name = name[idx+1:]
1098 if "https://"+serverName+"/u/"+name != orig {
1099 log.Printf("foreign request rejected")
1100 name = ""
1101 }
1102 } else {
1103 idx = strings.IndexByte(name, '@')
1104 if idx != -1 {
1105 name = name[:idx]
1106 if name+"@"+serverName != orig {
1107 log.Printf("foreign request rejected")
1108 name = ""
1109 }
1110 }
1111 }
1112 user, err := butwhatabout(name)
1113 if err != nil {
1114 http.NotFound(w, r)
1115 return
1116 }
1117
1118 j := NewJunk()
1119 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1120 j["aliases"] = []string{user.URL}
1121 var links []map[string]interface{}
1122 l := NewJunk()
1123 l["rel"] = "self"
1124 l["type"] = `application/activity+json`
1125 l["href"] = user.URL
1126 links = append(links, l)
1127 j["links"] = links
1128
1129 w.Header().Set("Cache-Control", "max-age=3600")
1130 w.Header().Set("Content-Type", "application/jrd+json")
1131 WriteJunk(w, j)
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}