all repos — honk @ 851a7a23f3f17d69efd26a5ecf7bb44a39fab3f8

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