all repos — honk @ 9be0ce1985109a377066279c2744ba646ab7fd1e

my fork of honk

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}