all repos — honk @ 0cbbce8559e8c89e82c965c84daa2bb878ad834d

my fork of honk

honk.go (view raw)

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