all repos — honk @ a16ffbfb7006fd366bfb38591e6b674c745f9ca1

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