all repos — honk @ 2bb87a7cff4cd5c6fcae45f8cd40dfbd85bd456b

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