all repos — honk @ c771af412c97074b60e9b357bbbbc474e1d34e66

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, 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.RID = rid
 834	}
 835	if convoy == "" {
 836		convoy = "data:,electrichonkytonk-" + xfiltrate()
 837	}
 838	butnottooloud(honk.Audience)
 839	honk.Audience = oneofakind(honk.Audience)
 840	noise = obfusbreak(noise)
 841	honk.Noise = noise
 842	honk.Convoy = convoy
 843
 844	file, filehdr, err := r.FormFile("donk")
 845	if err == nil {
 846		var buf bytes.Buffer
 847		io.Copy(&buf, file)
 848		file.Close()
 849		data := buf.Bytes()
 850		xid := xfiltrate()
 851		var media, name string
 852		img, err := image.Vacuum(&buf)
 853		if err == nil {
 854			data = img.Data
 855			format := img.Format
 856			media = "image/" + format
 857			if format == "jpeg" {
 858				format = "jpg"
 859			}
 860			name = xid + "." + format
 861			xid = name
 862		} else {
 863			maxsize := 100000
 864			if len(data) > maxsize {
 865				log.Printf("bad image: %s too much text: %d", err, len(data))
 866				http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
 867				return
 868			}
 869			for i := 0; i < len(data); i++ {
 870				if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
 871					log.Printf("bad image: %s not text: %d", err, data[i])
 872					http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
 873					return
 874				}
 875			}
 876			media = "text/plain"
 877			name = filehdr.Filename
 878			if name == "" {
 879				name = xid + ".txt"
 880			}
 881			xid += ".txt"
 882		}
 883		url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
 884		res, err := stmtSaveFile.Exec(xid, name, url, media, data)
 885		if err != nil {
 886			log.Printf("unable to save image: %s", err)
 887			return
 888		}
 889		var d Donk
 890		d.FileID, _ = res.LastInsertId()
 891		d.XID = name
 892		d.Name = name
 893		d.Media = media
 894		d.URL = url
 895		honk.Donks = append(honk.Donks, &d)
 896	}
 897	herd := herdofemus(honk.Noise)
 898	for _, e := range herd {
 899		donk := savedonk(e.ID, e.Name, "image/png")
 900		if donk != nil {
 901			donk.Name = e.Name
 902			honk.Donks = append(honk.Donks, donk)
 903		}
 904	}
 905
 906	user, _ := butwhatabout(userinfo.Username)
 907
 908	aud := strings.Join(honk.Audience, " ")
 909	whofore := 0
 910	if strings.Contains(aud, user.URL) {
 911		whofore = 1
 912	}
 913	res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
 914		dt.Format(dbtimeformat), "", aud, noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
 915	if err != nil {
 916		log.Printf("error saving honk: %s", err)
 917		return
 918	}
 919	honk.ID, _ = res.LastInsertId()
 920	for _, d := range honk.Donks {
 921		_, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
 922		if err != nil {
 923			log.Printf("err saving donk: %s", err)
 924			return
 925		}
 926	}
 927
 928	go honkworldwide(user, &honk)
 929
 930	http.Redirect(w, r, "/", http.StatusSeeOther)
 931}
 932
 933func showhonkers(w http.ResponseWriter, r *http.Request) {
 934	userinfo := login.GetUserInfo(r)
 935	templinfo := getInfo(r)
 936	templinfo["Honkers"] = gethonkers(userinfo.UserID)
 937	templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
 938	err := readviews.Execute(w, "honkers.html", templinfo)
 939	if err != nil {
 940		log.Print(err)
 941	}
 942}
 943
 944func showcombos(w http.ResponseWriter, r *http.Request) {
 945	userinfo := login.GetUserInfo(r)
 946	templinfo := getInfo(r)
 947	honkers := gethonkers(userinfo.UserID)
 948	var combos []string
 949	for _, h := range honkers {
 950		combos = append(combos, h.Combos...)
 951	}
 952	for i, c := range combos {
 953		if c == "-" {
 954			combos[i] = ""
 955		}
 956	}
 957	combos = oneofakind(combos)
 958	sort.Strings(combos)
 959	templinfo["Combos"] = combos
 960	err := readviews.Execute(w, "combos.html", templinfo)
 961	if err != nil {
 962		log.Print(err)
 963	}
 964}
 965
 966func savehonker(w http.ResponseWriter, r *http.Request) {
 967	u := login.GetUserInfo(r)
 968	name := r.FormValue("name")
 969	url := r.FormValue("url")
 970	peep := r.FormValue("peep")
 971	combos := r.FormValue("combos")
 972	honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
 973
 974	if honkerid > 0 {
 975		goodbye := r.FormValue("goodbye")
 976		if goodbye == "goodbye" {
 977			db := opendatabase()
 978			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
 979				honkerid, u.UserID)
 980			var xid string
 981			err := row.Scan(&xid)
 982			if err != nil {
 983				log.Printf("can't get honker xid: %s", err)
 984				return
 985			}
 986			log.Printf("unsubscribing from %s", xid)
 987			user, _ := butwhatabout(u.Username)
 988			err = itakeitallback(user, xid)
 989			if err != nil {
 990				log.Printf("can't take it back: %s", err)
 991			} else {
 992				_, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
 993				if err != nil {
 994					log.Printf("error updating honker: %s", err)
 995					return
 996				}
 997			}
 998
 999			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1000			return
1001		}
1002		combos = " " + strings.TrimSpace(combos) + " "
1003		_, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1004		if err != nil {
1005			log.Printf("update honker err: %s", err)
1006			return
1007		}
1008		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1009	}
1010
1011	flavor := "presub"
1012	if peep == "peep" {
1013		flavor = "peep"
1014	}
1015	if url == "" {
1016		return
1017	}
1018	if url[0] == '@' {
1019		url = gofish(url)
1020	}
1021	if url == "" {
1022		return
1023	}
1024	_, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1025	if err != nil {
1026		log.Print(err)
1027		return
1028	}
1029	if flavor == "presub" {
1030		user, _ := butwhatabout(u.Username)
1031		go subsub(user, url)
1032	}
1033	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1034}
1035
1036type Zonker struct {
1037	ID        int64
1038	Name      string
1039	Wherefore string
1040}
1041
1042func killzone(w http.ResponseWriter, r *http.Request) {
1043	db := opendatabase()
1044	userinfo := login.GetUserInfo(r)
1045	rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1046	if err != nil {
1047		log.Printf("err: %s", err)
1048		return
1049	}
1050	var zonkers []Zonker
1051	for rows.Next() {
1052		var z Zonker
1053		rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1054		zonkers = append(zonkers, z)
1055	}
1056	templinfo := getInfo(r)
1057	templinfo["Zonkers"] = zonkers
1058	templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1059	err = readviews.Execute(w, "zonkers.html", templinfo)
1060	if err != nil {
1061		log.Print(err)
1062	}
1063}
1064
1065func killitwithfire(w http.ResponseWriter, r *http.Request) {
1066	userinfo := login.GetUserInfo(r)
1067	itsok := r.FormValue("itsok")
1068	if itsok == "iforgiveyou" {
1069		zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1070		db := opendatabase()
1071		db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1072			userinfo.UserID, zonkerid)
1073		bitethethumbs()
1074		http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1075		return
1076	}
1077	wherefore := r.FormValue("wherefore")
1078	name := r.FormValue("name")
1079	if name == "" {
1080		return
1081	}
1082	switch wherefore {
1083	case "zonker":
1084	case "zurl":
1085	case "zonvoy":
1086	default:
1087		return
1088	}
1089	db := opendatabase()
1090	db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1091		userinfo.UserID, name, wherefore)
1092	if wherefore == "zonker" || wherefore == "zurl" {
1093		bitethethumbs()
1094	}
1095
1096	http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1097}
1098
1099func accountpage(w http.ResponseWriter, r *http.Request) {
1100	u := login.GetUserInfo(r)
1101	user, _ := butwhatabout(u.Username)
1102	templinfo := getInfo(r)
1103	templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1104	templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1105	templinfo["WhatAbout"] = user.About
1106	err := readviews.Execute(w, "account.html", templinfo)
1107	if err != nil {
1108		log.Print(err)
1109	}
1110}
1111
1112func dochpass(w http.ResponseWriter, r *http.Request) {
1113	err := login.ChangePassword(w, r)
1114	if err != nil {
1115		log.Printf("error changing password: %s", err)
1116	}
1117	http.Redirect(w, r, "/account", http.StatusSeeOther)
1118}
1119
1120func fingerlicker(w http.ResponseWriter, r *http.Request) {
1121	orig := r.FormValue("resource")
1122
1123	log.Printf("finger lick: %s", orig)
1124
1125	if strings.HasPrefix(orig, "acct:") {
1126		orig = orig[5:]
1127	}
1128
1129	name := orig
1130	idx := strings.LastIndexByte(name, '/')
1131	if idx != -1 {
1132		name = name[idx+1:]
1133		if "https://"+serverName+"/u/"+name != orig {
1134			log.Printf("foreign request rejected")
1135			name = ""
1136		}
1137	} else {
1138		idx = strings.IndexByte(name, '@')
1139		if idx != -1 {
1140			name = name[:idx]
1141			if name+"@"+serverName != orig {
1142				log.Printf("foreign request rejected")
1143				name = ""
1144			}
1145		}
1146	}
1147	user, err := butwhatabout(name)
1148	if err != nil {
1149		http.NotFound(w, r)
1150		return
1151	}
1152
1153	j := NewJunk()
1154	j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1155	j["aliases"] = []string{user.URL}
1156	var links []map[string]interface{}
1157	l := NewJunk()
1158	l["rel"] = "self"
1159	l["type"] = `application/activity+json`
1160	l["href"] = user.URL
1161	links = append(links, l)
1162	j["links"] = links
1163
1164	w.Header().Set("Cache-Control", "max-age=3600")
1165	w.Header().Set("Content-Type", "application/jrd+json")
1166	WriteJunk(w, j)
1167}
1168
1169func somedays() string {
1170	secs := 432000 + notrand.Int63n(432000)
1171	return fmt.Sprintf("%d", secs)
1172}
1173
1174func avatate(w http.ResponseWriter, r *http.Request) {
1175	n := r.FormValue("a")
1176	a := avatar(n)
1177	w.Header().Set("Cache-Control", "max-age="+somedays())
1178	w.Write(a)
1179}
1180
1181func servecss(w http.ResponseWriter, r *http.Request) {
1182	w.Header().Set("Cache-Control", "max-age=7776000")
1183	http.ServeFile(w, r, "views"+r.URL.Path)
1184}
1185func servehtml(w http.ResponseWriter, r *http.Request) {
1186	templinfo := getInfo(r)
1187	err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1188	if err != nil {
1189		log.Print(err)
1190	}
1191}
1192func serveemu(w http.ResponseWriter, r *http.Request) {
1193	xid := mux.Vars(r)["xid"]
1194	w.Header().Set("Cache-Control", "max-age="+somedays())
1195	http.ServeFile(w, r, "emus/"+xid)
1196}
1197
1198func servefile(w http.ResponseWriter, r *http.Request) {
1199	xid := mux.Vars(r)["xid"]
1200	row := stmtFileData.QueryRow(xid)
1201	var media string
1202	var data []byte
1203	err := row.Scan(&media, &data)
1204	if err != nil {
1205		log.Printf("error loading file: %s", err)
1206		http.NotFound(w, r)
1207		return
1208	}
1209	w.Header().Set("Content-Type", media)
1210	w.Header().Set("X-Content-Type-Options", "nosniff")
1211	w.Header().Set("Cache-Control", "max-age="+somedays())
1212	w.Write(data)
1213}
1214
1215func serve() {
1216	db := opendatabase()
1217	login.Init(db)
1218
1219	listener, err := openListener()
1220	if err != nil {
1221		log.Fatal(err)
1222	}
1223	go redeliverator()
1224
1225	debug := false
1226	getconfig("debug", &debug)
1227	readviews = templates.Load(debug,
1228		"views/honkpage.html",
1229		"views/honkers.html",
1230		"views/zonkers.html",
1231		"views/combos.html",
1232		"views/honkform.html",
1233		"views/honk.html",
1234		"views/account.html",
1235		"views/login.html",
1236		"views/header.html",
1237	)
1238	if !debug {
1239		s := "views/style.css"
1240		savedstyleparams[s] = getstyleparam(s)
1241		s = "views/local.css"
1242		savedstyleparams[s] = getstyleparam(s)
1243	}
1244
1245	bitethethumbs()
1246
1247	mux := mux.NewRouter()
1248	mux.Use(login.Checker)
1249
1250	posters := mux.Methods("POST").Subrouter()
1251	getters := mux.Methods("GET").Subrouter()
1252
1253	getters.HandleFunc("/", homepage)
1254	getters.HandleFunc("/rss", showrss)
1255	getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1256	getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1257	getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1258	posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1259	getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1260	getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1261	getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1262	getters.HandleFunc("/a", avatate)
1263	getters.HandleFunc("/t", showconvoy)
1264	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1265	getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1266	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1267
1268	getters.HandleFunc("/style.css", servecss)
1269	getters.HandleFunc("/local.css", servecss)
1270	getters.HandleFunc("/login", servehtml)
1271	posters.HandleFunc("/dologin", login.LoginFunc)
1272	getters.HandleFunc("/logout", login.LogoutFunc)
1273
1274	loggedin := mux.NewRoute().Subrouter()
1275	loggedin.Use(login.Required)
1276	loggedin.HandleFunc("/account", accountpage)
1277	loggedin.HandleFunc("/chpass", dochpass)
1278	loggedin.HandleFunc("/atme", homepage)
1279	loggedin.HandleFunc("/killzone", killzone)
1280	loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1281	loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1282	loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1283	loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1284	loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1285	loggedin.HandleFunc("/honkers", showhonkers)
1286	loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1287	loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1288	loggedin.HandleFunc("/c", showcombos)
1289	loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1290
1291	err = http.Serve(listener, mux)
1292	if err != nil {
1293		log.Fatal(err)
1294	}
1295}
1296
1297var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1298var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1299var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1300var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1301var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1302var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1303var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1304var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1305
1306func preparetodie(db *sql.DB, s string) *sql.Stmt {
1307	stmt, err := db.Prepare(s)
1308	if err != nil {
1309		log.Fatalf("error %s: %s", err, s)
1310	}
1311	return stmt
1312}
1313
1314func prepareStatements(db *sql.DB) {
1315	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")
1316	stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1317	stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1318	stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1319	stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1320	stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1321
1322	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 "
1323	limit := " order by honkid desc limit 250"
1324	butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1325	stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1326	stmtPublicHonks = preparetodie(db, selecthonks+"where honker = '' and dt > ?"+limit)
1327	stmtUserHonks = preparetodie(db, selecthonks+"where honker = '' and username = ? and dt > ?"+limit)
1328	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)
1329	stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1330	stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1331	stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1332	stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or honker = '') and convoy = ?"+limit)
1333
1334	stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1335	stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1336	stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1337	stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1338	stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1339	stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1340	stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1341	stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1342	stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1343	stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1344	stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1345	stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1346	stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1347	stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1348	stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1349	stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1350	stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1351	stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1352}
1353
1354func ElaborateUnitTests() {
1355}
1356
1357func finishusersetup() error {
1358	db := opendatabase()
1359	k, err := rsa.GenerateKey(rand.Reader, 2048)
1360	if err != nil {
1361		return err
1362	}
1363	pubkey, err := zem(&k.PublicKey)
1364	if err != nil {
1365		return err
1366	}
1367	seckey, err := zem(k)
1368	if err != nil {
1369		return err
1370	}
1371	_, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1372	if err != nil {
1373		return err
1374	}
1375	return nil
1376}
1377
1378func main() {
1379	cmd := "run"
1380	if len(os.Args) > 1 {
1381		cmd = os.Args[1]
1382	}
1383	switch cmd {
1384	case "init":
1385		initdb()
1386	case "upgrade":
1387		upgradedb()
1388	}
1389	db := opendatabase()
1390	dbversion := 0
1391	getconfig("dbversion", &dbversion)
1392	if dbversion != myVersion {
1393		log.Fatal("incorrect database version. run upgrade.")
1394	}
1395	getconfig("servername", &serverName)
1396	prepareStatements(db)
1397	switch cmd {
1398	case "ping":
1399		if len(os.Args) < 4 {
1400			fmt.Printf("usage: honk ping from to\n")
1401			return
1402		}
1403		name := os.Args[2]
1404		targ := os.Args[3]
1405		user, err := butwhatabout(name)
1406		if err != nil {
1407			log.Printf("unknown user")
1408			return
1409		}
1410		ping(user, targ)
1411	case "peep":
1412		peeppeep()
1413	case "run":
1414		serve()
1415	case "test":
1416		ElaborateUnitTests()
1417	default:
1418		log.Fatal("unknown command")
1419	}
1420}