all repos — honk @ 9c41d372a392c2d6195ef4a32c7c244869282e90

my fork of honk

honk.go (view raw)

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