all repos — honk @ 87e955d322e60c8e66d1afc204d9ed5baabe67ec

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 needxonk(user, xonk) {
 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	rows, err := stmtPublicHonks.Query()
 654	return getsomehonks(rows, err)
 655}
 656func gethonksbyuser(name string) []*Honk {
 657	rows, err := stmtUserHonks.Query(name)
 658	return getsomehonks(rows, err)
 659}
 660func gethonksforuser(userid int64) []*Honk {
 661	dt := time.Now().UTC().Add(-2 * 24 * time.Hour)
 662	rows, err := stmtHonksForUser.Query(userid, dt.Format(dbtimeformat), userid)
 663	return getsomehonks(rows, err)
 664}
 665func gethonksforme(userid int64) []*Honk {
 666	dt := time.Now().UTC().Add(-4 * 24 * time.Hour)
 667	rows, err := stmtHonksForMe.Query(userid, dt.Format(dbtimeformat), userid)
 668	return getsomehonks(rows, err)
 669}
 670func gethonksbyhonker(userid int64, honker string) []*Honk {
 671	rows, err := stmtHonksByHonker.Query(userid, honker, userid)
 672	return getsomehonks(rows, err)
 673}
 674func gethonksbycombo(userid int64, combo string) []*Honk {
 675	combo = "% " + combo + " %"
 676	rows, err := stmtHonksByCombo.Query(userid, combo, userid)
 677	return getsomehonks(rows, err)
 678}
 679func gethonksbyconvoy(userid int64, convoy string) []*Honk {
 680	rows, err := stmtHonksByConvoy.Query(userid, convoy)
 681	return getsomehonks(rows, err)
 682}
 683
 684func getsomehonks(rows *sql.Rows, err error) []*Honk {
 685	if err != nil {
 686		log.Printf("error querying honks: %s", err)
 687		return nil
 688	}
 689	defer rows.Close()
 690	var honks []*Honk
 691	for rows.Next() {
 692		var h Honk
 693		var dt, aud string
 694		err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
 695			&dt, &h.URL, &aud, &h.Noise, &h.Convoy)
 696		if err != nil {
 697			log.Printf("error scanning honks: %s", err)
 698			return nil
 699		}
 700		h.Date, _ = time.Parse(dbtimeformat, dt)
 701		h.Audience = strings.Split(aud, " ")
 702		honks = append(honks, &h)
 703	}
 704	rows.Close()
 705	donksforhonks(honks)
 706	return honks
 707}
 708
 709func donksforhonks(honks []*Honk) {
 710	db := opendatabase()
 711	var ids []string
 712	hmap := make(map[int64]*Honk)
 713	for _, h := range honks {
 714		if h.What == "zonk" {
 715			continue
 716		}
 717		ids = append(ids, fmt.Sprintf("%d", h.ID))
 718		hmap[h.ID] = h
 719	}
 720	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, ","))
 721	rows, err := db.Query(q)
 722	if err != nil {
 723		log.Printf("error querying donks: %s", err)
 724		return
 725	}
 726	defer rows.Close()
 727	for rows.Next() {
 728		var hid int64
 729		var d Donk
 730		err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media)
 731		if err != nil {
 732			log.Printf("error scanning donk: %s", err)
 733			continue
 734		}
 735		h := hmap[hid]
 736		h.Donks = append(h.Donks, &d)
 737	}
 738}
 739
 740func savebonk(w http.ResponseWriter, r *http.Request) {
 741	xid := r.FormValue("xid")
 742
 743	log.Printf("bonking %s", xid)
 744
 745	xonk := getxonk("", xid)
 746	if xonk == nil {
 747		return
 748	}
 749	if xonk.Honker == "" {
 750		xonk.XID = fmt.Sprintf("https://%s/u/%s/h/%s", serverName, xonk.Username, xonk.XID)
 751	}
 752	convoy := xonk.Convoy
 753
 754	userinfo := login.GetUserInfo(r)
 755
 756	dt := time.Now().UTC()
 757	bonk := Honk{
 758		UserID:   userinfo.UserID,
 759		Username: userinfo.Username,
 760		Honker:   xonk.Honker,
 761		What:     "bonk",
 762		XID:      xonk.XID,
 763		Date:     dt,
 764		Noise:    xonk.Noise,
 765		Convoy:   convoy,
 766		Donks:    xonk.Donks,
 767		Audience: oneofakind(prepend(thewholeworld, xonk.Audience)),
 768	}
 769
 770	user, _ := butwhatabout(userinfo.Username)
 771
 772	aud := strings.Join(bonk.Audience, " ")
 773	whofore := 0
 774	if strings.Contains(aud, user.URL) {
 775		whofore = 1
 776	}
 777	res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", "", xid, "",
 778		dt.Format(dbtimeformat), "", aud, bonk.Noise, bonk.Convoy, whofore)
 779	if err != nil {
 780		log.Printf("error saving bonk: %s", err)
 781		return
 782	}
 783	bonk.ID, _ = res.LastInsertId()
 784	for _, d := range bonk.Donks {
 785		_, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
 786		if err != nil {
 787			log.Printf("err saving donk: %s", err)
 788			return
 789		}
 790	}
 791
 792	go honkworldwide(user, &bonk)
 793
 794}
 795
 796func zonkit(w http.ResponseWriter, r *http.Request) {
 797	wherefore := r.FormValue("wherefore")
 798	var what string
 799	switch wherefore {
 800	case "this honk":
 801		what = r.FormValue("honk")
 802		wherefore = "zonk"
 803	case "this honker":
 804		what = r.FormValue("honker")
 805		wherefore = "zonker"
 806	case "this convoy":
 807		what = r.FormValue("convoy")
 808		wherefore = "zonvoy"
 809	}
 810	if what == "" {
 811		return
 812	}
 813
 814	log.Printf("zonking %s %s", wherefore, what)
 815	userinfo := login.GetUserInfo(r)
 816	if wherefore == "zonk" {
 817		stmtZonkIt.Exec(userinfo.UserID, what)
 818	} else {
 819		_, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
 820		if err != nil {
 821			log.Printf("error saving zonker: %s", err)
 822			return
 823		}
 824	}
 825}
 826
 827func savehonk(w http.ResponseWriter, r *http.Request) {
 828	rid := r.FormValue("rid")
 829	noise := r.FormValue("noise")
 830
 831	userinfo := login.GetUserInfo(r)
 832
 833	dt := time.Now().UTC()
 834	xid := xfiltrate()
 835	what := "honk"
 836	if rid != "" {
 837		what = "tonk"
 838	}
 839	honk := Honk{
 840		UserID:   userinfo.UserID,
 841		Username: userinfo.Username,
 842		What:     "honk",
 843		XID:      xid,
 844		Date:     dt,
 845	}
 846	if noise[0] == '@' {
 847		honk.Audience = append(grapevine(noise), thewholeworld)
 848	} else {
 849		honk.Audience = prepend(thewholeworld, grapevine(noise))
 850	}
 851	var convoy string
 852	if rid != "" {
 853		xonk := getxonk("", rid)
 854		if xonk != nil {
 855			if xonk.Honker == "" {
 856				rid = "https://" + serverName + "/u/" + xonk.Username + "/h/" + rid
 857			}
 858			honk.Audience = append(honk.Audience, xonk.Audience...)
 859			convoy = xonk.Convoy
 860		} else {
 861			xonkaud, c := whosthere(rid)
 862			honk.Audience = append(honk.Audience, xonkaud...)
 863			convoy = c
 864		}
 865		honk.RID = rid
 866	}
 867	if convoy == "" {
 868		convoy = "data:,electrichonkytonk-" + xfiltrate()
 869	}
 870	honk.Audience = oneofakind(honk.Audience)
 871	noise = obfusbreak(noise)
 872	honk.Noise = noise
 873	honk.Convoy = convoy
 874
 875	file, filehdr, err := r.FormFile("donk")
 876	if err == nil {
 877		var buf bytes.Buffer
 878		io.Copy(&buf, file)
 879		file.Close()
 880		data := buf.Bytes()
 881		xid := xfiltrate()
 882		var media, name string
 883		img, format, err := image.Decode(&buf)
 884		if err == nil {
 885			data, format, err = vacuumwrap(img, format)
 886			if err != nil {
 887				log.Printf("can't vacuum image: %s", err)
 888				return
 889			}
 890			media = "image/" + format
 891			if format == "jpeg" {
 892				format = "jpg"
 893			}
 894			name = xid + "." + format
 895			xid = name
 896		} else {
 897			maxsize := 100000
 898			if len(data) > maxsize {
 899				log.Printf("bad image: %s too much text: %d", err, len(data))
 900				http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
 901				return
 902			}
 903			for i := 0; i < len(data); i++ {
 904				if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
 905					log.Printf("bad image: %s not text: %d", err, data[i])
 906					http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
 907					return
 908				}
 909			}
 910			media = "text/plain"
 911			name = filehdr.Filename
 912			if name == "" {
 913				name = xid + ".txt"
 914			}
 915			xid += ".txt"
 916		}
 917		url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
 918		res, err := stmtSaveFile.Exec(xid, name, url, media, data)
 919		if err != nil {
 920			log.Printf("unable to save image: %s", err)
 921			return
 922		}
 923		var d Donk
 924		d.FileID, _ = res.LastInsertId()
 925		d.XID = name
 926		d.Name = name
 927		d.Media = media
 928		d.URL = url
 929		honk.Donks = append(honk.Donks, &d)
 930	}
 931	herd := herdofemus(honk.Noise)
 932	for _, e := range herd {
 933		donk := savedonk(e.ID, e.Name, "image/png")
 934		if donk != nil {
 935			donk.Name = e.Name
 936			honk.Donks = append(honk.Donks, donk)
 937		}
 938	}
 939
 940	user, _ := butwhatabout(userinfo.Username)
 941
 942	aud := strings.Join(honk.Audience, " ")
 943	whofore := 0
 944	if strings.Contains(aud, user.URL) {
 945		whofore = 1
 946	}
 947	res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
 948		dt.Format(dbtimeformat), "", aud, noise, convoy, whofore)
 949	if err != nil {
 950		log.Printf("error saving honk: %s", err)
 951		return
 952	}
 953	honk.ID, _ = res.LastInsertId()
 954	for _, d := range honk.Donks {
 955		_, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
 956		if err != nil {
 957			log.Printf("err saving donk: %s", err)
 958			return
 959		}
 960	}
 961
 962	go honkworldwide(user, &honk)
 963
 964	http.Redirect(w, r, "/", http.StatusSeeOther)
 965}
 966
 967func showhonkers(w http.ResponseWriter, r *http.Request) {
 968	userinfo := login.GetUserInfo(r)
 969	templinfo := getInfo(r)
 970	templinfo["Honkers"] = gethonkers(userinfo.UserID)
 971	templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
 972	err := readviews.Execute(w, "honkers.html", templinfo)
 973	if err != nil {
 974		log.Print(err)
 975	}
 976}
 977
 978var handfull = make(map[string]string)
 979var handlock sync.Mutex
 980
 981func gofish(name string) string {
 982	if name[0] == '@' {
 983		name = name[1:]
 984	}
 985	m := strings.Split(name, "@")
 986	if len(m) != 2 {
 987		log.Printf("bad fish name: %s", name)
 988		return ""
 989	}
 990	handlock.Lock()
 991	ref, ok := handfull[name]
 992	handlock.Unlock()
 993	if ok {
 994		return ref
 995	}
 996	j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
 997	handlock.Lock()
 998	defer handlock.Unlock()
 999	if err != nil {
1000		log.Printf("failed to go fish %s: %s", name, err)
1001		handfull[name] = ""
1002		return ""
1003	}
1004	links, _ := jsongetarray(j, "links")
1005	for _, l := range links {
1006		href, _ := jsongetstring(l, "href")
1007		rel, _ := jsongetstring(l, "rel")
1008		t, _ := jsongetstring(l, "type")
1009		if rel == "self" && friendorfoe(t) {
1010			handfull[name] = href
1011			return href
1012		}
1013	}
1014	handfull[name] = ""
1015	return ""
1016}
1017
1018func savehonker(w http.ResponseWriter, r *http.Request) {
1019	u := login.GetUserInfo(r)
1020	name := r.FormValue("name")
1021	url := r.FormValue("url")
1022	peep := r.FormValue("peep")
1023	combos := r.FormValue("combos")
1024	honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1025
1026	if honkerid > 0 {
1027		goodbye := r.FormValue("goodbye")
1028		if goodbye == "goodbye" {
1029			db := opendatabase()
1030			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1031				honkerid, u.UserID)
1032			var xid string
1033			err := row.Scan(&xid)
1034			if err != nil {
1035				log.Printf("can't get honker xid: %s", err)
1036				return
1037			}
1038			log.Printf("unsubscribing from %s", xid)
1039			user, _ := butwhatabout(u.Username)
1040			err = itakeitallback(user, xid)
1041			if err != nil {
1042				log.Printf("can't take it back: %s", err)
1043			} else {
1044				_, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1045				if err != nil {
1046					log.Printf("error updating honker: %s", err)
1047					return
1048				}
1049			}
1050
1051			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1052			return
1053		}
1054		combos = " " + strings.TrimSpace(combos) + " "
1055		_, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1056		if err != nil {
1057			log.Printf("update honker err: %s", err)
1058			return
1059		}
1060		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1061	}
1062
1063	flavor := "presub"
1064	if peep == "peep" {
1065		flavor = "peep"
1066	}
1067	if url == "" {
1068		return
1069	}
1070	if url[0] == '@' {
1071		url = gofish(url)
1072	}
1073	if url == "" {
1074		return
1075	}
1076	_, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1077	if err != nil {
1078		log.Print(err)
1079		return
1080	}
1081	if flavor == "presub" {
1082		user, _ := butwhatabout(u.Username)
1083		go subsub(user, url)
1084	}
1085	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1086}
1087
1088type Zonker struct {
1089	ID        int64
1090	Name      string
1091	Wherefore string
1092}
1093
1094func killzone(w http.ResponseWriter, r *http.Request) {
1095	db := opendatabase()
1096	userinfo := login.GetUserInfo(r)
1097	rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1098	if err != nil {
1099		log.Printf("err: %s", err)
1100		return
1101	}
1102	var zonkers []Zonker
1103	for rows.Next() {
1104		var z Zonker
1105		rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1106		zonkers = append(zonkers, z)
1107	}
1108	templinfo := getInfo(r)
1109	templinfo["Zonkers"] = zonkers
1110	templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1111	err = readviews.Execute(w, "zonkers.html", templinfo)
1112	if err != nil {
1113		log.Print(err)
1114	}
1115}
1116
1117func killitwithfire(w http.ResponseWriter, r *http.Request) {
1118	userinfo := login.GetUserInfo(r)
1119	itsok := r.FormValue("itsok")
1120	if itsok == "iforgiveyou" {
1121		zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1122		db := opendatabase()
1123		db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1124			userinfo.UserID, zonkerid)
1125		bitethethumbs()
1126		http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1127		return
1128	}
1129	wherefore := r.FormValue("wherefore")
1130	name := r.FormValue("name")
1131	if name == "" {
1132		return
1133	}
1134	switch wherefore {
1135	case "zonker":
1136	case "zurl":
1137	case "zonvoy":
1138	default:
1139		return
1140	}
1141	db := opendatabase()
1142	db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1143		userinfo.UserID, name, wherefore)
1144	if wherefore == "zonker" || wherefore == "zurl" {
1145		bitethethumbs()
1146	}
1147
1148	http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1149}
1150
1151func somedays() string {
1152	secs := 432000 + notrand.Int63n(432000)
1153	return fmt.Sprintf("%d", secs)
1154}
1155
1156func avatate(w http.ResponseWriter, r *http.Request) {
1157	n := r.FormValue("a")
1158	a := avatar(n)
1159	w.Header().Set("Cache-Control", "max-age="+somedays())
1160	w.Write(a)
1161}
1162
1163func servecss(w http.ResponseWriter, r *http.Request) {
1164	w.Header().Set("Cache-Control", "max-age=7776000")
1165	http.ServeFile(w, r, "views"+r.URL.Path)
1166}
1167func servehtml(w http.ResponseWriter, r *http.Request) {
1168	templinfo := getInfo(r)
1169	err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1170	if err != nil {
1171		log.Print(err)
1172	}
1173}
1174func serveemu(w http.ResponseWriter, r *http.Request) {
1175	xid := mux.Vars(r)["xid"]
1176	w.Header().Set("Cache-Control", "max-age="+somedays())
1177	http.ServeFile(w, r, "emus/"+xid)
1178}
1179
1180func servefile(w http.ResponseWriter, r *http.Request) {
1181	xid := mux.Vars(r)["xid"]
1182	row := stmtFileData.QueryRow(xid)
1183	var media string
1184	var data []byte
1185	err := row.Scan(&media, &data)
1186	if err != nil {
1187		log.Printf("error loading file: %s", err)
1188		http.NotFound(w, r)
1189		return
1190	}
1191	w.Header().Set("Content-Type", media)
1192	w.Header().Set("X-Content-Type-Options", "nosniff")
1193	w.Header().Set("Cache-Control", "max-age="+somedays())
1194	w.Write(data)
1195}
1196
1197func serve() {
1198	db := opendatabase()
1199	login.Init(db)
1200
1201	listener, err := openListener()
1202	if err != nil {
1203		log.Fatal(err)
1204	}
1205	go redeliverator()
1206
1207	debug := false
1208	getconfig("debug", &debug)
1209	readviews = templates.Load(debug,
1210		"views/honkpage.html",
1211		"views/honkers.html",
1212		"views/zonkers.html",
1213		"views/honkform.html",
1214		"views/honk.html",
1215		"views/login.html",
1216		"views/header.html",
1217	)
1218	if !debug {
1219		s := "views/style.css"
1220		savedstyleparams[s] = getstyleparam(s)
1221		s = "views/local.css"
1222		savedstyleparams[s] = getstyleparam(s)
1223	}
1224
1225	bitethethumbs()
1226
1227	mux := mux.NewRouter()
1228	mux.Use(login.Checker)
1229
1230	posters := mux.Methods("POST").Subrouter()
1231	getters := mux.Methods("GET").Subrouter()
1232
1233	getters.HandleFunc("/", homepage)
1234	getters.HandleFunc("/rss", showrss)
1235	getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1236	getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1237	getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1238	posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1239	getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1240	getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1241	getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1242	getters.HandleFunc("/a", avatate)
1243	getters.HandleFunc("/t", showconvoy)
1244	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1245	getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1246	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1247
1248	getters.HandleFunc("/style.css", servecss)
1249	getters.HandleFunc("/local.css", servecss)
1250	getters.HandleFunc("/login", servehtml)
1251	posters.HandleFunc("/dologin", login.LoginFunc)
1252	getters.HandleFunc("/logout", login.LogoutFunc)
1253
1254	loggedin := mux.NewRoute().Subrouter()
1255	loggedin.Use(login.Required)
1256	loggedin.HandleFunc("/atme", homepage)
1257	loggedin.HandleFunc("/killzone", killzone)
1258	loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1259	loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1260	loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1261	loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1262	loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1263	loggedin.HandleFunc("/honkers", showhonkers)
1264	loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1265	loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1266	loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1267
1268	err = http.Serve(listener, mux)
1269	if err != nil {
1270		log.Fatal(err)
1271	}
1272}
1273
1274var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1275var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1276var stmtHonksForUser, stmtHonksForMe, stmtDeleteHonk, stmtSaveDub *sql.Stmt
1277var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1278var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1279var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1280var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtSaveZonker *sql.Stmt
1281var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1282
1283func preparetodie(db *sql.DB, s string) *sql.Stmt {
1284	stmt, err := db.Prepare(s)
1285	if err != nil {
1286		log.Fatalf("error %s: %s", err, s)
1287	}
1288	return stmt
1289}
1290
1291func prepareStatements(db *sql.DB) {
1292	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")
1293	stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1294	stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1295	stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1296	stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1297	stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1298
1299	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 "
1300	limit := " order by honkid desc limit 250"
1301	butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1302	stmtOneXonk = preparetodie(db, selecthonks+"where xid = ?")
1303	stmtPublicHonks = preparetodie(db, selecthonks+"where honker = ''"+limit)
1304	stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ?"+limit)
1305	stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ?"+butnotthose+limit)
1306	stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1307	stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1308	stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1309	stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1310
1311	stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1312	stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1313	stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1314	stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1315	stmtDeleteHonk = preparetodie(db, "update honks set what = 'zonk' where xid = ? and honker = ? and userid = ?")
1316	stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1317	stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1318	stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1319	stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1320	stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1321	stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1322	stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1323	stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1324	stmtZonkIt = preparetodie(db, "update honks set what = 'zonk' where userid = ? and xid = ?")
1325	stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1326	stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1327	stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1328	stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1329}
1330
1331func ElaborateUnitTests() {
1332}
1333
1334func finishusersetup() error {
1335	db := opendatabase()
1336	k, err := rsa.GenerateKey(rand.Reader, 2048)
1337	if err != nil {
1338		return err
1339	}
1340	pubkey, err := zem(&k.PublicKey)
1341	if err != nil {
1342		return err
1343	}
1344	seckey, err := zem(k)
1345	if err != nil {
1346		return err
1347	}
1348	_, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1349	if err != nil {
1350		return err
1351	}
1352	return nil
1353}
1354
1355func main() {
1356	cmd := "run"
1357	if len(os.Args) > 1 {
1358		cmd = os.Args[1]
1359	}
1360	switch cmd {
1361	case "init":
1362		initdb()
1363	case "upgrade":
1364		upgradedb()
1365	}
1366	db := opendatabase()
1367	dbversion := 0
1368	getconfig("dbversion", &dbversion)
1369	if dbversion != myVersion {
1370		log.Fatal("incorrect database version. run upgrade.")
1371	}
1372	getconfig("servername", &serverName)
1373	prepareStatements(db)
1374	switch cmd {
1375	case "ping":
1376		if len(os.Args) < 4 {
1377			fmt.Printf("usage: honk ping from to\n")
1378			return
1379		}
1380		name := os.Args[2]
1381		targ := os.Args[3]
1382		user, err := butwhatabout(name)
1383		if err != nil {
1384			log.Printf("unknown user")
1385			return
1386		}
1387		ping(user, targ)
1388	case "peep":
1389		peeppeep()
1390	case "run":
1391		serve()
1392	case "test":
1393		ElaborateUnitTests()
1394	default:
1395		log.Fatal("unknown command")
1396	}
1397}