all repos — honk @ c647edf71634c84a8889ed0c3dcfcbc94aa184ec

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