all repos — honk @ ed39930ffa00f8e100cc6bebab578c03b34e58d6

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	"crypto/rand"
  21	"crypto/rsa"
  22	"database/sql"
  23	"fmt"
  24	"html"
  25	"html/template"
  26	"io"
  27	"log"
  28	notrand "math/rand"
  29	"net/http"
  30	"os"
  31	"sort"
  32	"strconv"
  33	"strings"
  34	"time"
  35
  36	"github.com/gorilla/mux"
  37	"humungus.tedunangst.com/r/webs/image"
  38	"humungus.tedunangst.com/r/webs/login"
  39	"humungus.tedunangst.com/r/webs/rss"
  40	"humungus.tedunangst.com/r/webs/templates"
  41)
  42
  43type WhatAbout struct {
  44	ID      int64
  45	Name    string
  46	Display string
  47	About   string
  48	Key     string
  49	URL     string
  50}
  51
  52type Honk struct {
  53	ID       int64
  54	UserID   int64
  55	Username string
  56	What     string
  57	Honker   string
  58	Oonker   string
  59	XID      string
  60	RID      string
  61	Date     time.Time
  62	URL      string
  63	Noise    string
  64	Precis   string
  65	Convoy   string
  66	Audience []string
  67	Privacy  string
  68	HTML     template.HTML
  69	Donks    []*Donk
  70}
  71
  72type Donk struct {
  73	FileID  int64
  74	XID     string
  75	Name    string
  76	URL     string
  77	Media   string
  78	Content []byte
  79}
  80
  81type Honker struct {
  82	ID     int64
  83	UserID int64
  84	Name   string
  85	XID    string
  86	Flavor string
  87	Combos []string
  88}
  89
  90var serverName string
  91var iconName = "icon.png"
  92
  93var readviews *templates.Template
  94
  95func getInfo(r *http.Request) map[string]interface{} {
  96	templinfo := make(map[string]interface{})
  97	templinfo["StyleParam"] = getstyleparam("views/style.css")
  98	templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
  99	templinfo["ServerName"] = serverName
 100	templinfo["IconName"] = iconName
 101	templinfo["UserInfo"] = login.GetUserInfo(r)
 102	return templinfo
 103}
 104
 105func homepage(w http.ResponseWriter, r *http.Request) {
 106	templinfo := getInfo(r)
 107	u := login.GetUserInfo(r)
 108	var honks []*Honk
 109	if u != nil {
 110		if r.URL.Path == "/atme" {
 111			honks = gethonksforme(u.UserID)
 112		} else {
 113			honks = gethonksforuser(u.UserID)
 114		}
 115		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 116	} else {
 117		honks = getpublichonks()
 118	}
 119
 120	var modtime time.Time
 121	if len(honks) > 0 {
 122		modtime = honks[0].Date
 123	}
 124	debug := false
 125	getconfig("debug", &debug)
 126	imh := r.Header.Get("If-Modified-Since")
 127	if !debug && imh != "" && !modtime.IsZero() {
 128		ifmod, err := time.Parse(http.TimeFormat, imh)
 129		if err == nil && !modtime.After(ifmod) {
 130			w.WriteHeader(http.StatusNotModified)
 131			return
 132		}
 133	}
 134	reverbolate(honks)
 135
 136	msg := "Things happen."
 137	getconfig("servermsg", &msg)
 138	templinfo["Honks"] = honks
 139	templinfo["ShowRSS"] = true
 140	templinfo["ServerMessage"] = msg
 141	if u == nil {
 142		w.Header().Set("Cache-Control", "max-age=60")
 143	} else {
 144		w.Header().Set("Cache-Control", "max-age=0")
 145	}
 146	w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
 147	err := readviews.Execute(w, "honkpage.html", templinfo)
 148	if err != nil {
 149		log.Print(err)
 150	}
 151}
 152
 153func showrss(w http.ResponseWriter, r *http.Request) {
 154	name := mux.Vars(r)["name"]
 155
 156	var honks []*Honk
 157	if name != "" {
 158		honks = gethonksbyuser(name)
 159	} else {
 160		honks = getpublichonks()
 161	}
 162	reverbolate(honks)
 163
 164	home := fmt.Sprintf("https://%s/", serverName)
 165	base := home
 166	if name != "" {
 167		home += "u/" + name
 168		name += " "
 169	}
 170	feed := rss.Feed{
 171		Title:       name + "honk",
 172		Link:        home,
 173		Description: name + "honk rss",
 174		Image: &rss.Image{
 175			URL:   base + "icon.png",
 176			Title: name + "honk rss",
 177			Link:  home,
 178		},
 179	}
 180	var modtime time.Time
 181	for _, honk := range honks {
 182		desc := string(honk.HTML)
 183		for _, d := range honk.Donks {
 184			desc += fmt.Sprintf(`<p><a href="%sd/%s">Attachment: %s</a>`,
 185				base, d.XID, html.EscapeString(d.Name))
 186		}
 187
 188		feed.Items = append(feed.Items, &rss.Item{
 189			Title:       fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
 190			Description: rss.CData{desc},
 191			Link:        honk.URL,
 192			PubDate:     honk.Date.Format(time.RFC1123),
 193		})
 194		if honk.Date.After(modtime) {
 195			modtime = honk.Date
 196		}
 197	}
 198	w.Header().Set("Cache-Control", "max-age=300")
 199	w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
 200
 201	err := feed.Write(w)
 202	if err != nil {
 203		log.Printf("error writing rss: %s", err)
 204	}
 205}
 206
 207func butwhatabout(name string) (*WhatAbout, error) {
 208	row := stmtWhatAbout.QueryRow(name)
 209	var user WhatAbout
 210	err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key)
 211	user.URL = fmt.Sprintf("https://%s/u/%s", serverName, user.Name)
 212	return &user, err
 213}
 214
 215func crappola(j map[string]interface{}) bool {
 216	t, _ := jsongetstring(j, "type")
 217	a, _ := jsongetstring(j, "actor")
 218	o, _ := jsongetstring(j, "object")
 219	if t == "Delete" && a == o {
 220		log.Printf("crappola from %s", a)
 221		return true
 222	}
 223	return false
 224}
 225
 226func ping(user *WhatAbout, who string) {
 227	box, err := getboxes(who)
 228	if err != nil {
 229		log.Printf("no inbox for ping: %s", err)
 230		return
 231	}
 232	j := NewJunk()
 233	j["@context"] = itiswhatitis
 234	j["type"] = "Ping"
 235	j["id"] = user.URL + "/ping/" + xfiltrate()
 236	j["actor"] = user.URL
 237	j["to"] = who
 238	keyname, key := ziggy(user.Name)
 239	err = PostJunk(keyname, key, box.In, j)
 240	if err != nil {
 241		log.Printf("can't send ping: %s", err)
 242		return
 243	}
 244	log.Printf("sent ping to %s: %s", who, j["id"])
 245}
 246
 247func pong(user *WhatAbout, who string, obj string) {
 248	box, err := getboxes(who)
 249	if err != nil {
 250		log.Printf("no inbox for pong %s : %s", who, err)
 251		return
 252	}
 253	j := NewJunk()
 254	j["@context"] = itiswhatitis
 255	j["type"] = "Pong"
 256	j["id"] = user.URL + "/pong/" + xfiltrate()
 257	j["actor"] = user.URL
 258	j["to"] = who
 259	j["object"] = obj
 260	keyname, key := ziggy(user.Name)
 261	err = PostJunk(keyname, key, box.In, j)
 262	if err != nil {
 263		log.Printf("can't send pong: %s", err)
 264		return
 265	}
 266}
 267
 268func inbox(w http.ResponseWriter, r *http.Request) {
 269	name := mux.Vars(r)["name"]
 270	user, err := butwhatabout(name)
 271	if err != nil {
 272		http.NotFound(w, r)
 273		return
 274	}
 275	var buf bytes.Buffer
 276	io.Copy(&buf, r.Body)
 277	payload := buf.Bytes()
 278	j, err := ReadJunk(bytes.NewReader(payload))
 279	if err != nil {
 280		log.Printf("bad payload: %s", err)
 281		io.WriteString(os.Stdout, "bad payload\n")
 282		os.Stdout.Write(payload)
 283		io.WriteString(os.Stdout, "\n")
 284		return
 285	}
 286	if crappola(j) {
 287		return
 288	}
 289	keyname, err := zag(r, payload)
 290	if err != nil {
 291		log.Printf("inbox message failed signature: %s", err)
 292		if keyname != "" {
 293			keyname, err = makeitworksomehowwithoutregardforkeycontinuity(keyname, r, payload)
 294		}
 295		if err != nil {
 296			return
 297		}
 298	}
 299	what, _ := jsongetstring(j, "type")
 300	if what == "Like" {
 301		return
 302	}
 303	who, _ := jsongetstring(j, "actor")
 304	if !keymatch(keyname, who, what, user.ID) {
 305		log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
 306		return
 307	}
 308	objid, _ := jsongetstring(j, "id")
 309	if thoudostbitethythumb(user.ID, []string{who}, objid) {
 310		log.Printf("ignoring thumb sucker %s", who)
 311		return
 312	}
 313	switch what {
 314	case "Ping":
 315		obj, _ := jsongetstring(j, "id")
 316		log.Printf("ping from %s: %s", who, obj)
 317		pong(user, who, obj)
 318	case "Pong":
 319		obj, _ := jsongetstring(j, "object")
 320		log.Printf("pong from %s: %s", who, obj)
 321	case "Follow":
 322		obj, _ := jsongetstring(j, "object")
 323		if obj == user.URL {
 324			log.Printf("updating honker follow: %s", who)
 325			rubadubdub(user, j)
 326		} else {
 327			log.Printf("can't follow %s", obj)
 328		}
 329	case "Accept":
 330		log.Printf("updating honker accept: %s", who)
 331		_, err = stmtUpdateFlavor.Exec("sub", user.ID, who, "presub")
 332		if err != nil {
 333			log.Printf("error updating honker: %s", err)
 334			return
 335		}
 336	case "Undo":
 337		obj, ok := jsongetmap(j, "object")
 338		if !ok {
 339			log.Printf("unknown undo no object")
 340		} else {
 341			what, _ := jsongetstring(obj, "type")
 342			switch what {
 343			case "Follow":
 344				log.Printf("updating honker undo: %s", who)
 345				_, err = stmtUpdateFlavor.Exec("undub", user.ID, who, "dub")
 346				if err != nil {
 347					log.Printf("error updating honker: %s", err)
 348					return
 349				}
 350			case "Like":
 351			case "Announce":
 352			default:
 353				log.Printf("unknown undo: %s", what)
 354			}
 355		}
 356	default:
 357		xonk := xonkxonk(user, j)
 358		if xonk != nil {
 359			savexonk(user, xonk)
 360		}
 361	}
 362}
 363
 364func outbox(w http.ResponseWriter, r *http.Request) {
 365	name := mux.Vars(r)["name"]
 366	user, err := butwhatabout(name)
 367	if err != nil {
 368		http.NotFound(w, r)
 369		return
 370	}
 371	honks := gethonksbyuser(name)
 372
 373	var jonks []map[string]interface{}
 374	for _, h := range honks {
 375		j, _ := jonkjonk(user, h)
 376		jonks = append(jonks, j)
 377	}
 378
 379	j := NewJunk()
 380	j["@context"] = itiswhatitis
 381	j["id"] = user.URL + "/outbox"
 382	j["type"] = "OrderedCollection"
 383	j["totalItems"] = len(jonks)
 384	j["orderedItems"] = jonks
 385
 386	w.Header().Set("Cache-Control", "max-age=60")
 387	w.Header().Set("Content-Type", theonetruename)
 388	WriteJunk(w, j)
 389}
 390
 391func emptiness(w http.ResponseWriter, r *http.Request) {
 392	name := mux.Vars(r)["name"]
 393	user, err := butwhatabout(name)
 394	if err != nil {
 395		http.NotFound(w, r)
 396		return
 397	}
 398	colname := "/followers"
 399	if strings.HasSuffix(r.URL.Path, "/following") {
 400		colname = "/following"
 401	}
 402	j := NewJunk()
 403	j["@context"] = itiswhatitis
 404	j["id"] = user.URL + colname
 405	j["type"] = "OrderedCollection"
 406	j["totalItems"] = 0
 407	j["orderedItems"] = []interface{}{}
 408
 409	w.Header().Set("Cache-Control", "max-age=60")
 410	w.Header().Set("Content-Type", theonetruename)
 411	WriteJunk(w, j)
 412}
 413
 414func showuser(w http.ResponseWriter, r *http.Request) {
 415	name := mux.Vars(r)["name"]
 416	user, err := butwhatabout(name)
 417	if err != nil {
 418		http.NotFound(w, r)
 419		return
 420	}
 421	if friendorfoe(r.Header.Get("Accept")) {
 422		j := asjonker(user)
 423		w.Header().Set("Cache-Control", "max-age=600")
 424		w.Header().Set("Content-Type", theonetruename)
 425		WriteJunk(w, j)
 426		return
 427	}
 428	honks := gethonksbyuser(name)
 429	u := login.GetUserInfo(r)
 430	honkpage(w, r, u, user, honks, "")
 431}
 432
 433func showhonker(w http.ResponseWriter, r *http.Request) {
 434	name := mux.Vars(r)["name"]
 435	u := login.GetUserInfo(r)
 436	honks := gethonksbyhonker(u.UserID, name)
 437	honkpage(w, r, u, nil, honks, "honks by honker: "+name)
 438}
 439
 440func showcombo(w http.ResponseWriter, r *http.Request) {
 441	name := mux.Vars(r)["name"]
 442	u := login.GetUserInfo(r)
 443	honks := gethonksbycombo(u.UserID, name)
 444	honkpage(w, r, u, nil, honks, "honks by combo: "+name)
 445}
 446func showconvoy(w http.ResponseWriter, r *http.Request) {
 447	c := r.FormValue("c")
 448	var userid int64 = -1
 449	u := login.GetUserInfo(r)
 450	if u != nil {
 451		userid = u.UserID
 452	}
 453	honks := gethonksbyconvoy(userid, c)
 454	honkpage(w, r, u, nil, honks, "honks in convoy: "+c)
 455}
 456
 457func showhonk(w http.ResponseWriter, r *http.Request) {
 458	name := mux.Vars(r)["name"]
 459	xid := mux.Vars(r)["xid"]
 460	user, err := butwhatabout(name)
 461	if err != nil {
 462		http.NotFound(w, r)
 463		return
 464	}
 465	h := getxonk(user.ID, xid)
 466	if h == nil {
 467		http.NotFound(w, r)
 468		return
 469	}
 470	if friendorfoe(r.Header.Get("Accept")) {
 471		donksforhonks([]*Honk{h})
 472		_, j := jonkjonk(user, h)
 473		j["@context"] = itiswhatitis
 474		w.Header().Set("Cache-Control", "max-age=3600")
 475		w.Header().Set("Content-Type", theonetruename)
 476		WriteJunk(w, j)
 477		return
 478	}
 479	honks := gethonksbyconvoy(-1, h.Convoy)
 480	for _, hh := range honks {
 481		if hh.XID != h.XID {
 482			hh.Privacy = "limited"
 483		}
 484	}
 485	u := login.GetUserInfo(r)
 486	honkpage(w, r, u, nil, honks, "one honk maybe more")
 487}
 488
 489func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
 490	honks []*Honk, infomsg string) {
 491	reverbolate(honks)
 492	templinfo := getInfo(r)
 493	if u != nil {
 494		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 495	}
 496	if u == nil {
 497		w.Header().Set("Cache-Control", "max-age=60")
 498	}
 499	if user != nil {
 500		templinfo["Name"] = user.Name
 501		whatabout := user.About
 502		whatabout = obfusbreak(user.About)
 503		templinfo["WhatAbout"] = cleanstring(whatabout)
 504	}
 505	templinfo["Honks"] = honks
 506	templinfo["ServerMessage"] = infomsg
 507	err := readviews.Execute(w, "honkpage.html", templinfo)
 508	if err != nil {
 509		log.Print(err)
 510	}
 511}
 512
 513func saveuser(w http.ResponseWriter, r *http.Request) {
 514	whatabout := r.FormValue("whatabout")
 515	u := login.GetUserInfo(r)
 516	db := opendatabase()
 517	_, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
 518	if err != nil {
 519		log.Printf("error bouting what: %s", err)
 520	}
 521
 522	http.Redirect(w, r, "/account", http.StatusSeeOther)
 523}
 524
 525func gethonkers(userid int64) []*Honker {
 526	rows, err := stmtHonkers.Query(userid)
 527	if err != nil {
 528		log.Printf("error querying honkers: %s", err)
 529		return nil
 530	}
 531	defer rows.Close()
 532	var honkers []*Honker
 533	for rows.Next() {
 534		var f Honker
 535		var combos string
 536		err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
 537		f.Combos = strings.Split(strings.TrimSpace(combos), " ")
 538		if err != nil {
 539			log.Printf("error scanning honker: %s", err)
 540			return nil
 541		}
 542		honkers = append(honkers, &f)
 543	}
 544	return honkers
 545}
 546
 547func getdubs(userid int64) []*Honker {
 548	rows, err := stmtDubbers.Query(userid)
 549	if err != nil {
 550		log.Printf("error querying dubs: %s", err)
 551		return nil
 552	}
 553	defer rows.Close()
 554	var honkers []*Honker
 555	for rows.Next() {
 556		var f Honker
 557		err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
 558		if err != nil {
 559			log.Printf("error scanning honker: %s", err)
 560			return nil
 561		}
 562		honkers = append(honkers, &f)
 563	}
 564	return honkers
 565}
 566
 567func getxonk(userid int64, xid string) *Honk {
 568	h := new(Honk)
 569	var dt, aud string
 570	row := stmtOneXonk.QueryRow(userid, xid)
 571	err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
 572		&dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy)
 573	if err != nil {
 574		if err != sql.ErrNoRows {
 575			log.Printf("error scanning xonk: %s", err)
 576		}
 577		return nil
 578	}
 579	h.Date, _ = time.Parse(dbtimeformat, dt)
 580	h.Audience = strings.Split(aud, " ")
 581	return h
 582}
 583
 584func getpublichonks() []*Honk {
 585	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
 586	rows, err := stmtPublicHonks.Query(dt)
 587	return getsomehonks(rows, err)
 588}
 589func gethonksbyuser(name string) []*Honk {
 590	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
 591	rows, err := stmtUserHonks.Query(name, dt)
 592	return getsomehonks(rows, err)
 593}
 594func gethonksforuser(userid int64) []*Honk {
 595	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
 596	rows, err := stmtHonksForUser.Query(userid, dt, userid)
 597	return getsomehonks(rows, err)
 598}
 599func gethonksforme(userid int64) []*Honk {
 600	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
 601	rows, err := stmtHonksForMe.Query(userid, dt, userid)
 602	return getsomehonks(rows, err)
 603}
 604func gethonksbyhonker(userid int64, honker string) []*Honk {
 605	rows, err := stmtHonksByHonker.Query(userid, honker, userid)
 606	return getsomehonks(rows, err)
 607}
 608func gethonksbycombo(userid int64, combo string) []*Honk {
 609	combo = "% " + combo + " %"
 610	rows, err := stmtHonksByCombo.Query(userid, combo, userid)
 611	return getsomehonks(rows, err)
 612}
 613func gethonksbyconvoy(userid int64, convoy string) []*Honk {
 614	rows, err := stmtHonksByConvoy.Query(userid, convoy)
 615	honks := getsomehonks(rows, err)
 616	for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
 617		honks[i], honks[j] = honks[j], honks[i]
 618	}
 619	return honks
 620}
 621
 622func getsomehonks(rows *sql.Rows, err error) []*Honk {
 623	if err != nil {
 624		log.Printf("error querying honks: %s", err)
 625		return nil
 626	}
 627	defer rows.Close()
 628	var honks []*Honk
 629	for rows.Next() {
 630		var h Honk
 631		var dt, aud string
 632		err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker,
 633			&h.XID, &h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy)
 634		if err != nil {
 635			log.Printf("error scanning honks: %s", err)
 636			return nil
 637		}
 638		h.Date, _ = time.Parse(dbtimeformat, dt)
 639		h.Audience = strings.Split(aud, " ")
 640		h.Privacy = "limited"
 641		for _, a := range h.Audience {
 642			if a == thewholeworld {
 643				h.Privacy = ""
 644				break
 645			}
 646		}
 647		honks = append(honks, &h)
 648	}
 649	rows.Close()
 650	donksforhonks(honks)
 651	return honks
 652}
 653
 654func donksforhonks(honks []*Honk) {
 655	db := opendatabase()
 656	var ids []string
 657	hmap := make(map[int64]*Honk)
 658	for _, h := range honks {
 659		ids = append(ids, fmt.Sprintf("%d", h.ID))
 660		hmap[h.ID] = h
 661	}
 662	q := fmt.Sprintf("select honkid, donks.fileid, xid, name, url, media from donks join files on donks.fileid = files.fileid where honkid in (%s)", strings.Join(ids, ","))
 663	rows, err := db.Query(q)
 664	if err != nil {
 665		log.Printf("error querying donks: %s", err)
 666		return
 667	}
 668	defer rows.Close()
 669	for rows.Next() {
 670		var hid int64
 671		var d Donk
 672		err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media)
 673		if err != nil {
 674			log.Printf("error scanning donk: %s", err)
 675			continue
 676		}
 677		h := hmap[hid]
 678		h.Donks = append(h.Donks, &d)
 679	}
 680}
 681
 682func savebonk(w http.ResponseWriter, r *http.Request) {
 683	xid := r.FormValue("xid")
 684	userinfo := login.GetUserInfo(r)
 685
 686	log.Printf("bonking %s", xid)
 687
 688	xonk := getxonk(userinfo.UserID, xid)
 689	if xonk == nil {
 690		return
 691	}
 692	donksforhonks([]*Honk{xonk})
 693	if xonk.Honker == "" {
 694		xonk.XID = fmt.Sprintf("https://%s/u/%s/h/%s", serverName, xonk.Username, xonk.XID)
 695	}
 696
 697	dt := time.Now().UTC()
 698	bonk := Honk{
 699		UserID:   userinfo.UserID,
 700		Username: userinfo.Username,
 701		What:     "bonk",
 702		XID:      xonk.XID,
 703		Date:     dt,
 704		Donks:    xonk.Donks,
 705		Audience: []string{thewholeworld},
 706	}
 707
 708	user, _ := butwhatabout(userinfo.Username)
 709
 710	aud := strings.Join(bonk.Audience, " ")
 711	whofore := 0
 712	if strings.Contains(aud, user.URL) {
 713		whofore = 1
 714	}
 715	res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", "", xid, "",
 716		dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html", xonk.Precis, xonk.Honker)
 717	if err != nil {
 718		log.Printf("error saving bonk: %s", err)
 719		return
 720	}
 721	bonk.ID, _ = res.LastInsertId()
 722	for _, d := range bonk.Donks {
 723		_, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
 724		if err != nil {
 725			log.Printf("err saving donk: %s", err)
 726			return
 727		}
 728	}
 729
 730	go honkworldwide(user, &bonk)
 731}
 732
 733func zonkit(w http.ResponseWriter, r *http.Request) {
 734	wherefore := r.FormValue("wherefore")
 735	var what string
 736	switch wherefore {
 737	case "this honk":
 738		what = r.FormValue("honk")
 739		wherefore = "zonk"
 740	case "this honker":
 741		what = r.FormValue("honker")
 742		wherefore = "zonker"
 743	case "this convoy":
 744		what = r.FormValue("convoy")
 745		wherefore = "zonvoy"
 746	}
 747	if what == "" {
 748		return
 749	}
 750
 751	log.Printf("zonking %s %s", wherefore, what)
 752	userinfo := login.GetUserInfo(r)
 753	if wherefore == "zonk" {
 754		xonk := getxonk(userinfo.UserID, what)
 755		if xonk != nil {
 756			stmtZonkDonks.Exec(xonk.ID)
 757			stmtZonkIt.Exec(userinfo.UserID, what)
 758			if xonk.Honker == "" {
 759				zonk := Honk{
 760					What:     "zonk",
 761					XID:      xonk.XID,
 762					Date:     time.Now().UTC(),
 763					Audience: oneofakind(xonk.Audience),
 764				}
 765
 766				user, _ := butwhatabout(userinfo.Username)
 767				log.Printf("announcing deleted honk: %s", what)
 768				go honkworldwide(user, &zonk)
 769			}
 770		}
 771	} else {
 772		_, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
 773		if err != nil {
 774			log.Printf("error saving zonker: %s", err)
 775			return
 776		}
 777	}
 778}
 779
 780func savehonk(w http.ResponseWriter, r *http.Request) {
 781	rid := r.FormValue("rid")
 782	noise := r.FormValue("noise")
 783
 784	userinfo := login.GetUserInfo(r)
 785
 786	dt := time.Now().UTC()
 787	xid := xfiltrate()
 788	what := "honk"
 789	if rid != "" {
 790		what = "tonk"
 791	}
 792	honk := Honk{
 793		UserID:   userinfo.UserID,
 794		Username: userinfo.Username,
 795		What:     "honk",
 796		XID:      xid,
 797		Date:     dt,
 798	}
 799	if strings.HasPrefix(noise, "DZ:") {
 800		idx := strings.Index(noise, "\n")
 801		if idx == -1 {
 802			honk.Precis = noise
 803			noise = ""
 804		} else {
 805			honk.Precis = noise[:idx]
 806			noise = noise[idx+1:]
 807		}
 808	}
 809	noise = strings.TrimSpace(noise)
 810	honk.Precis = strings.TrimSpace(honk.Precis)
 811
 812	if noise != "" && noise[0] == '@' {
 813		honk.Audience = append(grapevine(noise), thewholeworld)
 814	} else {
 815		honk.Audience = prepend(thewholeworld, grapevine(noise))
 816	}
 817	var convoy string
 818	if rid != "" {
 819		xonk := getxonk(userinfo.UserID, rid)
 820		if xonk != nil {
 821			if xonk.Honker == "" {
 822				xonk.Honker = "https://" + serverName + "/u/" + xonk.Username
 823				rid = xonk.Honker + "/h/" + rid
 824			}
 825			honk.Audience = append(honk.Audience, xonk.Audience...)
 826			convoy = xonk.Convoy
 827		} else {
 828			xonkaud, c := whosthere(rid)
 829			honk.Audience = append(honk.Audience, xonkaud...)
 830			convoy = c
 831		}
 832		honk.Oonker = xonk.Honker
 833		honk.RID = rid
 834	}
 835	if convoy == "" {
 836		convoy = "data:,electrichonkytonk-" + xfiltrate()
 837	}
 838	butnottooloud(honk.Audience)
 839	honk.Audience = oneofakind(honk.Audience)
 840	noise = obfusbreak(noise)
 841	honk.Noise = noise
 842	honk.Convoy = convoy
 843
 844	file, filehdr, err := r.FormFile("donk")
 845	if err == nil {
 846		var buf bytes.Buffer
 847		io.Copy(&buf, file)
 848		file.Close()
 849		data := buf.Bytes()
 850		xid := xfiltrate()
 851		var media, name string
 852		img, err := image.Vacuum(&buf)
 853		if err == nil {
 854			data = img.Data
 855			format := img.Format
 856			media = "image/" + format
 857			if format == "jpeg" {
 858				format = "jpg"
 859			}
 860			name = xid + "." + format
 861			xid = name
 862		} else {
 863			maxsize := 100000
 864			if len(data) > maxsize {
 865				log.Printf("bad image: %s too much text: %d", err, len(data))
 866				http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
 867				return
 868			}
 869			for i := 0; i < len(data); i++ {
 870				if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
 871					log.Printf("bad image: %s not text: %d", err, data[i])
 872					http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
 873					return
 874				}
 875			}
 876			media = "text/plain"
 877			name = filehdr.Filename
 878			if name == "" {
 879				name = xid + ".txt"
 880			}
 881			xid += ".txt"
 882		}
 883		url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
 884		res, err := stmtSaveFile.Exec(xid, name, url, media, data)
 885		if err != nil {
 886			log.Printf("unable to save image: %s", err)
 887			return
 888		}
 889		var d Donk
 890		d.FileID, _ = res.LastInsertId()
 891		d.XID = name
 892		d.Name = name
 893		d.Media = media
 894		d.URL = url
 895		honk.Donks = append(honk.Donks, &d)
 896	}
 897	herd := herdofemus(honk.Noise)
 898	for _, e := range herd {
 899		donk := savedonk(e.ID, e.Name, "image/png")
 900		if donk != nil {
 901			donk.Name = e.Name
 902			honk.Donks = append(honk.Donks, donk)
 903		}
 904	}
 905
 906	user, _ := butwhatabout(userinfo.Username)
 907
 908	aud := strings.Join(honk.Audience, " ")
 909	whofore := 0
 910	if strings.Contains(aud, user.URL) {
 911		whofore = 1
 912	}
 913	res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
 914		dt.Format(dbtimeformat), "", aud, noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
 915	if err != nil {
 916		log.Printf("error saving honk: %s", err)
 917		return
 918	}
 919	honk.ID, _ = res.LastInsertId()
 920	for _, d := range honk.Donks {
 921		_, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
 922		if err != nil {
 923			log.Printf("err saving donk: %s", err)
 924			return
 925		}
 926	}
 927
 928	go honkworldwide(user, &honk)
 929
 930	http.Redirect(w, r, "/", http.StatusSeeOther)
 931}
 932
 933func showhonkers(w http.ResponseWriter, r *http.Request) {
 934	userinfo := login.GetUserInfo(r)
 935	templinfo := getInfo(r)
 936	templinfo["Honkers"] = gethonkers(userinfo.UserID)
 937	templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
 938	err := readviews.Execute(w, "honkers.html", templinfo)
 939	if err != nil {
 940		log.Print(err)
 941	}
 942}
 943
 944func showcombos(w http.ResponseWriter, r *http.Request) {
 945	userinfo := login.GetUserInfo(r)
 946	templinfo := getInfo(r)
 947	honkers := gethonkers(userinfo.UserID)
 948	var combos []string
 949	for _, h := range honkers {
 950		combos = append(combos, h.Combos...)
 951	}
 952	combos = oneofakind(combos)
 953	sort.Strings(combos)
 954	templinfo["Combos"] = combos
 955	err := readviews.Execute(w, "combos.html", templinfo)
 956	if err != nil {
 957		log.Print(err)
 958	}
 959}
 960
 961func savehonker(w http.ResponseWriter, r *http.Request) {
 962	u := login.GetUserInfo(r)
 963	name := r.FormValue("name")
 964	url := r.FormValue("url")
 965	peep := r.FormValue("peep")
 966	combos := r.FormValue("combos")
 967	honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
 968
 969	if honkerid > 0 {
 970		goodbye := r.FormValue("goodbye")
 971		if goodbye == "goodbye" {
 972			db := opendatabase()
 973			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
 974				honkerid, u.UserID)
 975			var xid string
 976			err := row.Scan(&xid)
 977			if err != nil {
 978				log.Printf("can't get honker xid: %s", err)
 979				return
 980			}
 981			log.Printf("unsubscribing from %s", xid)
 982			user, _ := butwhatabout(u.Username)
 983			err = itakeitallback(user, xid)
 984			if err != nil {
 985				log.Printf("can't take it back: %s", err)
 986			} else {
 987				_, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
 988				if err != nil {
 989					log.Printf("error updating honker: %s", err)
 990					return
 991				}
 992			}
 993
 994			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
 995			return
 996		}
 997		combos = " " + strings.TrimSpace(combos) + " "
 998		_, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
 999		if err != nil {
1000			log.Printf("update honker err: %s", err)
1001			return
1002		}
1003		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1004	}
1005
1006	flavor := "presub"
1007	if peep == "peep" {
1008		flavor = "peep"
1009	}
1010	if url == "" {
1011		return
1012	}
1013	if url[0] == '@' {
1014		url = gofish(url)
1015	}
1016	if url == "" {
1017		return
1018	}
1019	_, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1020	if err != nil {
1021		log.Print(err)
1022		return
1023	}
1024	if flavor == "presub" {
1025		user, _ := butwhatabout(u.Username)
1026		go subsub(user, url)
1027	}
1028	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1029}
1030
1031type Zonker struct {
1032	ID        int64
1033	Name      string
1034	Wherefore string
1035}
1036
1037func killzone(w http.ResponseWriter, r *http.Request) {
1038	db := opendatabase()
1039	userinfo := login.GetUserInfo(r)
1040	rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1041	if err != nil {
1042		log.Printf("err: %s", err)
1043		return
1044	}
1045	var zonkers []Zonker
1046	for rows.Next() {
1047		var z Zonker
1048		rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1049		zonkers = append(zonkers, z)
1050	}
1051	templinfo := getInfo(r)
1052	templinfo["Zonkers"] = zonkers
1053	templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1054	err = readviews.Execute(w, "zonkers.html", templinfo)
1055	if err != nil {
1056		log.Print(err)
1057	}
1058}
1059
1060func killitwithfire(w http.ResponseWriter, r *http.Request) {
1061	userinfo := login.GetUserInfo(r)
1062	itsok := r.FormValue("itsok")
1063	if itsok == "iforgiveyou" {
1064		zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1065		db := opendatabase()
1066		db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1067			userinfo.UserID, zonkerid)
1068		bitethethumbs()
1069		http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1070		return
1071	}
1072	wherefore := r.FormValue("wherefore")
1073	name := r.FormValue("name")
1074	if name == "" {
1075		return
1076	}
1077	switch wherefore {
1078	case "zonker":
1079	case "zurl":
1080	case "zonvoy":
1081	default:
1082		return
1083	}
1084	db := opendatabase()
1085	db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1086		userinfo.UserID, name, wherefore)
1087	if wherefore == "zonker" || wherefore == "zurl" {
1088		bitethethumbs()
1089	}
1090
1091	http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1092}
1093
1094func accountpage(w http.ResponseWriter, r *http.Request) {
1095	u := login.GetUserInfo(r)
1096	user, _ := butwhatabout(u.Username)
1097	templinfo := getInfo(r)
1098	templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1099	templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1100	templinfo["WhatAbout"] = user.About
1101	err := readviews.Execute(w, "account.html", templinfo)
1102	if err != nil {
1103		log.Print(err)
1104	}
1105}
1106
1107func dochpass(w http.ResponseWriter, r *http.Request) {
1108	err := login.ChangePassword(w, r)
1109	if err != nil {
1110		log.Printf("error changing password: %s", err)
1111	}
1112	http.Redirect(w, r, "/account", http.StatusSeeOther)
1113}
1114
1115func fingerlicker(w http.ResponseWriter, r *http.Request) {
1116	orig := r.FormValue("resource")
1117
1118	log.Printf("finger lick: %s", orig)
1119
1120	if strings.HasPrefix(orig, "acct:") {
1121		orig = orig[5:]
1122	}
1123
1124	name := orig
1125	idx := strings.LastIndexByte(name, '/')
1126	if idx != -1 {
1127		name = name[idx+1:]
1128		if "https://"+serverName+"/u/"+name != orig {
1129			log.Printf("foreign request rejected")
1130			name = ""
1131		}
1132	} else {
1133		idx = strings.IndexByte(name, '@')
1134		if idx != -1 {
1135			name = name[:idx]
1136			if name+"@"+serverName != orig {
1137				log.Printf("foreign request rejected")
1138				name = ""
1139			}
1140		}
1141	}
1142	user, err := butwhatabout(name)
1143	if err != nil {
1144		http.NotFound(w, r)
1145		return
1146	}
1147
1148	j := NewJunk()
1149	j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1150	j["aliases"] = []string{user.URL}
1151	var links []map[string]interface{}
1152	l := NewJunk()
1153	l["rel"] = "self"
1154	l["type"] = `application/activity+json`
1155	l["href"] = user.URL
1156	links = append(links, l)
1157	j["links"] = links
1158
1159	w.Header().Set("Cache-Control", "max-age=3600")
1160	w.Header().Set("Content-Type", "application/jrd+json")
1161	WriteJunk(w, j)
1162}
1163
1164func somedays() string {
1165	secs := 432000 + notrand.Int63n(432000)
1166	return fmt.Sprintf("%d", secs)
1167}
1168
1169func avatate(w http.ResponseWriter, r *http.Request) {
1170	n := r.FormValue("a")
1171	a := avatar(n)
1172	w.Header().Set("Cache-Control", "max-age="+somedays())
1173	w.Write(a)
1174}
1175
1176func servecss(w http.ResponseWriter, r *http.Request) {
1177	w.Header().Set("Cache-Control", "max-age=7776000")
1178	http.ServeFile(w, r, "views"+r.URL.Path)
1179}
1180func servehtml(w http.ResponseWriter, r *http.Request) {
1181	templinfo := getInfo(r)
1182	err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1183	if err != nil {
1184		log.Print(err)
1185	}
1186}
1187func serveemu(w http.ResponseWriter, r *http.Request) {
1188	xid := mux.Vars(r)["xid"]
1189	w.Header().Set("Cache-Control", "max-age="+somedays())
1190	http.ServeFile(w, r, "emus/"+xid)
1191}
1192
1193func servefile(w http.ResponseWriter, r *http.Request) {
1194	xid := mux.Vars(r)["xid"]
1195	row := stmtFileData.QueryRow(xid)
1196	var media string
1197	var data []byte
1198	err := row.Scan(&media, &data)
1199	if err != nil {
1200		log.Printf("error loading file: %s", err)
1201		http.NotFound(w, r)
1202		return
1203	}
1204	w.Header().Set("Content-Type", media)
1205	w.Header().Set("X-Content-Type-Options", "nosniff")
1206	w.Header().Set("Cache-Control", "max-age="+somedays())
1207	w.Write(data)
1208}
1209
1210func serve() {
1211	db := opendatabase()
1212	login.Init(db)
1213
1214	listener, err := openListener()
1215	if err != nil {
1216		log.Fatal(err)
1217	}
1218	go redeliverator()
1219
1220	debug := false
1221	getconfig("debug", &debug)
1222	readviews = templates.Load(debug,
1223		"views/honkpage.html",
1224		"views/honkers.html",
1225		"views/zonkers.html",
1226		"views/combos.html",
1227		"views/honkform.html",
1228		"views/honk.html",
1229		"views/account.html",
1230		"views/login.html",
1231		"views/header.html",
1232	)
1233	if !debug {
1234		s := "views/style.css"
1235		savedstyleparams[s] = getstyleparam(s)
1236		s = "views/local.css"
1237		savedstyleparams[s] = getstyleparam(s)
1238	}
1239
1240	bitethethumbs()
1241
1242	mux := mux.NewRouter()
1243	mux.Use(login.Checker)
1244
1245	posters := mux.Methods("POST").Subrouter()
1246	getters := mux.Methods("GET").Subrouter()
1247
1248	getters.HandleFunc("/", homepage)
1249	getters.HandleFunc("/rss", showrss)
1250	getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1251	getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1252	getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1253	posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1254	getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1255	getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1256	getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1257	getters.HandleFunc("/a", avatate)
1258	getters.HandleFunc("/t", showconvoy)
1259	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1260	getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1261	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1262
1263	getters.HandleFunc("/style.css", servecss)
1264	getters.HandleFunc("/local.css", servecss)
1265	getters.HandleFunc("/login", servehtml)
1266	posters.HandleFunc("/dologin", login.LoginFunc)
1267	getters.HandleFunc("/logout", login.LogoutFunc)
1268
1269	loggedin := mux.NewRoute().Subrouter()
1270	loggedin.Use(login.Required)
1271	loggedin.HandleFunc("/account", accountpage)
1272	loggedin.HandleFunc("/chpass", dochpass)
1273	loggedin.HandleFunc("/atme", homepage)
1274	loggedin.HandleFunc("/killzone", killzone)
1275	loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1276	loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1277	loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1278	loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1279	loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1280	loggedin.HandleFunc("/honkers", showhonkers)
1281	loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1282	loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1283	loggedin.HandleFunc("/c", showcombos)
1284	loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1285
1286	err = http.Serve(listener, mux)
1287	if err != nil {
1288		log.Fatal(err)
1289	}
1290}
1291
1292var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1293var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1294var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1295var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1296var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1297var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1298var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1299var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1300
1301func preparetodie(db *sql.DB, s string) *sql.Stmt {
1302	stmt, err := db.Prepare(s)
1303	if err != nil {
1304		log.Fatalf("error %s: %s", err, s)
1305	}
1306	return stmt
1307}
1308
1309func prepareStatements(db *sql.DB) {
1310	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")
1311	stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1312	stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1313	stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1314	stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1315	stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1316
1317	selecthonks := "select honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, convoy from honks join users on honks.userid = users.userid "
1318	limit := " order by honkid desc limit 250"
1319	butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1320	stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1321	stmtPublicHonks = preparetodie(db, selecthonks+"where honker = '' and dt > ?"+limit)
1322	stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ? and dt > ?"+limit)
1323	stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ?"+butnotthose+limit)
1324	stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1325	stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1326	stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1327	stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1328
1329	stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1330	stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1331	stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1332	stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1333	stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1334	stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1335	stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1336	stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1337	stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1338	stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1339	stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1340	stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1341	stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1342	stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1343	stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1344	stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1345	stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1346	stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1347}
1348
1349func ElaborateUnitTests() {
1350}
1351
1352func finishusersetup() error {
1353	db := opendatabase()
1354	k, err := rsa.GenerateKey(rand.Reader, 2048)
1355	if err != nil {
1356		return err
1357	}
1358	pubkey, err := zem(&k.PublicKey)
1359	if err != nil {
1360		return err
1361	}
1362	seckey, err := zem(k)
1363	if err != nil {
1364		return err
1365	}
1366	_, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1367	if err != nil {
1368		return err
1369	}
1370	return nil
1371}
1372
1373func main() {
1374	cmd := "run"
1375	if len(os.Args) > 1 {
1376		cmd = os.Args[1]
1377	}
1378	switch cmd {
1379	case "init":
1380		initdb()
1381	case "upgrade":
1382		upgradedb()
1383	}
1384	db := opendatabase()
1385	dbversion := 0
1386	getconfig("dbversion", &dbversion)
1387	if dbversion != myVersion {
1388		log.Fatal("incorrect database version. run upgrade.")
1389	}
1390	getconfig("servername", &serverName)
1391	prepareStatements(db)
1392	switch cmd {
1393	case "ping":
1394		if len(os.Args) < 4 {
1395			fmt.Printf("usage: honk ping from to\n")
1396			return
1397		}
1398		name := os.Args[2]
1399		targ := os.Args[3]
1400		user, err := butwhatabout(name)
1401		if err != nil {
1402			log.Printf("unknown user")
1403			return
1404		}
1405		ping(user, targ)
1406	case "peep":
1407		peeppeep()
1408	case "run":
1409		serve()
1410	case "test":
1411		ElaborateUnitTests()
1412	default:
1413		log.Fatal("unknown command")
1414	}
1415}