all repos — honk @ 6ee82b3133d0ed1c5876c6fc3178da7985caa61d

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