all repos — honk @ d27b5e87a29c2f14539c1668da9c62d9ea7fc7ed

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