all repos — honk @ 844cdfc15e932cfd026bff6104411bba73efb5be

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