all repos — honk @ 92cd22d3402a8c71a249a83899fde568f0b1a71c

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