all repos — honk @ b9c093e48e5c2491e142f9f796bf7bf178e11fe2

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