all repos — honk @ db46dcd27cdb611877347ece8df3b1199a8083cd

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	"database/sql"
  21	"fmt"
  22	"html"
  23	"html/template"
  24	"io"
  25	"io/ioutil"
  26	"log"
  27	notrand "math/rand"
  28	"net/http"
  29	"net/url"
  30	"os"
  31	"sort"
  32	"strconv"
  33	"strings"
  34	"time"
  35
  36	"github.com/gorilla/mux"
  37	"humungus.tedunangst.com/r/webs/css"
  38	"humungus.tedunangst.com/r/webs/htfilter"
  39	"humungus.tedunangst.com/r/webs/httpsig"
  40	"humungus.tedunangst.com/r/webs/image"
  41	"humungus.tedunangst.com/r/webs/junk"
  42	"humungus.tedunangst.com/r/webs/login"
  43	"humungus.tedunangst.com/r/webs/rss"
  44	"humungus.tedunangst.com/r/webs/templates"
  45)
  46
  47type WhatAbout struct {
  48	ID        int64
  49	Name      string
  50	Display   string
  51	About     string
  52	Key       string
  53	URL       string
  54	SkinnyCSS bool
  55}
  56
  57type Honk struct {
  58	ID       int64
  59	UserID   int64
  60	Username string
  61	What     string
  62	Honker   string
  63	Handle   string
  64	Oonker   string
  65	Oondle   string
  66	XID      string
  67	RID      string
  68	Date     time.Time
  69	URL      string
  70	Noise    string
  71	Precis   string
  72	Convoy   string
  73	Audience []string
  74	Public   bool
  75	Whofore  int64
  76	Replies  []*Honk
  77	Flags    int64
  78	HTML     template.HTML
  79	Style    string
  80	Open     string
  81	Donks    []*Donk
  82	Onts     []string
  83}
  84
  85const (
  86	flagIsAcked  = 1
  87	flagIsBonked = 2
  88)
  89
  90func (honk *Honk) IsAcked() bool {
  91	return honk.Flags&flagIsAcked != 0
  92}
  93
  94func (honk *Honk) IsBonked() bool {
  95	return honk.Flags&flagIsBonked != 0
  96}
  97
  98type Donk struct {
  99	FileID  int64
 100	XID     string
 101	Name    string
 102	URL     string
 103	Media   string
 104	Local   bool
 105	Content []byte
 106}
 107
 108type Honker struct {
 109	ID     int64
 110	UserID int64
 111	Name   string
 112	XID    string
 113	Handle string
 114	Flavor string
 115	Combos []string
 116}
 117
 118var serverName string
 119var iconName = "icon.png"
 120var serverMsg = "Things happen."
 121
 122var userSep = "u"
 123var honkSep = "h"
 124
 125var readviews *templates.Template
 126
 127func getuserstyle(u *login.UserInfo) template.CSS {
 128	if u == nil {
 129		return ""
 130	}
 131	user, _ := butwhatabout(u.Username)
 132	if user.SkinnyCSS {
 133		return "main { max-width: 700px; }"
 134	}
 135	return ""
 136}
 137
 138func getInfo(r *http.Request) map[string]interface{} {
 139	u := login.GetUserInfo(r)
 140	templinfo := make(map[string]interface{})
 141	templinfo["StyleParam"] = getstyleparam("views/style.css")
 142	templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
 143	templinfo["UserStyle"] = getuserstyle(u)
 144	templinfo["ServerName"] = serverName
 145	templinfo["IconName"] = iconName
 146	templinfo["UserInfo"] = u
 147	templinfo["UserSep"] = userSep
 148	return templinfo
 149}
 150
 151var donotfedafterdark = make(map[string]bool)
 152
 153func stealthed(r *http.Request) bool {
 154	addr := r.Header.Get("X-Forwarded-For")
 155	fake := donotfedafterdark[addr]
 156	if fake {
 157		log.Printf("faking 404 for %s", addr)
 158	}
 159	return fake
 160}
 161
 162func homepage(w http.ResponseWriter, r *http.Request) {
 163	templinfo := getInfo(r)
 164	u := login.GetUserInfo(r)
 165	var honks []*Honk
 166	var userid int64 = -1
 167	if r.URL.Path == "/front" || u == nil {
 168		honks = getpublichonks()
 169	} else {
 170		userid = u.UserID
 171		if r.URL.Path == "/atme" {
 172			honks = gethonksforme(userid)
 173		} else {
 174			honks = gethonksforuser(userid)
 175			honks = osmosis(honks, userid)
 176		}
 177		if len(honks) > 0 {
 178			templinfo["TopXID"] = honks[0].XID
 179		}
 180		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 181	}
 182
 183	tname := "honkpage.html"
 184	if topxid := r.FormValue("topxid"); topxid != "" {
 185		for i, h := range honks {
 186			if h.XID == topxid {
 187				honks = honks[0:i]
 188				break
 189			}
 190		}
 191		log.Printf("topxid %d frags", len(honks))
 192		tname = "honkfrags.html"
 193	}
 194
 195	reverbolate(userid, honks)
 196
 197	templinfo["Honks"] = honks
 198	templinfo["ShowRSS"] = true
 199	templinfo["ServerMessage"] = serverMsg
 200	if u == nil {
 201		w.Header().Set("Cache-Control", "max-age=60")
 202	} else {
 203		w.Header().Set("Cache-Control", "max-age=0")
 204	}
 205	w.Header().Set("Content-Type", "text/html; charset=utf-8")
 206
 207	err := readviews.Execute(w, tname, templinfo)
 208	if err != nil {
 209		log.Print(err)
 210	}
 211}
 212
 213func showfunzone(w http.ResponseWriter, r *http.Request) {
 214	var emunames, memenames []string
 215	dir, err := os.Open("emus")
 216	if err == nil {
 217		emunames, _ = dir.Readdirnames(0)
 218		dir.Close()
 219	}
 220	for i, e := range emunames {
 221		if len(e) > 4 {
 222			emunames[i] = e[:len(e)-4]
 223		}
 224	}
 225	dir, err = os.Open("memes")
 226	if err == nil {
 227		memenames, _ = dir.Readdirnames(0)
 228		dir.Close()
 229	}
 230	templinfo := getInfo(r)
 231	templinfo["Emus"] = emunames
 232	templinfo["Memes"] = memenames
 233	err = readviews.Execute(w, "funzone.html", templinfo)
 234	if err != nil {
 235		log.Print(err)
 236	}
 237
 238}
 239
 240func showrss(w http.ResponseWriter, r *http.Request) {
 241	name := mux.Vars(r)["name"]
 242
 243	var honks []*Honk
 244	if name != "" {
 245		honks = gethonksbyuser(name, false)
 246	} else {
 247		honks = getpublichonks()
 248	}
 249	reverbolate(-1, honks)
 250
 251	home := fmt.Sprintf("https://%s/", serverName)
 252	base := home
 253	if name != "" {
 254		home += "u/" + name
 255		name += " "
 256	}
 257	feed := rss.Feed{
 258		Title:       name + "honk",
 259		Link:        home,
 260		Description: name + "honk rss",
 261		Image: &rss.Image{
 262			URL:   base + "icon.png",
 263			Title: name + "honk rss",
 264			Link:  home,
 265		},
 266	}
 267	var modtime time.Time
 268	for _, honk := range honks {
 269		if !firstclass(honk) {
 270			continue
 271		}
 272		desc := string(honk.HTML)
 273		for _, d := range honk.Donks {
 274			desc += fmt.Sprintf(`<p><a href="%s">Attachment: %s</a>`,
 275				d.URL, html.EscapeString(d.Name))
 276		}
 277
 278		feed.Items = append(feed.Items, &rss.Item{
 279			Title:       fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
 280			Description: rss.CData{desc},
 281			Link:        honk.URL,
 282			PubDate:     honk.Date.Format(time.RFC1123),
 283			Guid:        &rss.Guid{IsPermaLink: true, Value: honk.URL},
 284		})
 285		if honk.Date.After(modtime) {
 286			modtime = honk.Date
 287		}
 288	}
 289	w.Header().Set("Cache-Control", "max-age=300")
 290	w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
 291
 292	err := feed.Write(w)
 293	if err != nil {
 294		log.Printf("error writing rss: %s", err)
 295	}
 296}
 297
 298func butwhatabout(name string) (*WhatAbout, error) {
 299	row := stmtWhatAbout.QueryRow(name)
 300	var user WhatAbout
 301	var options string
 302	err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key, &options)
 303	user.URL = fmt.Sprintf("https://%s/%s/%s", serverName, userSep, user.Name)
 304	user.SkinnyCSS = strings.Contains(options, " skinny ")
 305	return &user, err
 306}
 307
 308func crappola(j junk.Junk) bool {
 309	t, _ := j.GetString("type")
 310	a, _ := j.GetString("actor")
 311	o, _ := j.GetString("object")
 312	if t == "Delete" && a == o {
 313		log.Printf("crappola from %s", a)
 314		return true
 315	}
 316	return false
 317}
 318
 319func ping(user *WhatAbout, who string) {
 320	box, err := getboxes(who)
 321	if err != nil {
 322		log.Printf("no inbox for ping: %s", err)
 323		return
 324	}
 325	j := junk.New()
 326	j["@context"] = itiswhatitis
 327	j["type"] = "Ping"
 328	j["id"] = user.URL + "/ping/" + xfiltrate()
 329	j["actor"] = user.URL
 330	j["to"] = who
 331	keyname, key := ziggy(user.Name)
 332	err = PostJunk(keyname, key, box.In, j)
 333	if err != nil {
 334		log.Printf("can't send ping: %s", err)
 335		return
 336	}
 337	log.Printf("sent ping to %s: %s", who, j["id"])
 338}
 339
 340func pong(user *WhatAbout, who string, obj string) {
 341	box, err := getboxes(who)
 342	if err != nil {
 343		log.Printf("no inbox for pong %s : %s", who, err)
 344		return
 345	}
 346	j := junk.New()
 347	j["@context"] = itiswhatitis
 348	j["type"] = "Pong"
 349	j["id"] = user.URL + "/pong/" + xfiltrate()
 350	j["actor"] = user.URL
 351	j["to"] = who
 352	j["object"] = obj
 353	keyname, key := ziggy(user.Name)
 354	err = PostJunk(keyname, key, box.In, j)
 355	if err != nil {
 356		log.Printf("can't send pong: %s", err)
 357		return
 358	}
 359}
 360
 361func inbox(w http.ResponseWriter, r *http.Request) {
 362	name := mux.Vars(r)["name"]
 363	user, err := butwhatabout(name)
 364	if err != nil {
 365		http.NotFound(w, r)
 366		return
 367	}
 368	var buf bytes.Buffer
 369	io.Copy(&buf, r.Body)
 370	payload := buf.Bytes()
 371	j, err := junk.Read(bytes.NewReader(payload))
 372	if err != nil {
 373		log.Printf("bad payload: %s", err)
 374		io.WriteString(os.Stdout, "bad payload\n")
 375		os.Stdout.Write(payload)
 376		io.WriteString(os.Stdout, "\n")
 377		return
 378	}
 379	if crappola(j) {
 380		return
 381	}
 382	keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
 383	if err != nil {
 384		log.Printf("inbox message failed signature: %s", err)
 385		if keyname != "" {
 386			keyname, err = makeitworksomehowwithoutregardforkeycontinuity(keyname, r, payload)
 387			if err != nil {
 388				log.Printf("still failed: %s", err)
 389			}
 390		}
 391		if err != nil {
 392			return
 393		}
 394	}
 395	what, _ := j.GetString("type")
 396	if what == "Like" {
 397		return
 398	}
 399	who, _ := j.GetString("actor")
 400	origin := keymatch(keyname, who)
 401	if origin == "" {
 402		log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
 403		return
 404	}
 405	objid, _ := j.GetString("id")
 406	if thoudostbitethythumb(user.ID, []string{who}, objid) {
 407		log.Printf("ignoring thumb sucker %s", who)
 408		return
 409	}
 410	switch what {
 411	case "Ping":
 412		obj, _ := j.GetString("id")
 413		log.Printf("ping from %s: %s", who, obj)
 414		pong(user, who, obj)
 415	case "Pong":
 416		obj, _ := j.GetString("object")
 417		log.Printf("pong from %s: %s", who, obj)
 418	case "Follow":
 419		obj, _ := j.GetString("object")
 420		if obj == user.URL {
 421			log.Printf("updating honker follow: %s", who)
 422			stmtSaveDub.Exec(user.ID, who, who, "dub")
 423			go rubadubdub(user, j)
 424		} else {
 425			log.Printf("can't follow %s", obj)
 426		}
 427	case "Accept":
 428		log.Printf("updating honker accept: %s", who)
 429		_, err = stmtUpdateFlavor.Exec("sub", user.ID, who, "presub")
 430		if err != nil {
 431			log.Printf("error updating honker: %s", err)
 432			return
 433		}
 434	case "Update":
 435		obj, ok := j.GetMap("object")
 436		if ok {
 437			what, _ := obj.GetString("type")
 438			switch what {
 439			case "Person":
 440				return
 441			case "Question":
 442				return
 443			}
 444		}
 445		log.Printf("unknown Update activity")
 446		fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 447		j.Write(fd)
 448		io.WriteString(fd, "\n")
 449		fd.Close()
 450
 451	case "Undo":
 452		obj, ok := j.GetMap("object")
 453		if !ok {
 454			log.Printf("unknown undo no object")
 455		} else {
 456			what, _ := obj.GetString("type")
 457			switch what {
 458			case "Follow":
 459				log.Printf("updating honker undo: %s", who)
 460				_, err = stmtUpdateFlavor.Exec("undub", user.ID, who, "dub")
 461				if err != nil {
 462					log.Printf("error updating honker: %s", err)
 463					return
 464				}
 465			case "Announce":
 466				xid, _ := obj.GetString("object")
 467				log.Printf("undo announce: %s", xid)
 468			case "Like":
 469			default:
 470				log.Printf("unknown undo: %s", what)
 471			}
 472		}
 473	default:
 474		go consumeactivity(user, j, origin)
 475	}
 476}
 477
 478func ximport(w http.ResponseWriter, r *http.Request) {
 479	xid := r.FormValue("xid")
 480	p, _ := investigate(xid)
 481	if p != nil {
 482		xid = p.XID
 483	}
 484	j, err := GetJunk(xid)
 485	if err != nil {
 486		http.Error(w, "error getting external object", http.StatusInternalServerError)
 487		log.Printf("error getting external object: %s", err)
 488		return
 489	}
 490	log.Printf("importing %s", xid)
 491	u := login.GetUserInfo(r)
 492	user, _ := butwhatabout(u.Username)
 493
 494	what, _ := j.GetString("type")
 495	if isactor(what) {
 496		outbox, _ := j.GetString("outbox")
 497		gimmexonks(user, outbox)
 498		http.Redirect(w, r, "/h?xid="+url.QueryEscape(xid), http.StatusSeeOther)
 499		return
 500	}
 501	xonk := xonkxonk(user, j, originate(xid))
 502	convoy := ""
 503	if xonk != nil {
 504		convoy = xonk.Convoy
 505		savexonk(user, xonk)
 506	}
 507	http.Redirect(w, r, "/t?c="+url.QueryEscape(convoy), http.StatusSeeOther)
 508}
 509
 510func xzone(w http.ResponseWriter, r *http.Request) {
 511	u := login.GetUserInfo(r)
 512	rows, err := stmtRecentHonkers.Query(u.UserID, u.UserID)
 513	if err != nil {
 514		log.Printf("query err: %s", err)
 515		return
 516	}
 517	defer rows.Close()
 518	var honkers []Honker
 519	for rows.Next() {
 520		var xid string
 521		rows.Scan(&xid)
 522		honkers = append(honkers, Honker{XID: xid})
 523	}
 524	rows.Close()
 525	for i, _ := range honkers {
 526		_, honkers[i].Handle = handles(honkers[i].XID)
 527	}
 528	templinfo := getInfo(r)
 529	templinfo["XCSRF"] = login.GetCSRF("ximport", r)
 530	templinfo["Honkers"] = honkers
 531	err = readviews.Execute(w, "xzone.html", templinfo)
 532	if err != nil {
 533		log.Print(err)
 534	}
 535}
 536
 537func outbox(w http.ResponseWriter, r *http.Request) {
 538	name := mux.Vars(r)["name"]
 539	user, err := butwhatabout(name)
 540	if err != nil {
 541		http.NotFound(w, r)
 542		return
 543	}
 544	if stealthed(r) {
 545		http.NotFound(w, r)
 546		return
 547	}
 548
 549	honks := gethonksbyuser(name, false)
 550
 551	var jonks []junk.Junk
 552	for _, h := range honks {
 553		j, _ := jonkjonk(user, h)
 554		jonks = append(jonks, j)
 555	}
 556
 557	j := junk.New()
 558	j["@context"] = itiswhatitis
 559	j["id"] = user.URL + "/outbox"
 560	j["type"] = "OrderedCollection"
 561	j["totalItems"] = len(jonks)
 562	j["orderedItems"] = jonks
 563
 564	w.Header().Set("Content-Type", theonetruename)
 565	j.Write(w)
 566}
 567
 568func emptiness(w http.ResponseWriter, r *http.Request) {
 569	name := mux.Vars(r)["name"]
 570	user, err := butwhatabout(name)
 571	if err != nil {
 572		http.NotFound(w, r)
 573		return
 574	}
 575	colname := "/followers"
 576	if strings.HasSuffix(r.URL.Path, "/following") {
 577		colname = "/following"
 578	}
 579	j := junk.New()
 580	j["@context"] = itiswhatitis
 581	j["id"] = user.URL + colname
 582	j["type"] = "OrderedCollection"
 583	j["totalItems"] = 0
 584	j["orderedItems"] = []junk.Junk{}
 585
 586	w.Header().Set("Content-Type", theonetruename)
 587	j.Write(w)
 588}
 589
 590func showuser(w http.ResponseWriter, r *http.Request) {
 591	name := mux.Vars(r)["name"]
 592	user, err := butwhatabout(name)
 593	if err != nil {
 594		log.Printf("user not found %s: %s", name, err)
 595		http.NotFound(w, r)
 596		return
 597	}
 598	if friendorfoe(r.Header.Get("Accept")) {
 599		j := asjonker(user)
 600		w.Header().Set("Content-Type", theonetruename)
 601		j.Write(w)
 602		return
 603	}
 604	u := login.GetUserInfo(r)
 605	honks := gethonksbyuser(name, u != nil && u.Username == name)
 606	honkpage(w, r, u, user, honks, "")
 607}
 608
 609func showhonker(w http.ResponseWriter, r *http.Request) {
 610	u := login.GetUserInfo(r)
 611	name := mux.Vars(r)["name"]
 612	var honks []*Honk
 613	if name == "" {
 614		name = r.FormValue("xid")
 615		honks = gethonksbyxonker(u.UserID, name)
 616	} else {
 617		honks = gethonksbyhonker(u.UserID, name)
 618	}
 619	name = html.EscapeString(name)
 620	msg := fmt.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, name, name)
 621	honkpage(w, r, u, nil, honks, template.HTML(msg))
 622}
 623
 624func showcombo(w http.ResponseWriter, r *http.Request) {
 625	name := mux.Vars(r)["name"]
 626	u := login.GetUserInfo(r)
 627	honks := gethonksbycombo(u.UserID, name)
 628	honks = osmosis(honks, u.UserID)
 629	honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks by combo: "+name)))
 630}
 631func showconvoy(w http.ResponseWriter, r *http.Request) {
 632	c := r.FormValue("c")
 633	u := login.GetUserInfo(r)
 634	honks := gethonksbyconvoy(u.UserID, c)
 635	honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks in convoy: "+c)))
 636}
 637func showsearch(w http.ResponseWriter, r *http.Request) {
 638	q := r.FormValue("q")
 639	u := login.GetUserInfo(r)
 640	honks := gethonksbysearch(u.UserID, q)
 641	honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks for search: "+q)))
 642}
 643func showontology(w http.ResponseWriter, r *http.Request) {
 644	name := mux.Vars(r)["name"]
 645	u := login.GetUserInfo(r)
 646	var userid int64 = -1
 647	if u != nil {
 648		userid = u.UserID
 649	}
 650	honks := gethonksbyontology(userid, "#"+name)
 651	honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks by ontology: "+name)))
 652}
 653
 654func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
 655	u := login.GetUserInfo(r)
 656	var userid int64 = -1
 657	if u != nil {
 658		userid = u.UserID
 659	}
 660	rows, err := stmtSelectOnts.Query(userid)
 661	if err != nil {
 662		log.Printf("selection error: %s", err)
 663		return
 664	}
 665	var onts [][]string
 666	for rows.Next() {
 667		var o string
 668		err := rows.Scan(&o)
 669		if err != nil {
 670			log.Printf("error scanning ont: %s", err)
 671			continue
 672		}
 673		onts = append(onts, []string{o, o[1:]})
 674	}
 675	if u == nil {
 676		w.Header().Set("Cache-Control", "max-age=300")
 677	}
 678	templinfo := getInfo(r)
 679	templinfo["Onts"] = onts
 680	err = readviews.Execute(w, "onts.html", templinfo)
 681	if err != nil {
 682		log.Print(err)
 683	}
 684}
 685
 686func showhonk(w http.ResponseWriter, r *http.Request) {
 687	name := mux.Vars(r)["name"]
 688	user, err := butwhatabout(name)
 689	if err != nil {
 690		http.NotFound(w, r)
 691		return
 692	}
 693	if stealthed(r) {
 694		http.NotFound(w, r)
 695		return
 696	}
 697
 698	xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
 699	honk := getxonk(user.ID, xid)
 700	if honk == nil {
 701		http.NotFound(w, r)
 702		return
 703	}
 704	u := login.GetUserInfo(r)
 705	if u != nil && u.UserID != user.ID {
 706		u = nil
 707	}
 708	if !honk.Public {
 709		if u == nil {
 710			http.NotFound(w, r)
 711			return
 712
 713		}
 714		honkpage(w, r, u, nil, []*Honk{honk}, "one honk maybe more")
 715		return
 716	}
 717	rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy)
 718	if friendorfoe(r.Header.Get("Accept")) {
 719		for _, h := range rawhonks {
 720			if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
 721				honk.Replies = append(honk.Replies, h)
 722			}
 723		}
 724		donksforhonks([]*Honk{honk})
 725		_, j := jonkjonk(user, honk)
 726		j["@context"] = itiswhatitis
 727		w.Header().Set("Content-Type", theonetruename)
 728		j.Write(w)
 729		return
 730	}
 731	var honks []*Honk
 732	for _, h := range rawhonks {
 733		if h.Public && (h.Whofore == 2 || h.IsAcked()) {
 734			honks = append(honks, h)
 735		}
 736	}
 737
 738	honkpage(w, r, u, nil, honks, "one honk maybe more")
 739}
 740
 741func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
 742	honks []*Honk, infomsg template.HTML) {
 743	templinfo := getInfo(r)
 744	var userid int64 = -1
 745	if u != nil {
 746		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 747		userid = u.UserID
 748	}
 749	if u == nil {
 750		w.Header().Set("Cache-Control", "max-age=60")
 751	}
 752	reverbolate(userid, honks)
 753	if user != nil {
 754		filt := htfilter.New()
 755		templinfo["Name"] = user.Name
 756		whatabout := user.About
 757		whatabout = obfusbreak(user.About)
 758		templinfo["WhatAbout"], _ = filt.String(whatabout)
 759	}
 760	templinfo["Honks"] = honks
 761	templinfo["ServerMessage"] = infomsg
 762	err := readviews.Execute(w, "honkpage.html", templinfo)
 763	if err != nil {
 764		log.Print(err)
 765	}
 766}
 767
 768func saveuser(w http.ResponseWriter, r *http.Request) {
 769	whatabout := r.FormValue("whatabout")
 770	u := login.GetUserInfo(r)
 771	db := opendatabase()
 772	options := ""
 773	if r.FormValue("skinny") == "skinny" {
 774		options += " skinny "
 775	}
 776	_, err := db.Exec("update users set about = ?, options = ? where username = ?", whatabout, options, u.Username)
 777	if err != nil {
 778		log.Printf("error bouting what: %s", err)
 779	}
 780
 781	http.Redirect(w, r, "/account", http.StatusSeeOther)
 782}
 783
 784func gethonkers(userid int64) []*Honker {
 785	rows, err := stmtHonkers.Query(userid)
 786	if err != nil {
 787		log.Printf("error querying honkers: %s", err)
 788		return nil
 789	}
 790	defer rows.Close()
 791	var honkers []*Honker
 792	for rows.Next() {
 793		var f Honker
 794		var combos string
 795		err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
 796		f.Combos = strings.Split(strings.TrimSpace(combos), " ")
 797		if err != nil {
 798			log.Printf("error scanning honker: %s", err)
 799			return nil
 800		}
 801		honkers = append(honkers, &f)
 802	}
 803	return honkers
 804}
 805
 806func getdubs(userid int64) []*Honker {
 807	rows, err := stmtDubbers.Query(userid)
 808	if err != nil {
 809		log.Printf("error querying dubs: %s", err)
 810		return nil
 811	}
 812	defer rows.Close()
 813	var honkers []*Honker
 814	for rows.Next() {
 815		var f Honker
 816		err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
 817		if err != nil {
 818			log.Printf("error scanning honker: %s", err)
 819			return nil
 820		}
 821		honkers = append(honkers, &f)
 822	}
 823	return honkers
 824}
 825
 826func allusers() []login.UserInfo {
 827	var users []login.UserInfo
 828	rows, _ := opendatabase().Query("select userid, username from users")
 829	defer rows.Close()
 830	for rows.Next() {
 831		var u login.UserInfo
 832		rows.Scan(&u.UserID, &u.Username)
 833		users = append(users, u)
 834	}
 835	return users
 836}
 837
 838func getxonk(userid int64, xid string) *Honk {
 839	h := new(Honk)
 840	var dt, aud, onts string
 841	row := stmtOneXonk.QueryRow(userid, xid)
 842	err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
 843		&dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
 844	if err != nil {
 845		if err != sql.ErrNoRows {
 846			log.Printf("error scanning xonk: %s", err)
 847		}
 848		return nil
 849	}
 850	h.Date, _ = time.Parse(dbtimeformat, dt)
 851	h.Audience = strings.Split(aud, " ")
 852	h.Public = !keepitquiet(h.Audience)
 853	if len(onts) > 0 {
 854		h.Onts = strings.Split(onts, " ")
 855	}
 856	return h
 857}
 858
 859func getbonk(userid int64, xid string) *Honk {
 860	h := new(Honk)
 861	var dt, aud, onts string
 862	row := stmtOneBonk.QueryRow(userid, xid)
 863	err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
 864		&dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
 865	if err != nil {
 866		if err != sql.ErrNoRows {
 867			log.Printf("error scanning xonk: %s", err)
 868		}
 869		return nil
 870	}
 871	h.Date, _ = time.Parse(dbtimeformat, dt)
 872	h.Audience = strings.Split(aud, " ")
 873	h.Public = !keepitquiet(h.Audience)
 874	if len(onts) > 0 {
 875		h.Onts = strings.Split(onts, " ")
 876	}
 877	return h
 878}
 879
 880func getpublichonks() []*Honk {
 881	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
 882	rows, err := stmtPublicHonks.Query(dt)
 883	return getsomehonks(rows, err)
 884}
 885func gethonksbyuser(name string, includeprivate bool) []*Honk {
 886	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
 887	whofore := 2
 888	if includeprivate {
 889		whofore = 3
 890	}
 891	rows, err := stmtUserHonks.Query(whofore, name, dt)
 892	return getsomehonks(rows, err)
 893}
 894func gethonksforuser(userid int64) []*Honk {
 895	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
 896	rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
 897	return getsomehonks(rows, err)
 898}
 899func gethonksforme(userid int64) []*Honk {
 900	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
 901	rows, err := stmtHonksForMe.Query(userid, dt, userid)
 902	return getsomehonks(rows, err)
 903}
 904func gethonksbyhonker(userid int64, honker string) []*Honk {
 905	rows, err := stmtHonksByHonker.Query(userid, honker, userid)
 906	return getsomehonks(rows, err)
 907}
 908func gethonksbyxonker(userid int64, xonker string) []*Honk {
 909	rows, err := stmtHonksByXonker.Query(userid, xonker, xonker, userid)
 910	return getsomehonks(rows, err)
 911}
 912func gethonksbycombo(userid int64, combo string) []*Honk {
 913	combo = "% " + combo + " %"
 914	rows, err := stmtHonksByCombo.Query(userid, combo, userid)
 915	return getsomehonks(rows, err)
 916}
 917func gethonksbyconvoy(userid int64, convoy string) []*Honk {
 918	rows, err := stmtHonksByConvoy.Query(userid, userid, convoy)
 919	honks := getsomehonks(rows, err)
 920	for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
 921		honks[i], honks[j] = honks[j], honks[i]
 922	}
 923	return honks
 924}
 925func gethonksbysearch(userid int64, q string) []*Honk {
 926	q = "%" + q + "%"
 927	rows, err := stmtHonksBySearch.Query(userid, q)
 928	honks := getsomehonks(rows, err)
 929	return honks
 930}
 931func gethonksbyontology(userid int64, name string) []*Honk {
 932	rows, err := stmtHonksByOntology.Query(name, userid, userid)
 933	honks := getsomehonks(rows, err)
 934	return honks
 935}
 936
 937func getsomehonks(rows *sql.Rows, err error) []*Honk {
 938	if err != nil {
 939		log.Printf("error querying honks: %s", err)
 940		return nil
 941	}
 942	defer rows.Close()
 943	var honks []*Honk
 944	for rows.Next() {
 945		var h Honk
 946		var dt, aud, onts string
 947		err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID,
 948			&h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
 949		if err != nil {
 950			log.Printf("error scanning honks: %s", err)
 951			return nil
 952		}
 953		h.Date, _ = time.Parse(dbtimeformat, dt)
 954		h.Audience = strings.Split(aud, " ")
 955		h.Public = !keepitquiet(h.Audience)
 956		if len(onts) > 0 {
 957			h.Onts = strings.Split(onts, " ")
 958		}
 959		honks = append(honks, &h)
 960	}
 961	rows.Close()
 962	donksforhonks(honks)
 963	return honks
 964}
 965
 966func donksforhonks(honks []*Honk) {
 967	db := opendatabase()
 968	var ids []string
 969	hmap := make(map[int64]*Honk)
 970	for _, h := range honks {
 971		ids = append(ids, fmt.Sprintf("%d", h.ID))
 972		hmap[h.ID] = h
 973	}
 974	q := fmt.Sprintf("select honkid, donks.fileid, xid, name, url, media, local from donks join files on donks.fileid = files.fileid where honkid in (%s)", strings.Join(ids, ","))
 975	rows, err := db.Query(q)
 976	if err != nil {
 977		log.Printf("error querying donks: %s", err)
 978		return
 979	}
 980	defer rows.Close()
 981	for rows.Next() {
 982		var hid int64
 983		var d Donk
 984		err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media, &d.Local)
 985		if err != nil {
 986			log.Printf("error scanning donk: %s", err)
 987			continue
 988		}
 989		h := hmap[hid]
 990		h.Donks = append(h.Donks, &d)
 991	}
 992}
 993
 994func savebonk(w http.ResponseWriter, r *http.Request) {
 995	xid := r.FormValue("xid")
 996	userinfo := login.GetUserInfo(r)
 997	user, _ := butwhatabout(userinfo.Username)
 998
 999	log.Printf("bonking %s", xid)
1000
1001	xonk := getxonk(userinfo.UserID, xid)
1002	if xonk == nil {
1003		return
1004	}
1005	if !xonk.Public {
1006		return
1007	}
1008	donksforhonks([]*Honk{xonk})
1009
1010	_, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
1011	if err != nil {
1012		log.Printf("error acking bonk: %s", err)
1013	}
1014
1015	oonker := xonk.Oonker
1016	if oonker == "" {
1017		oonker = xonk.Honker
1018	}
1019	dt := time.Now().UTC()
1020	bonk := Honk{
1021		UserID:   userinfo.UserID,
1022		Username: userinfo.Username,
1023		What:     "bonk",
1024		Honker:   user.URL,
1025		XID:      xonk.XID,
1026		Date:     dt,
1027		Donks:    xonk.Donks,
1028		Convoy:   xonk.Convoy,
1029		Audience: []string{thewholeworld, oonker},
1030		Public:   true,
1031	}
1032
1033	aud := strings.Join(bonk.Audience, " ")
1034	whofore := 2
1035	onts := xonk.Onts
1036	res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
1037		dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
1038		xonk.Precis, oonker, 0, strings.Join(onts, " "))
1039	if err != nil {
1040		log.Printf("error saving bonk: %s", err)
1041		return
1042	}
1043	bonk.ID, _ = res.LastInsertId()
1044	for _, d := range bonk.Donks {
1045		_, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
1046		if err != nil {
1047			log.Printf("err saving donk: %s", err)
1048			return
1049		}
1050	}
1051	for _, o := range onts {
1052		_, err = stmtSaveOnts.Exec(strings.ToLower(o), bonk.ID)
1053		if err != nil {
1054			log.Printf("error saving ont: %s", err)
1055		}
1056	}
1057
1058	go honkworldwide(user, &bonk)
1059}
1060
1061func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
1062	zonk := Honk{
1063		What:     what,
1064		XID:      xonk.XID,
1065		Date:     time.Now().UTC(),
1066		Audience: oneofakind(xonk.Audience),
1067	}
1068	zonk.Public = !keepitquiet(zonk.Audience)
1069
1070	log.Printf("announcing %sed honk: %s", what, xonk.XID)
1071	go honkworldwide(user, &zonk)
1072}
1073
1074func zonkit(w http.ResponseWriter, r *http.Request) {
1075	wherefore := r.FormValue("wherefore")
1076	what := r.FormValue("what")
1077	userinfo := login.GetUserInfo(r)
1078	user, _ := butwhatabout(userinfo.Username)
1079
1080	if wherefore == "ack" {
1081		xonk := getxonk(userinfo.UserID, what)
1082		if xonk != nil {
1083			_, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1084			if err != nil {
1085				log.Printf("error acking: %s", err)
1086			}
1087			sendzonkofsorts(xonk, user, "ack")
1088		}
1089		return
1090	}
1091
1092	if wherefore == "deack" {
1093		xonk := getxonk(userinfo.UserID, what)
1094		if xonk != nil {
1095			_, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1096			if err != nil {
1097				log.Printf("error deacking: %s", err)
1098			}
1099			sendzonkofsorts(xonk, user, "deack")
1100		}
1101		return
1102	}
1103
1104	if wherefore == "unbonk" {
1105		xonk := getbonk(userinfo.UserID, what)
1106		if xonk != nil {
1107			_, err := stmtZonkDonks.Exec(xonk.ID)
1108			if err != nil {
1109				log.Printf("error zonking: %s", err)
1110			}
1111			_, err = stmtZonkIt.Exec(xonk.ID)
1112			if err != nil {
1113				log.Printf("error zonking: %s", err)
1114			}
1115			xonk = getxonk(userinfo.UserID, what)
1116			_, err = stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1117			if err != nil {
1118				log.Printf("error unbonking: %s", err)
1119			}
1120			sendzonkofsorts(xonk, user, "unbonk")
1121		}
1122		return
1123	}
1124
1125	log.Printf("zonking %s %s", wherefore, what)
1126	if wherefore == "zonk" {
1127		xonk := getxonk(userinfo.UserID, what)
1128		if xonk != nil {
1129			_, err := stmtZonkDonks.Exec(xonk.ID)
1130			if err != nil {
1131				log.Printf("error zonking: %s", err)
1132			}
1133			_, err = stmtZonkIt.Exec(xonk.ID)
1134			if err != nil {
1135				log.Printf("error zonking: %s", err)
1136			}
1137			if xonk.Whofore == 2 || xonk.Whofore == 3 {
1138				sendzonkofsorts(xonk, user, "zonk")
1139			}
1140		}
1141	}
1142	_, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1143	if err != nil {
1144		log.Printf("error saving zonker: %s", err)
1145		return
1146	}
1147}
1148
1149func savehonk(w http.ResponseWriter, r *http.Request) {
1150	rid := r.FormValue("rid")
1151	noise := r.FormValue("noise")
1152
1153	userinfo := login.GetUserInfo(r)
1154	user, _ := butwhatabout(userinfo.Username)
1155
1156	dt := time.Now().UTC()
1157	xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1158	what := "honk"
1159	if rid != "" {
1160		what = "tonk"
1161	}
1162	honk := Honk{
1163		UserID:   userinfo.UserID,
1164		Username: userinfo.Username,
1165		What:     "honk",
1166		Honker:   user.URL,
1167		XID:      xid,
1168		Date:     dt,
1169	}
1170	if strings.HasPrefix(noise, "DZ:") {
1171		idx := strings.Index(noise, "\n")
1172		if idx == -1 {
1173			honk.Precis = noise
1174			noise = ""
1175		} else {
1176			honk.Precis = noise[:idx]
1177			noise = noise[idx+1:]
1178		}
1179	}
1180	noise = hooterize(noise)
1181	noise = strings.TrimSpace(noise)
1182	honk.Precis = strings.TrimSpace(honk.Precis)
1183
1184	var convoy string
1185	if rid != "" {
1186		xonk := getxonk(userinfo.UserID, rid)
1187		if xonk != nil {
1188			if xonk.Public {
1189				honk.Audience = append(honk.Audience, xonk.Audience...)
1190			}
1191			convoy = xonk.Convoy
1192		} else {
1193			xonkaud, c := whosthere(rid)
1194			honk.Audience = append(honk.Audience, xonkaud...)
1195			convoy = c
1196		}
1197		for i, a := range honk.Audience {
1198			if a == thewholeworld {
1199				honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1200				break
1201			}
1202		}
1203		honk.RID = rid
1204	} else {
1205		honk.Audience = []string{thewholeworld}
1206	}
1207	if noise != "" && noise[0] == '@' {
1208		honk.Audience = append(grapevine(noise), honk.Audience...)
1209	} else {
1210		honk.Audience = append(honk.Audience, grapevine(noise)...)
1211	}
1212	if convoy == "" {
1213		convoy = "data:,electrichonkytonk-" + xfiltrate()
1214	}
1215	butnottooloud(honk.Audience)
1216	honk.Audience = oneofakind(honk.Audience)
1217	if len(honk.Audience) == 0 {
1218		log.Printf("honk to nowhere")
1219		http.Error(w, "honk to nowhere...", http.StatusNotFound)
1220		return
1221	}
1222	honk.Public = !keepitquiet(honk.Audience)
1223	noise = obfusbreak(noise)
1224	honk.Noise = noise
1225	honk.Convoy = convoy
1226
1227	donkxid := r.FormValue("donkxid")
1228	if donkxid == "" {
1229		file, filehdr, err := r.FormFile("donk")
1230		if err == nil {
1231			var buf bytes.Buffer
1232			io.Copy(&buf, file)
1233			file.Close()
1234			data := buf.Bytes()
1235			xid := xfiltrate()
1236			var media, name string
1237			img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
1238			if err == nil {
1239				data = img.Data
1240				format := img.Format
1241				media = "image/" + format
1242				if format == "jpeg" {
1243					format = "jpg"
1244				}
1245				name = xid + "." + format
1246				xid = name
1247			} else {
1248				maxsize := 100000
1249				if len(data) > maxsize {
1250					log.Printf("bad image: %s too much text: %d", err, len(data))
1251					http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1252					return
1253				}
1254				for i := 0; i < len(data); i++ {
1255					if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1256						log.Printf("bad image: %s not text: %d", err, data[i])
1257						http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1258						return
1259					}
1260				}
1261				media = "text/plain"
1262				name = filehdr.Filename
1263				if name == "" {
1264					name = xid + ".txt"
1265				}
1266				xid += ".txt"
1267			}
1268			url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1269			res, err := stmtSaveFile.Exec(xid, name, url, media, 1, data)
1270			if err != nil {
1271				log.Printf("unable to save image: %s", err)
1272				return
1273			}
1274			var d Donk
1275			d.FileID, _ = res.LastInsertId()
1276			d.XID = name
1277			d.Name = name
1278			d.Media = media
1279			d.URL = url
1280			d.Local = true
1281			honk.Donks = append(honk.Donks, &d)
1282			donkxid = d.XID
1283		}
1284	} else {
1285		xid := donkxid
1286		url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1287		var donk Donk
1288		row := stmtFindFile.QueryRow(url)
1289		err := row.Scan(&donk.FileID)
1290		if err == nil {
1291			donk.XID = xid
1292			donk.Local = true
1293			donk.URL = url
1294			honk.Donks = append(honk.Donks, &donk)
1295		} else {
1296			log.Printf("can't find file: %s", xid)
1297		}
1298	}
1299	herd := herdofemus(honk.Noise)
1300	for _, e := range herd {
1301		donk := savedonk(e.ID, e.Name, "image/png", true)
1302		if donk != nil {
1303			donk.Name = e.Name
1304			honk.Donks = append(honk.Donks, donk)
1305		}
1306	}
1307	memetize(&honk)
1308
1309	aud := strings.Join(honk.Audience, " ")
1310	whofore := 2
1311	if !honk.Public {
1312		whofore = 3
1313	}
1314	if r.FormValue("preview") == "preview" {
1315		honks := []*Honk{&honk}
1316		reverbolate(userinfo.UserID, honks)
1317		templinfo := getInfo(r)
1318		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1319		templinfo["Honks"] = honks
1320		templinfo["InReplyTo"] = r.FormValue("rid")
1321		templinfo["Noise"] = r.FormValue("noise")
1322		templinfo["SavedFile"] = donkxid
1323		templinfo["ServerMessage"] = "honk preview"
1324		err := readviews.Execute(w, "honkpage.html", templinfo)
1325		if err != nil {
1326			log.Print(err)
1327		}
1328		return
1329	}
1330	honk.Onts = oneofakind(ontologies(honk.Noise))
1331	res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
1332		dt.Format(dbtimeformat), "", aud, honk.Noise, convoy, whofore, "html",
1333		honk.Precis, honk.Oonker, 0, strings.Join(honk.Onts, " "))
1334	if err != nil {
1335		log.Printf("error saving honk: %s", err)
1336		http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1337		return
1338	}
1339	honk.ID, _ = res.LastInsertId()
1340	for _, d := range honk.Donks {
1341		_, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
1342		if err != nil {
1343			log.Printf("err saving donk: %s", err)
1344			http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1345			return
1346		}
1347	}
1348	for _, o := range honk.Onts {
1349		_, err = stmtSaveOnts.Exec(strings.ToLower(o), honk.ID)
1350		if err != nil {
1351			log.Printf("error saving ont: %s", err)
1352		}
1353	}
1354
1355	go honkworldwide(user, &honk)
1356
1357	http.Redirect(w, r, xid, http.StatusSeeOther)
1358}
1359
1360func showhonkers(w http.ResponseWriter, r *http.Request) {
1361	userinfo := login.GetUserInfo(r)
1362	templinfo := getInfo(r)
1363	templinfo["Honkers"] = gethonkers(userinfo.UserID)
1364	templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1365	err := readviews.Execute(w, "honkers.html", templinfo)
1366	if err != nil {
1367		log.Print(err)
1368	}
1369}
1370
1371func showcombos(w http.ResponseWriter, r *http.Request) {
1372	userinfo := login.GetUserInfo(r)
1373	templinfo := getInfo(r)
1374	honkers := gethonkers(userinfo.UserID)
1375	var combos []string
1376	for _, h := range honkers {
1377		combos = append(combos, h.Combos...)
1378	}
1379	for i, c := range combos {
1380		if c == "-" {
1381			combos[i] = ""
1382		}
1383	}
1384	combos = oneofakind(combos)
1385	sort.Strings(combos)
1386	templinfo["Combos"] = combos
1387	err := readviews.Execute(w, "combos.html", templinfo)
1388	if err != nil {
1389		log.Print(err)
1390	}
1391}
1392
1393func savehonker(w http.ResponseWriter, r *http.Request) {
1394	u := login.GetUserInfo(r)
1395	name := r.FormValue("name")
1396	url := r.FormValue("url")
1397	peep := r.FormValue("peep")
1398	combos := r.FormValue("combos")
1399	honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1400
1401	if honkerid > 0 {
1402		goodbye := r.FormValue("goodbye")
1403		if goodbye == "F" {
1404			db := opendatabase()
1405			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1406				honkerid, u.UserID)
1407			var xid string
1408			err := row.Scan(&xid)
1409			if err != nil {
1410				log.Printf("can't get honker xid: %s", err)
1411				return
1412			}
1413			log.Printf("unsubscribing from %s", xid)
1414			user, _ := butwhatabout(u.Username)
1415			go itakeitallback(user, xid)
1416			_, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1417			if err != nil {
1418				log.Printf("error updating honker: %s", err)
1419				return
1420			}
1421
1422			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1423			return
1424		}
1425		combos = " " + strings.TrimSpace(combos) + " "
1426		_, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1427		if err != nil {
1428			log.Printf("update honker err: %s", err)
1429			return
1430		}
1431		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1432	}
1433
1434	flavor := "presub"
1435	if peep == "peep" {
1436		flavor = "peep"
1437	}
1438	p, err := investigate(url)
1439	if err != nil {
1440		http.Error(w, "error investigating: "+err.Error(), http.StatusInternalServerError)
1441		log.Printf("failed to investigate honker")
1442		return
1443	}
1444	url = p.XID
1445	if name == "" {
1446		name = p.Handle
1447	}
1448	_, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1449	if err != nil {
1450		log.Print(err)
1451		return
1452	}
1453	if flavor == "presub" {
1454		user, _ := butwhatabout(u.Username)
1455		go subsub(user, url)
1456	}
1457	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1458}
1459
1460type Zonker struct {
1461	ID        int64
1462	Name      string
1463	Wherefore string
1464}
1465
1466func zonkzone(w http.ResponseWriter, r *http.Request) {
1467	userinfo := login.GetUserInfo(r)
1468	rows, err := stmtGetZonkers.Query(userinfo.UserID)
1469	if err != nil {
1470		log.Printf("err: %s", err)
1471		return
1472	}
1473	defer rows.Close()
1474	var zonkers []Zonker
1475	for rows.Next() {
1476		var z Zonker
1477		rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1478		zonkers = append(zonkers, z)
1479	}
1480	sort.Slice(zonkers, func(i, j int) bool {
1481		w1 := zonkers[i].Wherefore
1482		w2 := zonkers[j].Wherefore
1483		if w1 == w2 {
1484			return zonkers[i].Name < zonkers[j].Name
1485		}
1486		if w1 == "zonvoy" {
1487			w1 = "zzzzzzz"
1488		}
1489		if w2 == "zonvoy" {
1490			w2 = "zzzzzzz"
1491		}
1492		return w1 < w2
1493	})
1494
1495	templinfo := getInfo(r)
1496	templinfo["Zonkers"] = zonkers
1497	templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1498	err = readviews.Execute(w, "zonkers.html", templinfo)
1499	if err != nil {
1500		log.Print(err)
1501	}
1502}
1503
1504func zonkzonk(w http.ResponseWriter, r *http.Request) {
1505	userinfo := login.GetUserInfo(r)
1506	itsok := r.FormValue("itsok")
1507	if itsok == "iforgiveyou" {
1508		zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1509		db := opendatabase()
1510		db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1511			userinfo.UserID, zonkerid)
1512		bitethethumbs()
1513		http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1514		return
1515	}
1516	wherefore := r.FormValue("wherefore")
1517	name := r.FormValue("name")
1518	if name == "" {
1519		return
1520	}
1521	switch wherefore {
1522	case "zonker":
1523	case "zomain":
1524	case "zonvoy":
1525	case "zord":
1526	case "zilence":
1527	default:
1528		return
1529	}
1530	db := opendatabase()
1531	db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1532		userinfo.UserID, name, wherefore)
1533	if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" || wherefore == "zilence" {
1534		bitethethumbs()
1535	}
1536
1537	http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1538}
1539
1540func accountpage(w http.ResponseWriter, r *http.Request) {
1541	u := login.GetUserInfo(r)
1542	user, _ := butwhatabout(u.Username)
1543	templinfo := getInfo(r)
1544	templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1545	templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1546	templinfo["User"] = user
1547	err := readviews.Execute(w, "account.html", templinfo)
1548	if err != nil {
1549		log.Print(err)
1550	}
1551}
1552
1553func dochpass(w http.ResponseWriter, r *http.Request) {
1554	err := login.ChangePassword(w, r)
1555	if err != nil {
1556		log.Printf("error changing password: %s", err)
1557	}
1558	http.Redirect(w, r, "/account", http.StatusSeeOther)
1559}
1560
1561func fingerlicker(w http.ResponseWriter, r *http.Request) {
1562	orig := r.FormValue("resource")
1563
1564	log.Printf("finger lick: %s", orig)
1565
1566	if strings.HasPrefix(orig, "acct:") {
1567		orig = orig[5:]
1568	}
1569
1570	name := orig
1571	idx := strings.LastIndexByte(name, '/')
1572	if idx != -1 {
1573		name = name[idx+1:]
1574		if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1575			log.Printf("foreign request rejected")
1576			name = ""
1577		}
1578	} else {
1579		idx = strings.IndexByte(name, '@')
1580		if idx != -1 {
1581			name = name[:idx]
1582			if name+"@"+serverName != orig {
1583				log.Printf("foreign request rejected")
1584				name = ""
1585			}
1586		}
1587	}
1588	user, err := butwhatabout(name)
1589	if err != nil {
1590		http.NotFound(w, r)
1591		return
1592	}
1593
1594	j := junk.New()
1595	j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1596	j["aliases"] = []string{user.URL}
1597	var links []junk.Junk
1598	l := junk.New()
1599	l["rel"] = "self"
1600	l["type"] = `application/activity+json`
1601	l["href"] = user.URL
1602	links = append(links, l)
1603	j["links"] = links
1604
1605	w.Header().Set("Cache-Control", "max-age=3600")
1606	w.Header().Set("Content-Type", "application/jrd+json")
1607	j.Write(w)
1608}
1609
1610func somedays() string {
1611	secs := 432000 + notrand.Int63n(432000)
1612	return fmt.Sprintf("%d", secs)
1613}
1614
1615func avatate(w http.ResponseWriter, r *http.Request) {
1616	n := r.FormValue("a")
1617	a := avatar(n)
1618	w.Header().Set("Cache-Control", "max-age="+somedays())
1619	w.Write(a)
1620}
1621
1622func servecss(w http.ResponseWriter, r *http.Request) {
1623	data, _ := ioutil.ReadFile("views" + r.URL.Path)
1624	s := css.Process(string(data))
1625	w.Header().Set("Cache-Control", "max-age=7776000")
1626	w.Header().Set("Content-Type", "text/css; charset=utf-8")
1627	w.Write([]byte(s))
1628}
1629func servehtml(w http.ResponseWriter, r *http.Request) {
1630	templinfo := getInfo(r)
1631	err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1632	if err != nil {
1633		log.Print(err)
1634	}
1635}
1636func serveemu(w http.ResponseWriter, r *http.Request) {
1637	xid := mux.Vars(r)["xid"]
1638	w.Header().Set("Cache-Control", "max-age="+somedays())
1639	http.ServeFile(w, r, "emus/"+xid)
1640}
1641func servememe(w http.ResponseWriter, r *http.Request) {
1642	xid := mux.Vars(r)["xid"]
1643	w.Header().Set("Cache-Control", "max-age="+somedays())
1644	http.ServeFile(w, r, "memes/"+xid)
1645}
1646
1647func servefile(w http.ResponseWriter, r *http.Request) {
1648	xid := mux.Vars(r)["xid"]
1649	row := stmtFileData.QueryRow(xid)
1650	var media string
1651	var data []byte
1652	err := row.Scan(&media, &data)
1653	if err != nil {
1654		log.Printf("error loading file: %s", err)
1655		http.NotFound(w, r)
1656		return
1657	}
1658	w.Header().Set("Content-Type", media)
1659	w.Header().Set("X-Content-Type-Options", "nosniff")
1660	w.Header().Set("Cache-Control", "max-age="+somedays())
1661	w.Write(data)
1662}
1663
1664func nomoroboto(w http.ResponseWriter, r *http.Request) {
1665	io.WriteString(w, "User-agent: *\n")
1666	io.WriteString(w, "Disallow: /a\n")
1667	io.WriteString(w, "Disallow: /d\n")
1668	io.WriteString(w, "Disallow: /meme\n")
1669	for _, u := range allusers() {
1670		fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1671	}
1672}
1673
1674func serve() {
1675	db := opendatabase()
1676	login.Init(db)
1677
1678	listener, err := openListener()
1679	if err != nil {
1680		log.Fatal(err)
1681	}
1682	go redeliverator()
1683
1684	debug := false
1685	getconfig("debug", &debug)
1686	readviews = templates.Load(debug,
1687		"views/honkpage.html",
1688		"views/honkfrags.html",
1689		"views/honkers.html",
1690		"views/zonkers.html",
1691		"views/combos.html",
1692		"views/honkform.html",
1693		"views/honk.html",
1694		"views/account.html",
1695		"views/about.html",
1696		"views/funzone.html",
1697		"views/login.html",
1698		"views/xzone.html",
1699		"views/header.html",
1700		"views/onts.html",
1701	)
1702	if !debug {
1703		s := "views/style.css"
1704		savedstyleparams[s] = getstyleparam(s)
1705		s = "views/local.css"
1706		savedstyleparams[s] = getstyleparam(s)
1707	}
1708
1709	bitethethumbs()
1710
1711	mux := mux.NewRouter()
1712	mux.Use(login.Checker)
1713
1714	posters := mux.Methods("POST").Subrouter()
1715	getters := mux.Methods("GET").Subrouter()
1716
1717	getters.HandleFunc("/", homepage)
1718	getters.HandleFunc("/home", homepage)
1719	getters.HandleFunc("/front", homepage)
1720	getters.HandleFunc("/robots.txt", nomoroboto)
1721	getters.HandleFunc("/rss", showrss)
1722	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1723	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1724	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1725	posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1726	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1727	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1728	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1729	getters.HandleFunc("/a", avatate)
1730	getters.HandleFunc("/o", thelistingoftheontologies)
1731	getters.HandleFunc("/o/{name:[a-z0-9-]+}", showontology)
1732	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1733	getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1734	getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1735	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1736
1737	getters.HandleFunc("/style.css", servecss)
1738	getters.HandleFunc("/local.css", servecss)
1739	getters.HandleFunc("/about", servehtml)
1740	getters.HandleFunc("/login", servehtml)
1741	posters.HandleFunc("/dologin", login.LoginFunc)
1742	getters.HandleFunc("/logout", login.LogoutFunc)
1743
1744	loggedin := mux.NewRoute().Subrouter()
1745	loggedin.Use(login.Required)
1746	loggedin.HandleFunc("/account", accountpage)
1747	loggedin.HandleFunc("/funzone", showfunzone)
1748	loggedin.HandleFunc("/chpass", dochpass)
1749	loggedin.HandleFunc("/atme", homepage)
1750	loggedin.HandleFunc("/zonkzone", zonkzone)
1751	loggedin.HandleFunc("/xzone", xzone)
1752	loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1753	loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1754	loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1755	loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1756	loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1757	loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1758	loggedin.HandleFunc("/honkers", showhonkers)
1759	loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1760	loggedin.HandleFunc("/h", showhonker)
1761	loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1762	loggedin.HandleFunc("/c", showcombos)
1763	loggedin.HandleFunc("/t", showconvoy)
1764	loggedin.HandleFunc("/q", showsearch)
1765	loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1766
1767	err = http.Serve(listener, mux)
1768	if err != nil {
1769		log.Fatal(err)
1770	}
1771}
1772
1773func cleanupdb(arg string) {
1774	db := opendatabase()
1775	days, err := strconv.Atoi(arg)
1776	if err != nil {
1777		honker := arg
1778		expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1779		doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1780		doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1781	} else {
1782		expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1783		doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and convoy not in (select convoy from honks where whofore = 2 or whofore = 3))", expdate)
1784		doordie(db, "delete from honks where dt < ? and whofore = 0 and convoy not in (select convoy from honks where whofore = 2 or whofore = 3)", expdate)
1785	}
1786	doordie(db, "delete from files where fileid not in (select fileid from donks)")
1787	for _, u := range allusers() {
1788		doordie(db, "delete from zonkers where userid = ? and wherefore = 'zonvoy' and zonkerid < (select zonkerid from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 1 offset 200)", u.UserID, u.UserID)
1789	}
1790}
1791
1792var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1793var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1794var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1795var stmtHonksBySearch, stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1796var stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1797var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1798var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1799var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
1800var stmtSelectOnts, stmtSaveOnts, stmtUpdateFlags, stmtClearFlags *sql.Stmt
1801
1802func preparetodie(db *sql.DB, s string) *sql.Stmt {
1803	stmt, err := db.Prepare(s)
1804	if err != nil {
1805		log.Fatalf("error %s: %s", err, s)
1806	}
1807	return stmt
1808}
1809
1810func prepareStatements(db *sql.DB) {
1811	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")
1812	stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1813	stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1814	stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1815	stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1816	stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1817
1818	selecthonks := "select honks.honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, convoy, whofore, flags, onts from honks join users on honks.userid = users.userid "
1819	limit := " order by honks.honkid desc limit 250"
1820	butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1821	stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1822	stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
1823	stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1824	stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1825	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)
1826	stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1827	stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1828	stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1829	stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1830	stmtHonksBySearch = preparetodie(db, selecthonks+"where honks.userid = ? and noise like ?"+limit)
1831	stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1832	stmtHonksByOntology = preparetodie(db, selecthonks+"join onts on honks.honkid = onts.honkid where onts.ontology = ? and (honks.userid = ? or (? = -1 and honks.whofore = 2))"+limit)
1833
1834	stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags, onts) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1835	stmtSaveOnts = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
1836	stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1837	stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1838	stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1839	stmtZonkIt = preparetodie(db, "delete from honks where honkid = ?")
1840	stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1841	stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1842	stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1843	stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey, options from users where username = ?")
1844	stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1845	stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1846	stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1847	stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1848	stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1849	stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zomain' or wherefore = 'zord' or wherefore = 'zilence')")
1850	stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1851	stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1852	stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1853	stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1854	stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1855	stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
1856	stmtRecentHonkers = preparetodie(db, "select distinct(honker) from honks where userid = ? and honker not in (select xid from honkers where userid = ? and flavor = 'sub') order by honkid desc limit 100")
1857	stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
1858	stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
1859	stmtSelectOnts = preparetodie(db, "select distinct(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2)")
1860}
1861
1862func ElaborateUnitTests() {
1863}
1864
1865func main() {
1866	cmd := "run"
1867	if len(os.Args) > 1 {
1868		cmd = os.Args[1]
1869	}
1870	switch cmd {
1871	case "init":
1872		initdb()
1873	case "upgrade":
1874		upgradedb()
1875	}
1876	db := opendatabase()
1877	dbversion := 0
1878	getconfig("dbversion", &dbversion)
1879	if dbversion != myVersion {
1880		log.Fatal("incorrect database version. run upgrade.")
1881	}
1882	getconfig("servermsg", &serverMsg)
1883	getconfig("servername", &serverName)
1884	getconfig("usersep", &userSep)
1885	getconfig("honksep", &honkSep)
1886	getconfig("dnf", &donotfedafterdark)
1887	prepareStatements(db)
1888	switch cmd {
1889	case "adduser":
1890		adduser()
1891	case "cleanup":
1892		arg := "30"
1893		if len(os.Args) > 2 {
1894			arg = os.Args[2]
1895		}
1896		cleanupdb(arg)
1897	case "ping":
1898		if len(os.Args) < 4 {
1899			fmt.Printf("usage: honk ping from to\n")
1900			return
1901		}
1902		name := os.Args[2]
1903		targ := os.Args[3]
1904		user, err := butwhatabout(name)
1905		if err != nil {
1906			log.Printf("unknown user")
1907			return
1908		}
1909		ping(user, targ)
1910	case "peep":
1911		peeppeep()
1912	case "run":
1913		serve()
1914	case "test":
1915		ElaborateUnitTests()
1916	default:
1917		log.Fatal("unknown command")
1918	}
1919}