all repos — honk @ 6a957a5a46f5bb43246df10b4404cc5ceadb7b77

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