all repos — honk @ e857d06e4e277639ef58f5d542f6f941178d47d4

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