all repos — honk @ 5a087a56b5a46f56d630a28242de1e711b76bfd7

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