all repos — honk @ ffb6d2aca9b4ea2a8b142b21ba3eb0ddc45e45b7

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