all repos — honk @ e86a497081d41ff952ffcbd6527761d27e21b0b2

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