all repos — honk @ 9d093a0fca36080ed05bc153c79e1998c08ddf74

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