all repos — honk @ 84ca26f386e4216a3689b2e1a019b1e0389c3e4a

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