all repos — honk @ 1957a2e8cd08775eb299ad2d4456fed36d20f2ba

my fork of honk

database.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/sha512"
  21	"database/sql"
  22	_ "embed"
  23	"encoding/json"
  24	"fmt"
  25	"html/template"
  26	"sort"
  27	"strconv"
  28	"strings"
  29	"sync"
  30	"time"
  31
  32	"humungus.tedunangst.com/r/webs/cache"
  33	"humungus.tedunangst.com/r/webs/httpsig"
  34	"humungus.tedunangst.com/r/webs/login"
  35	"humungus.tedunangst.com/r/webs/mz"
  36)
  37
  38//go:embed schema.sql
  39var sqlSchema string
  40
  41func userfromrow(row *sql.Row) (*WhatAbout, error) {
  42	user := new(WhatAbout)
  43	var seckey, options string
  44	err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key, &seckey, &options)
  45	if err == nil {
  46		user.SecKey, _, err = httpsig.DecodeKey(seckey)
  47	}
  48	if err != nil {
  49		return nil, err
  50	}
  51	if user.ID > 0 {
  52		user.URL = fmt.Sprintf("https://%s/%s/%s", serverName, userSep, user.Name)
  53		err = unjsonify(options, &user.Options)
  54		if err != nil {
  55			elog.Printf("error processing user options: %s", err)
  56		}
  57	} else {
  58		user.URL = fmt.Sprintf("https://%s/%s", serverName, user.Name)
  59	}
  60	if user.Options.Reaction == "" {
  61		user.Options.Reaction = "none"
  62	}
  63
  64	return user, nil
  65}
  66
  67var somenamedusers = cache.New(cache.Options{Filler: func(name string) (*WhatAbout, bool) {
  68	row := stmtUserByName.QueryRow(name)
  69	user, err := userfromrow(row)
  70	if err != nil {
  71		return nil, false
  72	}
  73	var marker mz.Marker
  74	marker.HashLinker = ontoreplacer
  75	marker.AtLinker = attoreplacer
  76	user.HTAbout = template.HTML(marker.Mark(user.About))
  77	user.Onts = marker.HashTags
  78	return user, true
  79}})
  80
  81var somenumberedusers = cache.New(cache.Options{Filler: func(userid int64) (*WhatAbout, bool) {
  82	row := stmtUserByNumber.QueryRow(userid)
  83	user, err := userfromrow(row)
  84	if err != nil {
  85		return nil, false
  86	}
  87	// don't touch attoreplacer, which introduces a loop
  88	// finger -> getjunk -> keys -> users
  89	return user, true
  90}})
  91
  92func getserveruser() *WhatAbout {
  93	var user *WhatAbout
  94	ok := somenumberedusers.Get(serverUID, &user)
  95	if !ok {
  96		elog.Panicf("lost server user")
  97	}
  98	return user
  99}
 100
 101func butwhatabout(name string) (*WhatAbout, error) {
 102	var user *WhatAbout
 103	ok := somenamedusers.Get(name, &user)
 104	if !ok {
 105		return nil, fmt.Errorf("no user: %s", name)
 106	}
 107	return user, nil
 108}
 109
 110var honkerinvalidator cache.Invalidator
 111
 112func gethonkers(userid int64) []*Honker {
 113	rows, err := stmtHonkers.Query(userid)
 114	if err != nil {
 115		elog.Printf("error querying honkers: %s", err)
 116		return nil
 117	}
 118	defer rows.Close()
 119	var honkers []*Honker
 120	for rows.Next() {
 121		h := new(Honker)
 122		var combos, meta string
 123		err = rows.Scan(&h.ID, &h.UserID, &h.Name, &h.XID, &h.Flavor, &combos, &meta)
 124		if err == nil {
 125			err = unjsonify(meta, &h.Meta)
 126		}
 127		if err != nil {
 128			elog.Printf("error scanning honker: %s", err)
 129			continue
 130		}
 131		h.Combos = strings.Split(strings.TrimSpace(combos), " ")
 132		honkers = append(honkers, h)
 133	}
 134	return honkers
 135}
 136
 137func getdubs(userid int64) []*Honker {
 138	rows, err := stmtDubbers.Query(userid)
 139	return dubsfromrows(rows, err)
 140}
 141
 142func getnameddubs(userid int64, name string) []*Honker {
 143	rows, err := stmtNamedDubbers.Query(userid, name)
 144	return dubsfromrows(rows, err)
 145}
 146
 147func dubsfromrows(rows *sql.Rows, err error) []*Honker {
 148	if err != nil {
 149		elog.Printf("error querying dubs: %s", err)
 150		return nil
 151	}
 152	defer rows.Close()
 153	var honkers []*Honker
 154	for rows.Next() {
 155		h := new(Honker)
 156		err = rows.Scan(&h.ID, &h.UserID, &h.Name, &h.XID, &h.Flavor)
 157		if err != nil {
 158			elog.Printf("error scanning honker: %s", err)
 159			return nil
 160		}
 161		honkers = append(honkers, h)
 162	}
 163	return honkers
 164}
 165
 166func allusers() []login.UserInfo {
 167	var users []login.UserInfo
 168	rows, _ := opendatabase().Query("select userid, username from users where userid > 0")
 169	defer rows.Close()
 170	for rows.Next() {
 171		var u login.UserInfo
 172		rows.Scan(&u.UserID, &u.Username)
 173		users = append(users, u)
 174	}
 175	return users
 176}
 177
 178func getxonk(userid int64, xid string) *Honk {
 179	row := stmtOneXonk.QueryRow(userid, xid)
 180	return scanhonk(row)
 181}
 182
 183func getbonk(userid int64, xid string) *Honk {
 184	row := stmtOneBonk.QueryRow(userid, xid)
 185	return scanhonk(row)
 186}
 187
 188func getpublichonks() []*Honk {
 189	dt := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(dbtimeformat)
 190	rows, err := stmtPublicHonks.Query(dt, 100)
 191	return getsomehonks(rows, err)
 192}
 193func geteventhonks(userid int64) []*Honk {
 194	rows, err := stmtEventHonks.Query(userid, 25)
 195	honks := getsomehonks(rows, err)
 196	sort.Slice(honks, func(i, j int) bool {
 197		var t1, t2 time.Time
 198		if honks[i].Time == nil {
 199			t1 = honks[i].Date
 200		} else {
 201			t1 = honks[i].Time.StartTime
 202		}
 203		if honks[j].Time == nil {
 204			t2 = honks[j].Date
 205		} else {
 206			t2 = honks[j].Time.StartTime
 207		}
 208		return t1.After(t2)
 209	})
 210	now := time.Now().Add(-24 * time.Hour)
 211	for i, h := range honks {
 212		t := h.Date
 213		if tm := h.Time; tm != nil {
 214			t = tm.StartTime
 215		}
 216		if t.Before(now) {
 217			honks = honks[:i]
 218			break
 219		}
 220	}
 221	reversehonks(honks)
 222	return honks
 223}
 224func gethonksbyuser(name string, includeprivate bool, wanted int64) []*Honk {
 225	dt := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(dbtimeformat)
 226	limit := 50
 227	whofore := 2
 228	if includeprivate {
 229		whofore = 3
 230	}
 231	rows, err := stmtUserHonks.Query(wanted, whofore, name, dt, limit)
 232	return getsomehonks(rows, err)
 233}
 234func gethonksforuser(userid int64, wanted int64) []*Honk {
 235	dt := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(dbtimeformat)
 236	rows, err := stmtHonksForUser.Query(wanted, userid, dt, userid, userid)
 237	return getsomehonks(rows, err)
 238}
 239func gethonksforuserfirstclass(userid int64, wanted int64) []*Honk {
 240	dt := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(dbtimeformat)
 241	rows, err := stmtHonksForUserFirstClass.Query(wanted, userid, dt, userid, userid)
 242	return getsomehonks(rows, err)
 243}
 244
 245func gethonksforme(userid int64, wanted int64) []*Honk {
 246	dt := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(dbtimeformat)
 247	rows, err := stmtHonksForMe.Query(wanted, userid, dt, userid)
 248	return getsomehonks(rows, err)
 249}
 250func gethonksfromlongago(userid int64, wanted int64) []*Honk {
 251	now := time.Now()
 252	var honks []*Honk
 253	for i := 1; i <= 3; i++ {
 254		dt := time.Date(now.Year()-i, now.Month(), now.Day(), now.Hour(), now.Minute(),
 255			now.Second(), 0, now.Location())
 256		dt1 := dt.Add(-36 * time.Hour).UTC().Format(dbtimeformat)
 257		dt2 := dt.Add(12 * time.Hour).UTC().Format(dbtimeformat)
 258		rows, err := stmtHonksFromLongAgo.Query(wanted, userid, dt1, dt2, userid)
 259		honks = append(honks, getsomehonks(rows, err)...)
 260	}
 261	return honks
 262}
 263func getsavedhonks(userid int64, wanted int64) []*Honk {
 264	rows, err := stmtHonksISaved.Query(wanted, userid)
 265	return getsomehonks(rows, err)
 266}
 267func gethonksbyhonker(userid int64, honker string, wanted int64) []*Honk {
 268	rows, err := stmtHonksByHonker.Query(wanted, userid, honker, userid)
 269	return getsomehonks(rows, err)
 270}
 271func gethonksbyxonker(userid int64, xonker string, wanted int64) []*Honk {
 272	rows, err := stmtHonksByXonker.Query(wanted, userid, xonker, xonker, userid)
 273	return getsomehonks(rows, err)
 274}
 275func gethonksbycombo(userid int64, combo string, wanted int64) []*Honk {
 276	combo = "% " + combo + " %"
 277	rows, err := stmtHonksByCombo.Query(wanted, userid, userid, combo, userid, wanted, userid, combo, userid)
 278	return getsomehonks(rows, err)
 279}
 280func gethonksbyconvoy(userid int64, convoy string, wanted int64) []*Honk {
 281	rows, err := stmtHonksByConvoy.Query(wanted, userid, userid, convoy)
 282	honks := getsomehonks(rows, err)
 283	return honks
 284}
 285func gethonksbysearch(userid int64, q string, wanted int64) []*Honk {
 286	var queries []string
 287	var params []interface{}
 288	queries = append(queries, "honks.honkid > ?")
 289	params = append(params, wanted)
 290	queries = append(queries, "honks.userid = ?")
 291	params = append(params, userid)
 292
 293	terms := strings.Split(q, " ")
 294	for _, t := range terms {
 295		if t == "" {
 296			continue
 297		}
 298		negate := " "
 299		if t[0] == '-' {
 300			t = t[1:]
 301			negate = " not "
 302		}
 303		if t == "" {
 304			continue
 305		}
 306		if strings.HasPrefix(t, "site:") {
 307			site := t[5:]
 308			site = "%" + site + "%"
 309			queries = append(queries, "xid"+negate+"like ?")
 310			params = append(params, site)
 311			continue
 312		}
 313		if strings.HasPrefix(t, "honker:") {
 314			honker := t[7:]
 315			xid := fullname(honker, userid)
 316			if xid != "" {
 317				honker = xid
 318			}
 319			queries = append(queries, negate+"(honks.honker = ? or honks.oonker = ?)")
 320			params = append(params, honker)
 321			params = append(params, honker)
 322			continue
 323		}
 324		t = "%" + t + "%"
 325		queries = append(queries, "noise"+negate+"like ?")
 326		params = append(params, t)
 327	}
 328
 329	selecthonks := "select honks.honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, format, convoy, whofore, flags from honks join users on honks.userid = users.userid "
 330	where := "where " + strings.Join(queries, " and ")
 331	butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
 332	limit := " order by honks.honkid desc limit 250"
 333	params = append(params, userid)
 334	rows, err := opendatabase().Query(selecthonks+where+butnotthose+limit, params...)
 335	honks := getsomehonks(rows, err)
 336	return honks
 337}
 338func gethonksbyontology(userid int64, name string, wanted int64) []*Honk {
 339	rows, err := stmtHonksByOntology.Query(wanted, name, userid, userid)
 340	honks := getsomehonks(rows, err)
 341	return honks
 342}
 343
 344func reversehonks(honks []*Honk) {
 345	for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
 346		honks[i], honks[j] = honks[j], honks[i]
 347	}
 348}
 349
 350func getsomehonks(rows *sql.Rows, err error) []*Honk {
 351	if err != nil {
 352		elog.Printf("error querying honks: %s", err)
 353		return nil
 354	}
 355	defer rows.Close()
 356	var honks []*Honk
 357	for rows.Next() {
 358		h := scanhonk(rows)
 359		if h != nil {
 360			honks = append(honks, h)
 361		}
 362	}
 363	rows.Close()
 364	donksforhonks(honks)
 365	return honks
 366}
 367
 368type RowLike interface {
 369	Scan(dest ...interface{}) error
 370}
 371
 372func scanhonk(row RowLike) *Honk {
 373	h := new(Honk)
 374	var dt, aud string
 375	err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
 376		&dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Format, &h.Convoy, &h.Whofore, &h.Flags)
 377	if err != nil {
 378		if err != sql.ErrNoRows {
 379			elog.Printf("error scanning honk: %s", err)
 380		}
 381		return nil
 382	}
 383	h.Date, _ = time.Parse(dbtimeformat, dt)
 384	h.Audience = strings.Split(aud, " ")
 385	h.Public = loudandproud(h.Audience)
 386	return h
 387}
 388
 389func donksforhonks(honks []*Honk) {
 390	db := opendatabase()
 391	var ids []string
 392	hmap := make(map[int64]*Honk)
 393	for _, h := range honks {
 394		ids = append(ids, fmt.Sprintf("%d", h.ID))
 395		hmap[h.ID] = h
 396	}
 397	idset := strings.Join(ids, ",")
 398	// grab donks
 399	q := fmt.Sprintf("select honkid, donks.fileid, xid, name, description, url, media, local from donks join filemeta on donks.fileid = filemeta.fileid where honkid in (%s)", idset)
 400	rows, err := db.Query(q)
 401	if err != nil {
 402		elog.Printf("error querying donks: %s", err)
 403		return
 404	}
 405	defer rows.Close()
 406	for rows.Next() {
 407		var hid int64
 408		d := new(Donk)
 409		err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.Desc, &d.URL, &d.Media, &d.Local)
 410		if err != nil {
 411			elog.Printf("error scanning donk: %s", err)
 412			continue
 413		}
 414		d.External = !strings.HasPrefix(d.URL, serverPrefix)
 415		h := hmap[hid]
 416		h.Donks = append(h.Donks, d)
 417	}
 418	rows.Close()
 419
 420	// grab onts
 421	q = fmt.Sprintf("select honkid, ontology from onts where honkid in (%s)", idset)
 422	rows, err = db.Query(q)
 423	if err != nil {
 424		elog.Printf("error querying onts: %s", err)
 425		return
 426	}
 427	defer rows.Close()
 428	for rows.Next() {
 429		var hid int64
 430		var o string
 431		err = rows.Scan(&hid, &o)
 432		if err != nil {
 433			elog.Printf("error scanning donk: %s", err)
 434			continue
 435		}
 436		h := hmap[hid]
 437		h.Onts = append(h.Onts, o)
 438	}
 439	rows.Close()
 440
 441	// grab meta
 442	q = fmt.Sprintf("select honkid, genus, json from honkmeta where honkid in (%s)", idset)
 443	rows, err = db.Query(q)
 444	if err != nil {
 445		elog.Printf("error querying honkmeta: %s", err)
 446		return
 447	}
 448	defer rows.Close()
 449	for rows.Next() {
 450		var hid int64
 451		var genus, j string
 452		err = rows.Scan(&hid, &genus, &j)
 453		if err != nil {
 454			elog.Printf("error scanning honkmeta: %s", err)
 455			continue
 456		}
 457		h := hmap[hid]
 458		switch genus {
 459		case "place":
 460			p := new(Place)
 461			err = unjsonify(j, p)
 462			if err != nil {
 463				elog.Printf("error parsing place: %s", err)
 464				continue
 465			}
 466			h.Place = p
 467		case "time":
 468			t := new(Time)
 469			err = unjsonify(j, t)
 470			if err != nil {
 471				elog.Printf("error parsing time: %s", err)
 472				continue
 473			}
 474			h.Time = t
 475		case "mentions":
 476			err = unjsonify(j, &h.Mentions)
 477			if err != nil {
 478				elog.Printf("error parsing mentions: %s", err)
 479				continue
 480			}
 481		case "badonks":
 482			err = unjsonify(j, &h.Badonks)
 483			if err != nil {
 484				elog.Printf("error parsing badonks: %s", err)
 485				continue
 486			}
 487		case "wonkles":
 488		case "guesses":
 489		case "oldrev":
 490		default:
 491			elog.Printf("unknown meta genus: %s", genus)
 492		}
 493	}
 494	rows.Close()
 495}
 496
 497func donksforchonks(chonks []*Chonk) {
 498	db := opendatabase()
 499	var ids []string
 500	chmap := make(map[int64]*Chonk)
 501	for _, ch := range chonks {
 502		ids = append(ids, fmt.Sprintf("%d", ch.ID))
 503		chmap[ch.ID] = ch
 504	}
 505	idset := strings.Join(ids, ",")
 506	// grab donks
 507	q := fmt.Sprintf("select chonkid, donks.fileid, xid, name, description, url, media, local from donks join filemeta on donks.fileid = filemeta.fileid where chonkid in (%s)", idset)
 508	rows, err := db.Query(q)
 509	if err != nil {
 510		elog.Printf("error querying donks: %s", err)
 511		return
 512	}
 513	defer rows.Close()
 514	for rows.Next() {
 515		var chid int64
 516		d := new(Donk)
 517		err = rows.Scan(&chid, &d.FileID, &d.XID, &d.Name, &d.Desc, &d.URL, &d.Media, &d.Local)
 518		if err != nil {
 519			elog.Printf("error scanning donk: %s", err)
 520			continue
 521		}
 522		ch := chmap[chid]
 523		ch.Donks = append(ch.Donks, d)
 524	}
 525}
 526
 527func savefile(name string, desc string, url string, media string, local bool, data []byte) (int64, error) {
 528	fileid, _, err := savefileandxid(name, desc, url, media, local, data)
 529	return fileid, err
 530}
 531
 532func hashfiledata(data []byte) string {
 533	h := sha512.New512_256()
 534	h.Write(data)
 535	return fmt.Sprintf("%x", h.Sum(nil))
 536}
 537
 538func savefileandxid(name string, desc string, url string, media string, local bool, data []byte) (int64, string, error) {
 539	var xid string
 540	if local {
 541		hash := hashfiledata(data)
 542		row := stmtCheckFileData.QueryRow(hash)
 543		err := row.Scan(&xid)
 544		if err == sql.ErrNoRows {
 545			xid = xfiltrate()
 546			switch media {
 547			case "image/png":
 548				xid += ".png"
 549			case "image/jpeg":
 550				xid += ".jpg"
 551			case "application/pdf":
 552				xid += ".pdf"
 553			case "text/plain":
 554				xid += ".txt"
 555			}
 556			_, err = stmtSaveFileData.Exec(xid, media, hash, data)
 557			if err != nil {
 558				return 0, "", err
 559			}
 560		} else if err != nil {
 561			elog.Printf("error checking file hash: %s", err)
 562			return 0, "", err
 563		}
 564		if url == "" {
 565			url = fmt.Sprintf("https://%s/d/%s", serverName, xid)
 566		}
 567	}
 568
 569	res, err := stmtSaveFile.Exec(xid, name, desc, url, media, local)
 570	if err != nil {
 571		return 0, "", err
 572	}
 573	fileid, _ := res.LastInsertId()
 574	return fileid, xid, nil
 575}
 576
 577func finddonkid(fileid int64, url string) *Donk {
 578	donk := new(Donk)
 579	row := stmtFindFileId.QueryRow(fileid, url)
 580	err := row.Scan(&donk.XID, &donk.Local, &donk.Desc)
 581	if err == nil {
 582		donk.FileID = fileid
 583		return donk
 584	}
 585	if err != sql.ErrNoRows {
 586		elog.Printf("error finding file: %s", err)
 587	}
 588	return nil
 589}
 590
 591func finddonk(url string) *Donk {
 592	donk := new(Donk)
 593	row := stmtFindFile.QueryRow(url)
 594	err := row.Scan(&donk.FileID, &donk.XID)
 595	if err == nil {
 596		return donk
 597	}
 598	if err != sql.ErrNoRows {
 599		elog.Printf("error finding file: %s", err)
 600	}
 601	return nil
 602}
 603
 604func savechonk(ch *Chonk) error {
 605	dt := ch.Date.UTC().Format(dbtimeformat)
 606	db := opendatabase()
 607	tx, err := db.Begin()
 608	if err != nil {
 609		elog.Printf("can't begin tx: %s", err)
 610		return err
 611	}
 612
 613	res, err := tx.Stmt(stmtSaveChonk).Exec(ch.UserID, ch.XID, ch.Who, ch.Target, dt, ch.Noise, ch.Format)
 614	if err == nil {
 615		ch.ID, _ = res.LastInsertId()
 616		for _, d := range ch.Donks {
 617			_, err := tx.Stmt(stmtSaveDonk).Exec(-1, ch.ID, d.FileID)
 618			if err != nil {
 619				elog.Printf("error saving donk: %s", err)
 620				break
 621			}
 622		}
 623		chatplusone(tx, ch.UserID)
 624		err = tx.Commit()
 625	} else {
 626		tx.Rollback()
 627	}
 628	return err
 629}
 630
 631func chatplusone(tx *sql.Tx, userid int64) {
 632	var user *WhatAbout
 633	ok := somenumberedusers.Get(userid, &user)
 634	if !ok {
 635		return
 636	}
 637	options := user.Options
 638	options.ChatCount += 1
 639	j, err := jsonify(options)
 640	if err == nil {
 641		_, err = tx.Exec("update users set options = ? where username = ?", j, user.Name)
 642	}
 643	if err != nil {
 644		elog.Printf("error plussing chat: %s", err)
 645	}
 646	somenamedusers.Clear(user.Name)
 647	somenumberedusers.Clear(user.ID)
 648}
 649
 650func chatnewnone(userid int64) {
 651	var user *WhatAbout
 652	ok := somenumberedusers.Get(userid, &user)
 653	if !ok || user.Options.ChatCount == 0 {
 654		return
 655	}
 656	options := user.Options
 657	options.ChatCount = 0
 658	j, err := jsonify(options)
 659	if err == nil {
 660		db := opendatabase()
 661		_, err = db.Exec("update users set options = ? where username = ?", j, user.Name)
 662	}
 663	if err != nil {
 664		elog.Printf("error noneing chat: %s", err)
 665	}
 666	somenamedusers.Clear(user.Name)
 667	somenumberedusers.Clear(user.ID)
 668}
 669
 670func meplusone(tx *sql.Tx, userid int64) {
 671	var user *WhatAbout
 672	ok := somenumberedusers.Get(userid, &user)
 673	if !ok {
 674		return
 675	}
 676	options := user.Options
 677	options.MeCount += 1
 678	j, err := jsonify(options)
 679	if err == nil {
 680		_, err = tx.Exec("update users set options = ? where username = ?", j, user.Name)
 681	}
 682	if err != nil {
 683		elog.Printf("error plussing me: %s", err)
 684	}
 685	somenamedusers.Clear(user.Name)
 686	somenumberedusers.Clear(user.ID)
 687}
 688
 689func menewnone(userid int64) {
 690	var user *WhatAbout
 691	ok := somenumberedusers.Get(userid, &user)
 692	if !ok || user.Options.MeCount == 0 {
 693		return
 694	}
 695	options := user.Options
 696	options.MeCount = 0
 697	j, err := jsonify(options)
 698	if err == nil {
 699		db := opendatabase()
 700		_, err = db.Exec("update users set options = ? where username = ?", j, user.Name)
 701	}
 702	if err != nil {
 703		elog.Printf("error noneing me: %s", err)
 704	}
 705	somenamedusers.Clear(user.Name)
 706	somenumberedusers.Clear(user.ID)
 707}
 708
 709func loadchatter(userid int64) []*Chatter {
 710	duedt := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
 711	rows, err := stmtLoadChonks.Query(userid, duedt)
 712	if err != nil {
 713		elog.Printf("error loading chonks: %s", err)
 714		return nil
 715	}
 716	defer rows.Close()
 717	chonks := make(map[string][]*Chonk)
 718	var allchonks []*Chonk
 719	for rows.Next() {
 720		ch := new(Chonk)
 721		var dt string
 722		err = rows.Scan(&ch.ID, &ch.UserID, &ch.XID, &ch.Who, &ch.Target, &dt, &ch.Noise, &ch.Format)
 723		if err != nil {
 724			elog.Printf("error scanning chonk: %s", err)
 725			continue
 726		}
 727		ch.Date, _ = time.Parse(dbtimeformat, dt)
 728		chonks[ch.Target] = append(chonks[ch.Target], ch)
 729		allchonks = append(allchonks, ch)
 730	}
 731	donksforchonks(allchonks)
 732	rows.Close()
 733	rows, err = stmtGetChatters.Query(userid)
 734	if err != nil {
 735		elog.Printf("error getting chatters: %s", err)
 736		return nil
 737	}
 738	for rows.Next() {
 739		var target string
 740		err = rows.Scan(&target)
 741		if err != nil {
 742			elog.Printf("error scanning chatter: %s", target)
 743			continue
 744		}
 745		if _, ok := chonks[target]; !ok {
 746			chonks[target] = []*Chonk{}
 747
 748		}
 749	}
 750	var chatter []*Chatter
 751	for target, chonks := range chonks {
 752		chatter = append(chatter, &Chatter{
 753			Target: target,
 754			Chonks: chonks,
 755		})
 756	}
 757	sort.Slice(chatter, func(i, j int) bool {
 758		a, b := chatter[i], chatter[j]
 759		if len(a.Chonks) == 0 || len(b.Chonks) == 0 {
 760			if len(a.Chonks) == len(b.Chonks) {
 761				return a.Target < b.Target
 762			}
 763			return len(a.Chonks) > len(b.Chonks)
 764		}
 765		return a.Chonks[len(a.Chonks)-1].Date.After(b.Chonks[len(b.Chonks)-1].Date)
 766	})
 767
 768	return chatter
 769}
 770
 771func savehonk(h *Honk) error {
 772	dt := h.Date.UTC().Format(dbtimeformat)
 773	aud := strings.Join(h.Audience, " ")
 774
 775	db := opendatabase()
 776	tx, err := db.Begin()
 777	if err != nil {
 778		elog.Printf("can't begin tx: %s", err)
 779		return err
 780	}
 781
 782	res, err := tx.Stmt(stmtSaveHonk).Exec(h.UserID, h.What, h.Honker, h.XID, h.RID, dt, h.URL,
 783		aud, h.Noise, h.Convoy, h.Whofore, h.Format, h.Precis,
 784		h.Oonker, h.Flags)
 785	if err == nil {
 786		h.ID, _ = res.LastInsertId()
 787		err = saveextras(tx, h)
 788	}
 789	if err == nil {
 790		if h.Whofore == 1 {
 791			meplusone(tx, h.UserID)
 792		}
 793		err = tx.Commit()
 794	} else {
 795		tx.Rollback()
 796	}
 797	if err != nil {
 798		elog.Printf("error saving honk: %s", err)
 799	}
 800	honkhonkline()
 801	return err
 802}
 803
 804func updatehonk(h *Honk) error {
 805	old := getxonk(h.UserID, h.XID)
 806	oldrev := OldRevision{Precis: old.Precis, Noise: old.Noise}
 807	dt := h.Date.UTC().Format(dbtimeformat)
 808
 809	db := opendatabase()
 810	tx, err := db.Begin()
 811	if err != nil {
 812		elog.Printf("can't begin tx: %s", err)
 813		return err
 814	}
 815
 816	err = deleteextras(tx, h.ID, false)
 817	if err == nil {
 818		_, err = tx.Stmt(stmtUpdateHonk).Exec(h.Precis, h.Noise, h.Format, h.Whofore, dt, h.ID)
 819	}
 820	if err == nil {
 821		err = saveextras(tx, h)
 822	}
 823	if err == nil {
 824		var j string
 825		j, err = jsonify(&oldrev)
 826		if err == nil {
 827			_, err = tx.Stmt(stmtSaveMeta).Exec(old.ID, "oldrev", j)
 828		}
 829		if err != nil {
 830			elog.Printf("error saving oldrev: %s", err)
 831		}
 832	}
 833	if err == nil {
 834		err = tx.Commit()
 835	} else {
 836		tx.Rollback()
 837	}
 838	if err != nil {
 839		elog.Printf("error updating honk %d: %s", h.ID, err)
 840	}
 841	return err
 842}
 843
 844func deletehonk(honkid int64) error {
 845	db := opendatabase()
 846	tx, err := db.Begin()
 847	if err != nil {
 848		elog.Printf("can't begin tx: %s", err)
 849		return err
 850	}
 851
 852	err = deleteextras(tx, honkid, true)
 853	if err == nil {
 854		_, err = tx.Stmt(stmtDeleteHonk).Exec(honkid)
 855	}
 856	if err == nil {
 857		err = tx.Commit()
 858	} else {
 859		tx.Rollback()
 860	}
 861	if err != nil {
 862		elog.Printf("error deleting honk %d: %s", honkid, err)
 863	}
 864	return err
 865}
 866
 867func saveextras(tx *sql.Tx, h *Honk) error {
 868	for _, d := range h.Donks {
 869		_, err := tx.Stmt(stmtSaveDonk).Exec(h.ID, -1, d.FileID)
 870		if err != nil {
 871			elog.Printf("error saving donk: %s", err)
 872			return err
 873		}
 874	}
 875	for _, o := range h.Onts {
 876		_, err := tx.Stmt(stmtSaveOnt).Exec(strings.ToLower(o), h.ID)
 877		if err != nil {
 878			elog.Printf("error saving ont: %s", err)
 879			return err
 880		}
 881	}
 882	if p := h.Place; p != nil {
 883		j, err := jsonify(p)
 884		if err == nil {
 885			_, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "place", j)
 886		}
 887		if err != nil {
 888			elog.Printf("error saving place: %s", err)
 889			return err
 890		}
 891	}
 892	if t := h.Time; t != nil {
 893		j, err := jsonify(t)
 894		if err == nil {
 895			_, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "time", j)
 896		}
 897		if err != nil {
 898			elog.Printf("error saving time: %s", err)
 899			return err
 900		}
 901	}
 902	if m := h.Mentions; len(m) > 0 {
 903		j, err := jsonify(m)
 904		if err == nil {
 905			_, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "mentions", j)
 906		}
 907		if err != nil {
 908			elog.Printf("error saving mentions: %s", err)
 909			return err
 910		}
 911	}
 912	return nil
 913}
 914
 915var baxonker sync.Mutex
 916
 917func addreaction(user *WhatAbout, xid string, who, react string) {
 918	baxonker.Lock()
 919	defer baxonker.Unlock()
 920	h := getxonk(user.ID, xid)
 921	if h == nil {
 922		return
 923	}
 924	h.Badonks = append(h.Badonks, Badonk{Who: who, What: react})
 925	j, _ := jsonify(h.Badonks)
 926	db := opendatabase()
 927	tx, _ := db.Begin()
 928	_, _ = tx.Stmt(stmtDeleteOneMeta).Exec(h.ID, "badonks")
 929	_, _ = tx.Stmt(stmtSaveMeta).Exec(h.ID, "badonks", j)
 930	tx.Commit()
 931}
 932
 933func deleteextras(tx *sql.Tx, honkid int64, everything bool) error {
 934	_, err := tx.Stmt(stmtDeleteDonks).Exec(honkid)
 935	if err != nil {
 936		return err
 937	}
 938	_, err = tx.Stmt(stmtDeleteOnts).Exec(honkid)
 939	if err != nil {
 940		return err
 941	}
 942	if everything {
 943		_, err = tx.Stmt(stmtDeleteAllMeta).Exec(honkid)
 944	} else {
 945		_, err = tx.Stmt(stmtDeleteSomeMeta).Exec(honkid)
 946	}
 947	if err != nil {
 948		return err
 949	}
 950	return nil
 951}
 952
 953func jsonify(what interface{}) (string, error) {
 954	var buf bytes.Buffer
 955	e := json.NewEncoder(&buf)
 956	e.SetEscapeHTML(false)
 957	e.SetIndent("", "")
 958	err := e.Encode(what)
 959	return buf.String(), err
 960}
 961
 962func unjsonify(s string, dest interface{}) error {
 963	d := json.NewDecoder(strings.NewReader(s))
 964	err := d.Decode(dest)
 965	return err
 966}
 967
 968func getxonker(what, flav string) string {
 969	var res string
 970	row := stmtGetXonker.QueryRow(what, flav)
 971	row.Scan(&res)
 972	return res
 973}
 974
 975func savexonker(what, value, flav, when string) {
 976	stmtSaveXonker.Exec(what, value, flav, when)
 977}
 978
 979func savehonker(user *WhatAbout, url, name, flavor, combos, mj string) (int64, error) {
 980	var owner string
 981	if url[0] == '#' {
 982		flavor = "peep"
 983		if name == "" {
 984			name = url[1:]
 985		}
 986		owner = url
 987	} else {
 988		info, err := investigate(url)
 989		if err != nil {
 990			ilog.Printf("failed to investigate honker: %s", err)
 991			return 0, err
 992		}
 993		url = info.XID
 994		if name == "" {
 995			name = info.Name
 996		}
 997		owner = info.Owner
 998	}
 999
1000	var x string
1001	db := opendatabase()
1002	row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('sub', 'unsub', 'peep')", url, user.ID)
1003	err := row.Scan(&x)
1004	if err != sql.ErrNoRows {
1005		if err != nil {
1006			elog.Printf("honker scan err: %s", err)
1007		} else {
1008			err = fmt.Errorf("it seems you are already subscribed to them")
1009		}
1010		return 0, err
1011	}
1012
1013	res, err := stmtSaveHonker.Exec(user.ID, name, url, flavor, combos, owner, mj)
1014	if err != nil {
1015		elog.Print(err)
1016		return 0, err
1017	}
1018	honkerid, _ := res.LastInsertId()
1019	return honkerid, nil
1020}
1021
1022func cleanupdb(arg string) {
1023	db := opendatabase()
1024	days, err := strconv.Atoi(arg)
1025	var sqlargs []interface{}
1026	var where string
1027	if err != nil {
1028		honker := arg
1029		expdate := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
1030		where = "dt < ? and honker = ?"
1031		sqlargs = append(sqlargs, expdate)
1032		sqlargs = append(sqlargs, honker)
1033	} else {
1034		expdate := time.Now().Add(-time.Duration(days) * 24 * time.Hour).UTC().Format(dbtimeformat)
1035		where = "dt < ? and convoy not in (select convoy from honks where flags & 4 or whofore = 2 or whofore = 3)"
1036		sqlargs = append(sqlargs, expdate)
1037	}
1038	doordie(db, "delete from honks where flags & 4 = 0 and whofore = 0 and "+where, sqlargs...)
1039	doordie(db, "delete from donks where honkid > 0 and honkid not in (select honkid from honks)")
1040	doordie(db, "delete from onts where honkid not in (select honkid from honks)")
1041	doordie(db, "delete from honkmeta where honkid not in (select honkid from honks)")
1042
1043	doordie(db, "delete from filemeta where fileid not in (select fileid from donks)")
1044	for _, u := range allusers() {
1045		doordie(db, "delete from zonkers where userid = ? and wherefore = 'zonvoy' and zonkerid < (select zonkerid from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 1 offset 200)", u.UserID, u.UserID)
1046	}
1047
1048	filexids := make(map[string]bool)
1049	blobdb := openblobdb()
1050	rows, err := blobdb.Query("select xid from filedata")
1051	if err != nil {
1052		elog.Fatal(err)
1053	}
1054	for rows.Next() {
1055		var xid string
1056		err = rows.Scan(&xid)
1057		if err != nil {
1058			elog.Fatal(err)
1059		}
1060		filexids[xid] = true
1061	}
1062	rows.Close()
1063	rows, err = db.Query("select xid from filemeta")
1064	for rows.Next() {
1065		var xid string
1066		err = rows.Scan(&xid)
1067		if err != nil {
1068			elog.Fatal(err)
1069		}
1070		delete(filexids, xid)
1071	}
1072	rows.Close()
1073	tx, err := blobdb.Begin()
1074	if err != nil {
1075		elog.Fatal(err)
1076	}
1077	for xid, _ := range filexids {
1078		_, err = tx.Exec("delete from filedata where xid = ?", xid)
1079		if err != nil {
1080			elog.Fatal(err)
1081		}
1082	}
1083	err = tx.Commit()
1084	if err != nil {
1085		elog.Fatal(err)
1086	}
1087}
1088
1089var stmtHonkers, stmtDubbers, stmtNamedDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateHonker *sql.Stmt
1090var stmtDeleteHonker *sql.Stmt
1091var stmtAnyXonk, stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1092var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1093var stmtHonksFromLongAgo *sql.Stmt
1094var stmtHonksByHonker, stmtSaveHonk, stmtUserByName, stmtUserByNumber *sql.Stmt
1095var stmtEventHonks, stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk *sql.Stmt
1096var stmtFindFile, stmtFindFileId, stmtGetFileData, stmtSaveFileData, stmtSaveFile *sql.Stmt
1097var stmtCheckFileData *sql.Stmt
1098var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover, stmtOneHonker *sql.Stmt
1099var stmtUntagged, stmtDeleteHonk, stmtDeleteDonks, stmtDeleteOnts, stmtSaveZonker *sql.Stmt
1100var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker, stmtDeleteOldXonkers *sql.Stmt
1101var stmtAllOnts, stmtSaveOnt, stmtUpdateFlags, stmtClearFlags *sql.Stmt
1102var stmtHonksForUserFirstClass *sql.Stmt
1103var stmtSaveMeta, stmtDeleteAllMeta, stmtDeleteOneMeta, stmtDeleteSomeMeta, stmtUpdateHonk *sql.Stmt
1104var stmtHonksISaved, stmtGetFilters, stmtSaveFilter, stmtDeleteFilter *sql.Stmt
1105var stmtGetTracks *sql.Stmt
1106var stmtSaveChonk, stmtLoadChonks, stmtGetChatters *sql.Stmt
1107
1108func preparetodie(db *sql.DB, s string) *sql.Stmt {
1109	stmt, err := db.Prepare(s)
1110	if err != nil {
1111		elog.Fatalf("error %s: %s", err, s)
1112	}
1113	return stmt
1114}
1115
1116func prepareStatements(db *sql.DB) {
1117	stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor, combos, meta from honkers where userid = ? and (flavor = 'presub' or flavor = 'sub' or flavor = 'peep' or flavor = 'unsub') order by name")
1118	stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner, meta, folxid) values (?, ?, ?, ?, ?, ?, ?, '')")
1119	stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ?, folxid = ? where userid = ? and name = ? and xid = ? and flavor = ?")
1120	stmtUpdateHonker = preparetodie(db, "update honkers set name = ?, combos = ?, meta = ? where honkerid = ? and userid = ?")
1121	stmtDeleteHonker = preparetodie(db, "delete from honkers where honkerid = ?")
1122	stmtOneHonker = preparetodie(db, "select xid from honkers where name = ? and userid = ?")
1123	stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1124	stmtNamedDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and name = ? and flavor = 'dub'")
1125
1126	selecthonks := "select honks.honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, format, convoy, whofore, flags from honks join users on honks.userid = users.userid "
1127	limit := " order by honks.honkid desc limit 250"
1128	smalllimit := " order by honks.honkid desc limit ?"
1129	butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1130	stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1131	stmtAnyXonk = preparetodie(db, selecthonks+"where xid = ? order by honks.honkid asc")
1132	stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
1133	stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+smalllimit)
1134	stmtEventHonks = preparetodie(db, selecthonks+"where (whofore = 2 or honks.userid = ?) and what = 'event'"+smalllimit)
1135	stmtUserHonks = preparetodie(db, selecthonks+"where honks.honkid > ? and (whofore = 2 or whofore = ?) and username = ? and dt > ?"+smalllimit)
1136	myhonkers := " and honker in (select xid from honkers where userid = ? and (flavor = 'sub' or flavor = 'peep' or flavor = 'presub') and combos not like '% - %')"
1137	stmtHonksForUser = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ?"+myhonkers+butnotthose+limit)
1138	stmtHonksForUserFirstClass = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and (what <> 'tonk')"+myhonkers+butnotthose+limit)
1139	stmtHonksForMe = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1140	stmtHonksFromLongAgo = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and dt < ? and whofore = 2"+butnotthose+limit)
1141	stmtHonksISaved = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and flags & 4 order by honks.honkid desc")
1142	stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.honkid > ? and honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1143	stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.honkid > ? and honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1144	stmtHonksByCombo = preparetodie(db, selecthonks+" where honks.honkid > ? and honks.userid = ? and honks.honker in (select xid from honkers where honkers.userid = ? and honkers.combos like ?) "+butnotthose+" union "+selecthonks+"join onts on honks.honkid = onts.honkid where honks.honkid > ? and honks.userid = ? and onts.ontology in (select xid from honkers where combos like ?)"+butnotthose+limit)
1145	stmtHonksByConvoy = preparetodie(db, selecthonks+"where honks.honkid > ? and (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1146	stmtHonksByOntology = preparetodie(db, selecthonks+"join onts on honks.honkid = onts.honkid where honks.honkid > ? and onts.ontology = ? and (honks.userid = ? or (? = -1 and honks.whofore = 2))"+limit)
1147
1148	stmtSaveMeta = preparetodie(db, "insert into honkmeta (honkid, genus, json) values (?, ?, ?)")
1149	stmtDeleteAllMeta = preparetodie(db, "delete from honkmeta where honkid = ?")
1150	stmtDeleteSomeMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus not in ('oldrev')")
1151	stmtDeleteOneMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus = ?")
1152	stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1153	stmtDeleteHonk = preparetodie(db, "delete from honks where honkid = ?")
1154	stmtUpdateHonk = preparetodie(db, "update honks set precis = ?, noise = ?, format = ?, whofore = ?, dt = ? where honkid = ?")
1155	stmtSaveOnt = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
1156	stmtDeleteOnts = preparetodie(db, "delete from onts where honkid = ?")
1157	stmtSaveDonk = preparetodie(db, "insert into donks (honkid, chonkid, fileid) values (?, ?, ?)")
1158	stmtDeleteDonks = preparetodie(db, "delete from donks where honkid = ?")
1159	stmtSaveFile = preparetodie(db, "insert into filemeta (xid, name, description, url, media, local) values (?, ?, ?, ?, ?, ?)")
1160	blobdb := openblobdb()
1161	stmtSaveFileData = preparetodie(blobdb, "insert into filedata (xid, media, hash, content) values (?, ?, ?, ?)")
1162	stmtCheckFileData = preparetodie(blobdb, "select xid from filedata where hash = ?")
1163	stmtGetFileData = preparetodie(blobdb, "select media, content from filedata where xid = ?")
1164	stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1165	stmtFindFile = preparetodie(db, "select fileid, xid from filemeta where url = ? and local = 1")
1166	stmtFindFileId = preparetodie(db, "select xid, local, description from filemeta where fileid = ? and url = ? and local = 1")
1167	stmtUserByName = preparetodie(db, "select userid, username, displayname, about, pubkey, seckey, options from users where username = ? and userid > 0")
1168	stmtUserByNumber = preparetodie(db, "select userid, username, displayname, about, pubkey, seckey, options from users where userid = ?")
1169	stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner, meta, folxid) values (?, ?, ?, ?, '', '', '', ?)")
1170	stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, userid, rcpt, msg) values (?, ?, ?, ?, ?)")
1171	stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1172	stmtLoadDoover = preparetodie(db, "select tries, userid, rcpt, msg from doovers where dooverid = ?")
1173	stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1174	stmtUntagged = preparetodie(db, "select xid, rid, flags from (select honkid, xid, rid, flags from honks where userid = ? order by honkid desc limit 10000) order by honkid asc")
1175	stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1176	stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1177	stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1178	stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1179	stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor, dt) values (?, ?, ?, ?)")
1180	stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ? and dt < ?")
1181	stmtDeleteOldXonkers = preparetodie(db, "delete from xonkers where flavor = ? and dt < ?")
1182	stmtRecentHonkers = preparetodie(db, "select distinct(honker) from honks where userid = ? and honker not in (select xid from honkers where userid = ? and flavor = 'sub') order by honkid desc limit 100")
1183	stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
1184	stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
1185	stmtAllOnts = preparetodie(db, "select ontology, count(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2) group by ontology")
1186	stmtGetFilters = preparetodie(db, "select hfcsid, json from hfcs where userid = ?")
1187	stmtSaveFilter = preparetodie(db, "insert into hfcs (userid, json) values (?, ?)")
1188	stmtDeleteFilter = preparetodie(db, "delete from hfcs where userid = ? and hfcsid = ?")
1189	stmtGetTracks = preparetodie(db, "select fetches from tracks where xid = ?")
1190	stmtSaveChonk = preparetodie(db, "insert into chonks (userid, xid, who, target, dt, noise, format) values (?, ?, ?, ?, ?, ?, ?)")
1191	stmtLoadChonks = preparetodie(db, "select chonkid, userid, xid, who, target, dt, noise, format from chonks where userid = ? and dt > ? order by chonkid asc")
1192	stmtGetChatters = preparetodie(db, "select distinct(target) from chonks where userid = ?")
1193}