all repos — honk @ 09cf39892dbea47518d42ae7400f0923c4b3eb03

my fork of honk

honk.go (view raw)

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