all repos — honk @ 14d1cd8cc0bdeca1ae9780639c73299623b7c640

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