all repos — honk @ v0.4.0

my fork of honk

honk.go (view raw)

   1//
   2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
   3//
   4// Permission to use, copy, modify, and distribute this software for any
   5// purpose with or without fee is hereby granted, provided that the above
   6// copyright notice and this permission notice appear in all copies.
   7//
   8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15
  16package main
  17
  18import (
  19	"bytes"
  20	"crypto/rand"
  21	"crypto/rsa"
  22	"database/sql"
  23	"fmt"
  24	"html"
  25	"html/template"
  26	"image"
  27	"io"
  28	"log"
  29	notrand "math/rand"
  30	"net/http"
  31	"os"
  32	"sort"
  33	"strconv"
  34	"strings"
  35	"sync"
  36	"time"
  37
  38	"github.com/gorilla/mux"
  39	"humungus.tedunangst.com/r/webs/login"
  40	"humungus.tedunangst.com/r/webs/templates"
  41)
  42
  43type WhatAbout struct {
  44	ID      int64
  45	Name    string
  46	Display string
  47	About   string
  48	Key     string
  49	URL     string
  50}
  51
  52type Honk struct {
  53	ID       int64
  54	UserID   int64
  55	Username string
  56	What     string
  57	Honker   string
  58	XID      string
  59	RID      string
  60	Date     time.Time
  61	URL      string
  62	Noise    string
  63	Convoy   string
  64	Audience []string
  65	Privacy  string
  66	HTML     template.HTML
  67	Donks    []*Donk
  68}
  69
  70type Donk struct {
  71	FileID  int64
  72	XID     string
  73	Name    string
  74	URL     string
  75	Media   string
  76	Content []byte
  77}
  78
  79type Honker struct {
  80	ID     int64
  81	UserID int64
  82	Name   string
  83	XID    string
  84	Flavor string
  85	Combos []string
  86}
  87
  88var serverName string
  89var iconName = "icon.png"
  90
  91var readviews *templates.Template
  92
  93func getInfo(r *http.Request) map[string]interface{} {
  94	templinfo := make(map[string]interface{})
  95	templinfo["StyleParam"] = getstyleparam("views/style.css")
  96	templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
  97	templinfo["ServerName"] = serverName
  98	templinfo["IconName"] = iconName
  99	templinfo["UserInfo"] = login.GetUserInfo(r)
 100	templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
 101	return templinfo
 102}
 103
 104func homepage(w http.ResponseWriter, r *http.Request) {
 105	templinfo := getInfo(r)
 106	u := login.GetUserInfo(r)
 107	var honks []*Honk
 108	if u != nil {
 109		if r.URL.Path == "/atme" {
 110			honks = gethonksforme(u.UserID)
 111		} else {
 112			honks = gethonksforuser(u.UserID)
 113		}
 114		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 115	} else {
 116		honks = getpublichonks()
 117	}
 118	sort.Slice(honks, func(i, j int) bool {
 119		return honks[i].Date.After(honks[j].Date)
 120	})
 121
 122	var modtime time.Time
 123	if len(honks) > 0 {
 124		modtime = honks[0].Date
 125	}
 126	debug := false
 127	getconfig("debug", &debug)
 128	imh := r.Header.Get("If-Modified-Since")
 129	if !debug && imh != "" && !modtime.IsZero() {
 130		ifmod, err := time.Parse(http.TimeFormat, imh)
 131		if err == nil && !modtime.After(ifmod) {
 132			w.WriteHeader(http.StatusNotModified)
 133			return
 134		}
 135	}
 136	reverbolate(honks)
 137
 138	msg := "Things happen."
 139	getconfig("servermsg", &msg)
 140	templinfo["Honks"] = honks
 141	templinfo["ShowRSS"] = true
 142	templinfo["ServerMessage"] = msg
 143	if u == nil {
 144		w.Header().Set("Cache-Control", "max-age=60")
 145	} else {
 146		w.Header().Set("Cache-Control", "max-age=0")
 147	}
 148	w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
 149	err := readviews.Execute(w, "honkpage.html", templinfo)
 150	if err != nil {
 151		log.Print(err)
 152	}
 153}
 154
 155func showrss(w http.ResponseWriter, r *http.Request) {
 156	name := mux.Vars(r)["name"]
 157
 158	var honks []*Honk
 159	if name != "" {
 160		honks = gethonksbyuser(name)
 161	} else {
 162		honks = getpublichonks()
 163	}
 164	sort.Slice(honks, func(i, j int) bool {
 165		return honks[i].Date.After(honks[j].Date)
 166	})
 167	reverbolate(honks)
 168
 169	home := fmt.Sprintf("https://%s/", serverName)
 170	base := home
 171	if name != "" {
 172		home += "u/" + name
 173		name += " "
 174	}
 175	feed := RssFeed{
 176		Title:       name + "honk",
 177		Link:        home,
 178		Description: name + "honk rss",
 179		FeedImage: &RssFeedImage{
 180			URL:   base + "icon.png",
 181			Title: name + "honk rss",
 182			Link:  home,
 183		},
 184	}
 185	var modtime time.Time
 186	past := time.Now().UTC().Add(-3 * 24 * time.Hour)
 187	for _, honk := range honks {
 188		if honk.Date.Before(past) {
 189			break
 190		}
 191		desc := string(honk.HTML)
 192		for _, d := range honk.Donks {
 193			desc += fmt.Sprintf(`<p><a href="%sd/%s">Attachment: %s</a>`,
 194				base, d.XID, html.EscapeString(d.Name))
 195		}
 196
 197		feed.Items = append(feed.Items, &RssItem{
 198			Title:       fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
 199			Description: RssCData{desc},
 200			Link:        honk.URL,
 201			PubDate:     honk.Date.Format(time.RFC1123),
 202		})
 203		if honk.Date.After(modtime) {
 204			modtime = honk.Date
 205		}
 206	}
 207	w.Header().Set("Cache-Control", "max-age=300")
 208	w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
 209
 210	err := feed.Write(w)
 211	if err != nil {
 212		log.Printf("error writing rss: %s", err)
 213	}
 214}
 215
 216func butwhatabout(name string) (*WhatAbout, error) {
 217	row := stmtWhatAbout.QueryRow(name)
 218	var user WhatAbout
 219	err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key)
 220	user.URL = fmt.Sprintf("https://%s/u/%s", serverName, user.Name)
 221	return &user, err
 222}
 223
 224func crappola(j map[string]interface{}) bool {
 225	t, _ := jsongetstring(j, "type")
 226	a, _ := jsongetstring(j, "actor")
 227	o, _ := jsongetstring(j, "object")
 228	if t == "Delete" && a == o {
 229		log.Printf("crappola from %s", a)
 230		return true
 231	}
 232	return false
 233}
 234
 235func ping(user *WhatAbout, who string) {
 236	box, err := getboxes(who)
 237	if err != nil {
 238		log.Printf("no inbox for ping: %s", err)
 239		return
 240	}
 241	j := NewJunk()
 242	j["@context"] = itiswhatitis
 243	j["type"] = "Ping"
 244	j["id"] = user.URL + "/ping/" + xfiltrate()
 245	j["actor"] = user.URL
 246	j["to"] = who
 247	keyname, key := ziggy(user.Name)
 248	err = PostJunk(keyname, key, box.In, j)
 249	if err != nil {
 250		log.Printf("can't send ping: %s", err)
 251		return
 252	}
 253	log.Printf("sent ping to %s: %s", who, j["id"])
 254}
 255
 256func pong(user *WhatAbout, who string, obj string) {
 257	box, err := getboxes(who)
 258	if err != nil {
 259		log.Printf("no inbox for pong %s : %s", who, err)
 260		return
 261	}
 262	j := NewJunk()
 263	j["@context"] = itiswhatitis
 264	j["type"] = "Pong"
 265	j["id"] = user.URL + "/pong/" + xfiltrate()
 266	j["actor"] = user.URL
 267	j["to"] = who
 268	j["object"] = obj
 269	keyname, key := ziggy(user.Name)
 270	err = PostJunk(keyname, key, box.In, j)
 271	if err != nil {
 272		log.Printf("can't send pong: %s", err)
 273		return
 274	}
 275}
 276
 277func inbox(w http.ResponseWriter, r *http.Request) {
 278	name := mux.Vars(r)["name"]
 279	user, err := butwhatabout(name)
 280	if err != nil {
 281		http.NotFound(w, r)
 282		return
 283	}
 284	var buf bytes.Buffer
 285	io.Copy(&buf, r.Body)
 286	payload := buf.Bytes()
 287	j, err := ReadJunk(bytes.NewReader(payload))
 288	if err != nil {
 289		log.Printf("bad payload: %s", err)
 290		io.WriteString(os.Stdout, "bad payload\n")
 291		os.Stdout.Write(payload)
 292		io.WriteString(os.Stdout, "\n")
 293		return
 294	}
 295	if crappola(j) {
 296		return
 297	}
 298	keyname, err := zag(r, payload)
 299	if err != nil {
 300		log.Printf("inbox message failed signature: %s", err)
 301		if keyname != "" {
 302			keyname, err = makeitworksomehowwithoutregardforkeycontinuity(keyname, r, payload)
 303		}
 304		if err != nil {
 305			fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 306			io.WriteString(fd, "bad signature:\n")
 307			WriteJunk(fd, j)
 308			io.WriteString(fd, "\n")
 309			fd.Close()
 310			return
 311		}
 312	}
 313	what, _ := jsongetstring(j, "type")
 314	if what == "Like" {
 315		return
 316	}
 317	who, _ := jsongetstring(j, "actor")
 318	if !keymatch(keyname, who, what, user.ID) {
 319		log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
 320		return
 321	}
 322	objid, _ := jsongetstring(j, "id")
 323	if thoudostbitethythumb(user.ID, []string{who}, objid) {
 324		log.Printf("ignoring thumb sucker %s", who)
 325		return
 326	}
 327	fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 328	WriteJunk(fd, j)
 329	io.WriteString(fd, "\n")
 330	fd.Close()
 331	switch what {
 332	case "Ping":
 333		obj, _ := jsongetstring(j, "id")
 334		log.Printf("ping from %s: %s", who, obj)
 335		pong(user, who, obj)
 336	case "Pong":
 337		obj, _ := jsongetstring(j, "object")
 338		log.Printf("pong from %s: %s", who, obj)
 339	case "Follow":
 340		obj, _ := jsongetstring(j, "object")
 341		if obj == user.URL {
 342			log.Printf("updating honker follow: %s", who)
 343			rubadubdub(user, j)
 344		} else {
 345			log.Printf("can't follow %s", obj)
 346		}
 347	case "Accept":
 348		log.Printf("updating honker accept: %s", who)
 349		_, err = stmtUpdateFlavor.Exec("sub", user.ID, who, "presub")
 350		if err != nil {
 351			log.Printf("error updating honker: %s", err)
 352			return
 353		}
 354	case "Undo":
 355		obj, ok := jsongetmap(j, "object")
 356		if !ok {
 357			log.Printf("unknown undo no object")
 358		} else {
 359			what, _ := jsongetstring(obj, "type")
 360			switch what {
 361			case "Follow":
 362				log.Printf("updating honker undo: %s", who)
 363				_, err = stmtUpdateFlavor.Exec("undub", user.ID, who, "dub")
 364				if err != nil {
 365					log.Printf("error updating honker: %s", err)
 366					return
 367				}
 368			case "Like":
 369			default:
 370				log.Printf("unknown undo: %s", what)
 371			}
 372		}
 373	default:
 374		xonk := xonkxonk(user, j)
 375		if xonk != nil {
 376			savexonk(user, xonk)
 377		}
 378	}
 379}
 380
 381func outbox(w http.ResponseWriter, r *http.Request) {
 382	name := mux.Vars(r)["name"]
 383	user, err := butwhatabout(name)
 384	if err != nil {
 385		http.NotFound(w, r)
 386		return
 387	}
 388	honks := gethonksbyuser(name)
 389
 390	var jonks []map[string]interface{}
 391	for _, h := range honks {
 392		j, _ := jonkjonk(user, h)
 393		jonks = append(jonks, j)
 394	}
 395
 396	j := NewJunk()
 397	j["@context"] = itiswhatitis
 398	j["id"] = user.URL + "/outbox"
 399	j["type"] = "OrderedCollection"
 400	j["totalItems"] = len(jonks)
 401	j["orderedItems"] = jonks
 402
 403	w.Header().Set("Cache-Control", "max-age=60")
 404	w.Header().Set("Content-Type", theonetruename)
 405	WriteJunk(w, j)
 406}
 407
 408func emptiness(w http.ResponseWriter, r *http.Request) {
 409	name := mux.Vars(r)["name"]
 410	user, err := butwhatabout(name)
 411	if err != nil {
 412		http.NotFound(w, r)
 413		return
 414	}
 415	colname := "/followers"
 416	if strings.HasSuffix(r.URL.Path, "/following") {
 417		colname = "/following"
 418	}
 419	j := NewJunk()
 420	j["@context"] = itiswhatitis
 421	j["id"] = user.URL + colname
 422	j["type"] = "OrderedCollection"
 423	j["totalItems"] = 0
 424	j["orderedItems"] = []interface{}{}
 425
 426	w.Header().Set("Cache-Control", "max-age=60")
 427	w.Header().Set("Content-Type", theonetruename)
 428	WriteJunk(w, j)
 429}
 430
 431func showuser(w http.ResponseWriter, r *http.Request) {
 432	name := mux.Vars(r)["name"]
 433	user, err := butwhatabout(name)
 434	if err != nil {
 435		http.NotFound(w, r)
 436		return
 437	}
 438	if friendorfoe(r.Header.Get("Accept")) {
 439		j := asjonker(user)
 440		w.Header().Set("Cache-Control", "max-age=600")
 441		w.Header().Set("Content-Type", theonetruename)
 442		WriteJunk(w, j)
 443		return
 444	}
 445	honks := gethonksbyuser(name)
 446	u := login.GetUserInfo(r)
 447	honkpage(w, r, u, user, honks, "")
 448}
 449
 450func showhonker(w http.ResponseWriter, r *http.Request) {
 451	name := mux.Vars(r)["name"]
 452	u := login.GetUserInfo(r)
 453	honks := gethonksbyhonker(u.UserID, name)
 454	honkpage(w, r, u, nil, honks, "honks by honker: "+name)
 455}
 456
 457func showcombo(w http.ResponseWriter, r *http.Request) {
 458	name := mux.Vars(r)["name"]
 459	u := login.GetUserInfo(r)
 460	honks := gethonksbycombo(u.UserID, name)
 461	honkpage(w, r, u, nil, honks, "honks by combo: "+name)
 462}
 463func showconvoy(w http.ResponseWriter, r *http.Request) {
 464	c := r.FormValue("c")
 465	var userid int64 = -1
 466	u := login.GetUserInfo(r)
 467	if u != nil {
 468		userid = u.UserID
 469	}
 470	honks := gethonksbyconvoy(userid, c)
 471	honkpage(w, r, u, nil, honks, "honks in convoy: "+c)
 472}
 473
 474func fingerlicker(w http.ResponseWriter, r *http.Request) {
 475	orig := r.FormValue("resource")
 476
 477	log.Printf("finger lick: %s", orig)
 478
 479	if strings.HasPrefix(orig, "acct:") {
 480		orig = orig[5:]
 481	}
 482
 483	name := orig
 484	idx := strings.LastIndexByte(name, '/')
 485	if idx != -1 {
 486		name = name[idx+1:]
 487		if "https://"+serverName+"/u/"+name != orig {
 488			log.Printf("foreign request rejected")
 489			name = ""
 490		}
 491	} else {
 492		idx = strings.IndexByte(name, '@')
 493		if idx != -1 {
 494			name = name[:idx]
 495			if name+"@"+serverName != orig {
 496				log.Printf("foreign request rejected")
 497				name = ""
 498			}
 499		}
 500	}
 501	user, err := butwhatabout(name)
 502	if err != nil {
 503		http.NotFound(w, r)
 504		return
 505	}
 506
 507	j := NewJunk()
 508	j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
 509	j["aliases"] = []string{user.URL}
 510	var links []map[string]interface{}
 511	l := NewJunk()
 512	l["rel"] = "self"
 513	l["type"] = `application/activity+json`
 514	l["href"] = user.URL
 515	links = append(links, l)
 516	j["links"] = links
 517
 518	w.Header().Set("Cache-Control", "max-age=3600")
 519	w.Header().Set("Content-Type", "application/jrd+json")
 520	WriteJunk(w, j)
 521}
 522
 523func showhonk(w http.ResponseWriter, r *http.Request) {
 524	name := mux.Vars(r)["name"]
 525	xid := mux.Vars(r)["xid"]
 526	user, err := butwhatabout(name)
 527	if err != nil {
 528		http.NotFound(w, r)
 529		return
 530	}
 531	h := getxonk(name, xid)
 532	if h == nil {
 533		http.NotFound(w, r)
 534		return
 535	}
 536	if friendorfoe(r.Header.Get("Accept")) {
 537		_, j := jonkjonk(user, h)
 538		j["@context"] = itiswhatitis
 539		w.Header().Set("Cache-Control", "max-age=3600")
 540		w.Header().Set("Content-Type", theonetruename)
 541		WriteJunk(w, j)
 542		return
 543	}
 544	u := login.GetUserInfo(r)
 545	honkpage(w, r, u, nil, []*Honk{h}, "one honk")
 546}
 547
 548func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
 549	honks []*Honk, infomsg string) {
 550	reverbolate(honks)
 551	templinfo := getInfo(r)
 552	if u != nil {
 553		if user != nil && u.Username == user.Name {
 554			templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
 555		}
 556		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 557	}
 558	if u == nil {
 559		w.Header().Set("Cache-Control", "max-age=60")
 560	}
 561	if user != nil {
 562		templinfo["Name"] = user.Name
 563		whatabout := user.About
 564		templinfo["RawWhatAbout"] = whatabout
 565		whatabout = obfusbreak(whatabout)
 566		templinfo["WhatAbout"] = cleanstring(whatabout)
 567	}
 568	templinfo["Honks"] = honks
 569	templinfo["ServerMessage"] = infomsg
 570	err := readviews.Execute(w, "honkpage.html", templinfo)
 571	if err != nil {
 572		log.Print(err)
 573	}
 574}
 575
 576func saveuser(w http.ResponseWriter, r *http.Request) {
 577	whatabout := r.FormValue("whatabout")
 578	u := login.GetUserInfo(r)
 579	db := opendatabase()
 580	_, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
 581	if err != nil {
 582		log.Printf("error bouting what: %s", err)
 583	}
 584
 585	http.Redirect(w, r, "/u/"+u.Username, http.StatusSeeOther)
 586}
 587
 588func gethonkers(userid int64) []*Honker {
 589	rows, err := stmtHonkers.Query(userid)
 590	if err != nil {
 591		log.Printf("error querying honkers: %s", err)
 592		return nil
 593	}
 594	defer rows.Close()
 595	var honkers []*Honker
 596	for rows.Next() {
 597		var f Honker
 598		var combos string
 599		err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
 600		f.Combos = strings.Split(strings.TrimSpace(combos), " ")
 601		if err != nil {
 602			log.Printf("error scanning honker: %s", err)
 603			return nil
 604		}
 605		honkers = append(honkers, &f)
 606	}
 607	return honkers
 608}
 609
 610func getdubs(userid int64) []*Honker {
 611	rows, err := stmtDubbers.Query(userid)
 612	if err != nil {
 613		log.Printf("error querying dubs: %s", err)
 614		return nil
 615	}
 616	defer rows.Close()
 617	var honkers []*Honker
 618	for rows.Next() {
 619		var f Honker
 620		err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
 621		if err != nil {
 622			log.Printf("error scanning honker: %s", err)
 623			return nil
 624		}
 625		honkers = append(honkers, &f)
 626	}
 627	return honkers
 628}
 629
 630func getxonk(name, xid string) *Honk {
 631	var h Honk
 632	var dt, aud string
 633	row := stmtOneXonk.QueryRow(xid)
 634	err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
 635		&dt, &h.URL, &aud, &h.Noise, &h.Convoy)
 636	if err != nil {
 637		if err != sql.ErrNoRows {
 638			log.Printf("error scanning xonk: %s", err)
 639		}
 640		return nil
 641	}
 642	if name != "" && h.Username != name {
 643		log.Printf("user xonk mismatch")
 644		return nil
 645	}
 646	h.Date, _ = time.Parse(dbtimeformat, dt)
 647	h.Audience = strings.Split(aud, " ")
 648	donksforhonks([]*Honk{&h})
 649	return &h
 650}
 651
 652func getpublichonks() []*Honk {
 653	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
 978func showcombos(w http.ResponseWriter, r *http.Request) {
 979	userinfo := login.GetUserInfo(r)
 980	templinfo := getInfo(r)
 981	honkers := gethonkers(userinfo.UserID)
 982	var combos []string
 983	for _, h := range honkers {
 984		combos = append(combos, h.Combos...)
 985	}
 986	combos = oneofakind(combos)
 987	sort.Strings(combos)
 988	templinfo["Combos"] = combos
 989	err := readviews.Execute(w, "combos.html", templinfo)
 990	if err != nil {
 991		log.Print(err)
 992	}
 993}
 994
 995var handfull = make(map[string]string)
 996var handlock sync.Mutex
 997
 998func gofish(name string) string {
 999	if name[0] == '@' {
1000		name = name[1:]
1001	}
1002	m := strings.Split(name, "@")
1003	if len(m) != 2 {
1004		log.Printf("bad fish name: %s", name)
1005		return ""
1006	}
1007	handlock.Lock()
1008	ref, ok := handfull[name]
1009	handlock.Unlock()
1010	if ok {
1011		return ref
1012	}
1013	j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1014	handlock.Lock()
1015	defer handlock.Unlock()
1016	if err != nil {
1017		log.Printf("failed to go fish %s: %s", name, err)
1018		handfull[name] = ""
1019		return ""
1020	}
1021	links, _ := jsongetarray(j, "links")
1022	for _, l := range links {
1023		href, _ := jsongetstring(l, "href")
1024		rel, _ := jsongetstring(l, "rel")
1025		t, _ := jsongetstring(l, "type")
1026		if rel == "self" && friendorfoe(t) {
1027			handfull[name] = href
1028			return href
1029		}
1030	}
1031	handfull[name] = ""
1032	return ""
1033}
1034
1035func savehonker(w http.ResponseWriter, r *http.Request) {
1036	u := login.GetUserInfo(r)
1037	name := r.FormValue("name")
1038	url := r.FormValue("url")
1039	peep := r.FormValue("peep")
1040	combos := r.FormValue("combos")
1041	honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1042
1043	if honkerid > 0 {
1044		goodbye := r.FormValue("goodbye")
1045		if goodbye == "goodbye" {
1046			db := opendatabase()
1047			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1048				honkerid, u.UserID)
1049			var xid string
1050			err := row.Scan(&xid)
1051			if err != nil {
1052				log.Printf("can't get honker xid: %s", err)
1053				return
1054			}
1055			log.Printf("unsubscribing from %s", xid)
1056			user, _ := butwhatabout(u.Username)
1057			err = itakeitallback(user, xid)
1058			if err != nil {
1059				log.Printf("can't take it back: %s", err)
1060			} else {
1061				_, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1062				if err != nil {
1063					log.Printf("error updating honker: %s", err)
1064					return
1065				}
1066			}
1067
1068			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1069			return
1070		}
1071		combos = " " + strings.TrimSpace(combos) + " "
1072		_, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1073		if err != nil {
1074			log.Printf("update honker err: %s", err)
1075			return
1076		}
1077		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1078	}
1079
1080	flavor := "presub"
1081	if peep == "peep" {
1082		flavor = "peep"
1083	}
1084	if url == "" {
1085		return
1086	}
1087	if url[0] == '@' {
1088		url = gofish(url)
1089	}
1090	if url == "" {
1091		return
1092	}
1093	_, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1094	if err != nil {
1095		log.Print(err)
1096		return
1097	}
1098	if flavor == "presub" {
1099		user, _ := butwhatabout(u.Username)
1100		go subsub(user, url)
1101	}
1102	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1103}
1104
1105type Zonker struct {
1106	ID        int64
1107	Name      string
1108	Wherefore string
1109}
1110
1111func killzone(w http.ResponseWriter, r *http.Request) {
1112	db := opendatabase()
1113	userinfo := login.GetUserInfo(r)
1114	rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1115	if err != nil {
1116		log.Printf("err: %s", err)
1117		return
1118	}
1119	var zonkers []Zonker
1120	for rows.Next() {
1121		var z Zonker
1122		rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1123		zonkers = append(zonkers, z)
1124	}
1125	templinfo := getInfo(r)
1126	templinfo["Zonkers"] = zonkers
1127	templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1128	err = readviews.Execute(w, "zonkers.html", templinfo)
1129	if err != nil {
1130		log.Print(err)
1131	}
1132}
1133
1134func killitwithfire(w http.ResponseWriter, r *http.Request) {
1135	userinfo := login.GetUserInfo(r)
1136	itsok := r.FormValue("itsok")
1137	if itsok == "iforgiveyou" {
1138		zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1139		db := opendatabase()
1140		db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1141			userinfo.UserID, zonkerid)
1142		bitethethumbs()
1143		http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1144		return
1145	}
1146	wherefore := r.FormValue("wherefore")
1147	name := r.FormValue("name")
1148	if name == "" {
1149		return
1150	}
1151	switch wherefore {
1152	case "zonker":
1153	case "zurl":
1154	case "zonvoy":
1155	default:
1156		return
1157	}
1158	db := opendatabase()
1159	db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1160		userinfo.UserID, name, wherefore)
1161	if wherefore == "zonker" || wherefore == "zurl" {
1162		bitethethumbs()
1163	}
1164
1165	http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1166}
1167
1168func somedays() string {
1169	secs := 432000 + notrand.Int63n(432000)
1170	return fmt.Sprintf("%d", secs)
1171}
1172
1173func avatate(w http.ResponseWriter, r *http.Request) {
1174	n := r.FormValue("a")
1175	a := avatar(n)
1176	w.Header().Set("Cache-Control", "max-age="+somedays())
1177	w.Write(a)
1178}
1179
1180func servecss(w http.ResponseWriter, r *http.Request) {
1181	w.Header().Set("Cache-Control", "max-age=7776000")
1182	http.ServeFile(w, r, "views"+r.URL.Path)
1183}
1184func servehtml(w http.ResponseWriter, r *http.Request) {
1185	templinfo := getInfo(r)
1186	err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1187	if err != nil {
1188		log.Print(err)
1189	}
1190}
1191func serveemu(w http.ResponseWriter, r *http.Request) {
1192	xid := mux.Vars(r)["xid"]
1193	w.Header().Set("Cache-Control", "max-age="+somedays())
1194	http.ServeFile(w, r, "emus/"+xid)
1195}
1196
1197func servefile(w http.ResponseWriter, r *http.Request) {
1198	xid := mux.Vars(r)["xid"]
1199	row := stmtFileData.QueryRow(xid)
1200	var media string
1201	var data []byte
1202	err := row.Scan(&media, &data)
1203	if err != nil {
1204		log.Printf("error loading file: %s", err)
1205		http.NotFound(w, r)
1206		return
1207	}
1208	w.Header().Set("Content-Type", media)
1209	w.Header().Set("X-Content-Type-Options", "nosniff")
1210	w.Header().Set("Cache-Control", "max-age="+somedays())
1211	w.Write(data)
1212}
1213
1214func serve() {
1215	db := opendatabase()
1216	login.Init(db)
1217
1218	listener, err := openListener()
1219	if err != nil {
1220		log.Fatal(err)
1221	}
1222	go redeliverator()
1223
1224	debug := false
1225	getconfig("debug", &debug)
1226	readviews = templates.Load(debug,
1227		"views/honkpage.html",
1228		"views/honkers.html",
1229		"views/zonkers.html",
1230		"views/combos.html",
1231		"views/honkform.html",
1232		"views/honk.html",
1233		"views/login.html",
1234		"views/header.html",
1235	)
1236	if !debug {
1237		s := "views/style.css"
1238		savedstyleparams[s] = getstyleparam(s)
1239		s = "views/local.css"
1240		savedstyleparams[s] = getstyleparam(s)
1241	}
1242
1243	bitethethumbs()
1244
1245	mux := mux.NewRouter()
1246	mux.Use(login.Checker)
1247
1248	posters := mux.Methods("POST").Subrouter()
1249	getters := mux.Methods("GET").Subrouter()
1250
1251	getters.HandleFunc("/", homepage)
1252	getters.HandleFunc("/rss", showrss)
1253	getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1254	getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1255	getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1256	posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1257	getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1258	getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1259	getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1260	getters.HandleFunc("/a", avatate)
1261	getters.HandleFunc("/t", showconvoy)
1262	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1263	getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1264	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1265
1266	getters.HandleFunc("/style.css", servecss)
1267	getters.HandleFunc("/local.css", servecss)
1268	getters.HandleFunc("/login", servehtml)
1269	posters.HandleFunc("/dologin", login.LoginFunc)
1270	getters.HandleFunc("/logout", login.LogoutFunc)
1271
1272	loggedin := mux.NewRoute().Subrouter()
1273	loggedin.Use(login.Required)
1274	loggedin.HandleFunc("/atme", homepage)
1275	loggedin.HandleFunc("/killzone", killzone)
1276	loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1277	loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1278	loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1279	loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1280	loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1281	loggedin.HandleFunc("/honkers", showhonkers)
1282	loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1283	loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1284	loggedin.HandleFunc("/c", showcombos)
1285	loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1286
1287	err = http.Serve(listener, mux)
1288	if err != nil {
1289		log.Fatal(err)
1290	}
1291}
1292
1293var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1294var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1295var stmtHonksForUser, stmtHonksForMe, stmtDeleteHonk, stmtSaveDub *sql.Stmt
1296var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1297var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1298var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1299var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtSaveZonker *sql.Stmt
1300var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1301
1302func preparetodie(db *sql.DB, s string) *sql.Stmt {
1303	stmt, err := db.Prepare(s)
1304	if err != nil {
1305		log.Fatalf("error %s: %s", err, s)
1306	}
1307	return stmt
1308}
1309
1310func prepareStatements(db *sql.DB) {
1311	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")
1312	stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1313	stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1314	stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1315	stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1316	stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1317
1318	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 "
1319	limit := " order by honkid desc limit 250"
1320	butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1321	stmtOneXonk = preparetodie(db, selecthonks+"where xid = ?")
1322	stmtPublicHonks = preparetodie(db, selecthonks+"where honker = ''"+limit)
1323	stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ?"+limit)
1324	stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ?"+butnotthose+limit)
1325	stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1326	stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1327	stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1328	stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1329
1330	stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1331	stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1332	stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1333	stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1334	stmtDeleteHonk = preparetodie(db, "update honks set what = 'zonk' where xid = ? and honker = ? and userid = ?")
1335	stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1336	stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1337	stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1338	stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1339	stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1340	stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1341	stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1342	stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1343	stmtZonkIt = preparetodie(db, "update honks set what = 'zonk' where userid = ? and xid = ?")
1344	stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1345	stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1346	stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1347	stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1348}
1349
1350func ElaborateUnitTests() {
1351}
1352
1353func finishusersetup() error {
1354	db := opendatabase()
1355	k, err := rsa.GenerateKey(rand.Reader, 2048)
1356	if err != nil {
1357		return err
1358	}
1359	pubkey, err := zem(&k.PublicKey)
1360	if err != nil {
1361		return err
1362	}
1363	seckey, err := zem(k)
1364	if err != nil {
1365		return err
1366	}
1367	_, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1368	if err != nil {
1369		return err
1370	}
1371	return nil
1372}
1373
1374func main() {
1375	cmd := "run"
1376	if len(os.Args) > 1 {
1377		cmd = os.Args[1]
1378	}
1379	switch cmd {
1380	case "init":
1381		initdb()
1382	case "upgrade":
1383		upgradedb()
1384	}
1385	db := opendatabase()
1386	dbversion := 0
1387	getconfig("dbversion", &dbversion)
1388	if dbversion != myVersion {
1389		log.Fatal("incorrect database version. run upgrade.")
1390	}
1391	getconfig("servername", &serverName)
1392	prepareStatements(db)
1393	switch cmd {
1394	case "ping":
1395		if len(os.Args) < 4 {
1396			fmt.Printf("usage: honk ping from to\n")
1397			return
1398		}
1399		name := os.Args[2]
1400		targ := os.Args[3]
1401		user, err := butwhatabout(name)
1402		if err != nil {
1403			log.Printf("unknown user")
1404			return
1405		}
1406		ping(user, targ)
1407	case "peep":
1408		peeppeep()
1409	case "run":
1410		serve()
1411	case "test":
1412		ElaborateUnitTests()
1413	default:
1414		log.Fatal("unknown command")
1415	}
1416}