all repos — honk @ 47377beb317edc4b5a114d5d0437d5c0b19258de

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 <= 4; 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 "image/svg+xml":
 552				xid += ".svg"
 553			case "application/pdf":
 554				xid += ".pdf"
 555			case "text/plain":
 556				xid += ".txt"
 557			}
 558			_, err = stmtSaveFileData.Exec(xid, media, hash, data)
 559			if err != nil {
 560				return 0, "", err
 561			}
 562		} else if err != nil {
 563			elog.Printf("error checking file hash: %s", err)
 564			return 0, "", err
 565		}
 566		if url == "" {
 567			url = fmt.Sprintf("https://%s/d/%s", serverName, xid)
 568		}
 569	}
 570
 571	res, err := stmtSaveFile.Exec(xid, name, desc, url, media, local)
 572	if err != nil {
 573		return 0, "", err
 574	}
 575	fileid, _ := res.LastInsertId()
 576	return fileid, xid, nil
 577}
 578
 579func finddonkid(fileid int64, url string) *Donk {
 580	donk := new(Donk)
 581	row := stmtFindFileId.QueryRow(fileid, url)
 582	err := row.Scan(&donk.XID, &donk.Local, &donk.Desc)
 583	if err == nil {
 584		donk.FileID = fileid
 585		return donk
 586	}
 587	if err != sql.ErrNoRows {
 588		elog.Printf("error finding file: %s", err)
 589	}
 590	return nil
 591}
 592
 593func finddonk(url string) *Donk {
 594	donk := new(Donk)
 595	row := stmtFindFile.QueryRow(url)
 596	err := row.Scan(&donk.FileID, &donk.XID)
 597	if err == nil {
 598		return donk
 599	}
 600	if err != sql.ErrNoRows {
 601		elog.Printf("error finding file: %s", err)
 602	}
 603	return nil
 604}
 605
 606func savechonk(ch *Chonk) error {
 607	dt := ch.Date.UTC().Format(dbtimeformat)
 608	db := opendatabase()
 609	tx, err := db.Begin()
 610	if err != nil {
 611		elog.Printf("can't begin tx: %s", err)
 612		return err
 613	}
 614
 615	res, err := tx.Stmt(stmtSaveChonk).Exec(ch.UserID, ch.XID, ch.Who, ch.Target, dt, ch.Noise, ch.Format)
 616	if err == nil {
 617		ch.ID, _ = res.LastInsertId()
 618		for _, d := range ch.Donks {
 619			_, err := tx.Stmt(stmtSaveDonk).Exec(-1, ch.ID, d.FileID)
 620			if err != nil {
 621				elog.Printf("error saving donk: %s", err)
 622				break
 623			}
 624		}
 625		chatplusone(tx, ch.UserID)
 626		err = tx.Commit()
 627	} else {
 628		tx.Rollback()
 629	}
 630	return err
 631}
 632
 633func chatplusone(tx *sql.Tx, userid int64) {
 634	var user *WhatAbout
 635	ok := somenumberedusers.Get(userid, &user)
 636	if !ok {
 637		return
 638	}
 639	options := user.Options
 640	options.ChatCount += 1
 641	j, err := jsonify(options)
 642	if err == nil {
 643		_, err = tx.Exec("update users set options = ? where username = ?", j, user.Name)
 644	}
 645	if err != nil {
 646		elog.Printf("error plussing chat: %s", err)
 647	}
 648	somenamedusers.Clear(user.Name)
 649	somenumberedusers.Clear(user.ID)
 650}
 651
 652func chatnewnone(userid int64) {
 653	var user *WhatAbout
 654	ok := somenumberedusers.Get(userid, &user)
 655	if !ok || user.Options.ChatCount == 0 {
 656		return
 657	}
 658	options := user.Options
 659	options.ChatCount = 0
 660	j, err := jsonify(options)
 661	if err == nil {
 662		db := opendatabase()
 663		_, err = db.Exec("update users set options = ? where username = ?", j, user.Name)
 664	}
 665	if err != nil {
 666		elog.Printf("error noneing chat: %s", err)
 667	}
 668	somenamedusers.Clear(user.Name)
 669	somenumberedusers.Clear(user.ID)
 670}
 671
 672func meplusone(tx *sql.Tx, userid int64) {
 673	var user *WhatAbout
 674	ok := somenumberedusers.Get(userid, &user)
 675	if !ok {
 676		return
 677	}
 678	options := user.Options
 679	options.MeCount += 1
 680	j, err := jsonify(options)
 681	if err == nil {
 682		_, err = tx.Exec("update users set options = ? where username = ?", j, user.Name)
 683	}
 684	if err != nil {
 685		elog.Printf("error plussing me: %s", err)
 686	}
 687	somenamedusers.Clear(user.Name)
 688	somenumberedusers.Clear(user.ID)
 689}
 690
 691func menewnone(userid int64) {
 692	var user *WhatAbout
 693	ok := somenumberedusers.Get(userid, &user)
 694	if !ok || user.Options.MeCount == 0 {
 695		return
 696	}
 697	options := user.Options
 698	options.MeCount = 0
 699	j, err := jsonify(options)
 700	if err == nil {
 701		db := opendatabase()
 702		_, err = db.Exec("update users set options = ? where username = ?", j, user.Name)
 703	}
 704	if err != nil {
 705		elog.Printf("error noneing me: %s", err)
 706	}
 707	somenamedusers.Clear(user.Name)
 708	somenumberedusers.Clear(user.ID)
 709}
 710
 711func loadchatter(userid int64) []*Chatter {
 712	duedt := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
 713	rows, err := stmtLoadChonks.Query(userid, duedt)
 714	if err != nil {
 715		elog.Printf("error loading chonks: %s", err)
 716		return nil
 717	}
 718	defer rows.Close()
 719	chonks := make(map[string][]*Chonk)
 720	var allchonks []*Chonk
 721	for rows.Next() {
 722		ch := new(Chonk)
 723		var dt string
 724		err = rows.Scan(&ch.ID, &ch.UserID, &ch.XID, &ch.Who, &ch.Target, &dt, &ch.Noise, &ch.Format)
 725		if err != nil {
 726			elog.Printf("error scanning chonk: %s", err)
 727			continue
 728		}
 729		ch.Date, _ = time.Parse(dbtimeformat, dt)
 730		chonks[ch.Target] = append(chonks[ch.Target], ch)
 731		allchonks = append(allchonks, ch)
 732	}
 733	donksforchonks(allchonks)
 734	rows.Close()
 735	rows, err = stmtGetChatters.Query(userid)
 736	if err != nil {
 737		elog.Printf("error getting chatters: %s", err)
 738		return nil
 739	}
 740	for rows.Next() {
 741		var target string
 742		err = rows.Scan(&target)
 743		if err != nil {
 744			elog.Printf("error scanning chatter: %s", target)
 745			continue
 746		}
 747		if _, ok := chonks[target]; !ok {
 748			chonks[target] = []*Chonk{}
 749
 750		}
 751	}
 752	var chatter []*Chatter
 753	for target, chonks := range chonks {
 754		chatter = append(chatter, &Chatter{
 755			Target: target,
 756			Chonks: chonks,
 757		})
 758	}
 759	sort.Slice(chatter, func(i, j int) bool {
 760		a, b := chatter[i], chatter[j]
 761		if len(a.Chonks) == 0 || len(b.Chonks) == 0 {
 762			if len(a.Chonks) == len(b.Chonks) {
 763				return a.Target < b.Target
 764			}
 765			return len(a.Chonks) > len(b.Chonks)
 766		}
 767		return a.Chonks[len(a.Chonks)-1].Date.After(b.Chonks[len(b.Chonks)-1].Date)
 768	})
 769
 770	return chatter
 771}
 772
 773func savehonk(h *Honk) error {
 774	dt := h.Date.UTC().Format(dbtimeformat)
 775	aud := strings.Join(h.Audience, " ")
 776
 777	db := opendatabase()
 778	tx, err := db.Begin()
 779	if err != nil {
 780		elog.Printf("can't begin tx: %s", err)
 781		return err
 782	}
 783
 784	res, err := tx.Stmt(stmtSaveHonk).Exec(h.UserID, h.What, h.Honker, h.XID, h.RID, dt, h.URL,
 785		aud, h.Noise, h.Convoy, h.Whofore, h.Format, h.Precis,
 786		h.Oonker, h.Flags)
 787	if err == nil {
 788		h.ID, _ = res.LastInsertId()
 789		err = saveextras(tx, h)
 790	}
 791	if err == nil {
 792		if h.Whofore == 1 {
 793			meplusone(tx, h.UserID)
 794		}
 795		err = tx.Commit()
 796	} else {
 797		tx.Rollback()
 798	}
 799	if err != nil {
 800		elog.Printf("error saving honk: %s", err)
 801	}
 802	honkhonkline()
 803	return err
 804}
 805
 806func updatehonk(h *Honk) error {
 807	old := getxonk(h.UserID, h.XID)
 808	oldrev := OldRevision{Precis: old.Precis, Noise: old.Noise}
 809	dt := h.Date.UTC().Format(dbtimeformat)
 810
 811	db := opendatabase()
 812	tx, err := db.Begin()
 813	if err != nil {
 814		elog.Printf("can't begin tx: %s", err)
 815		return err
 816	}
 817
 818	err = deleteextras(tx, h.ID, false)
 819	if err == nil {
 820		_, err = tx.Stmt(stmtUpdateHonk).Exec(h.Precis, h.Noise, h.Format, h.Whofore, dt, h.ID)
 821	}
 822	if err == nil {
 823		err = saveextras(tx, h)
 824	}
 825	if err == nil {
 826		var j string
 827		j, err = jsonify(&oldrev)
 828		if err == nil {
 829			_, err = tx.Stmt(stmtSaveMeta).Exec(old.ID, "oldrev", j)
 830		}
 831		if err != nil {
 832			elog.Printf("error saving oldrev: %s", err)
 833		}
 834	}
 835	if err == nil {
 836		err = tx.Commit()
 837	} else {
 838		tx.Rollback()
 839	}
 840	if err != nil {
 841		elog.Printf("error updating honk %d: %s", h.ID, err)
 842	}
 843	return err
 844}
 845
 846func deletehonk(honkid int64) error {
 847	db := opendatabase()
 848	tx, err := db.Begin()
 849	if err != nil {
 850		elog.Printf("can't begin tx: %s", err)
 851		return err
 852	}
 853
 854	err = deleteextras(tx, honkid, true)
 855	if err == nil {
 856		_, err = tx.Stmt(stmtDeleteHonk).Exec(honkid)
 857	}
 858	if err == nil {
 859		err = tx.Commit()
 860	} else {
 861		tx.Rollback()
 862	}
 863	if err != nil {
 864		elog.Printf("error deleting honk %d: %s", honkid, err)
 865	}
 866	return err
 867}
 868
 869func saveextras(tx *sql.Tx, h *Honk) error {
 870	for _, d := range h.Donks {
 871		_, err := tx.Stmt(stmtSaveDonk).Exec(h.ID, -1, d.FileID)
 872		if err != nil {
 873			elog.Printf("error saving donk: %s", err)
 874			return err
 875		}
 876	}
 877	for _, o := range h.Onts {
 878		_, err := tx.Stmt(stmtSaveOnt).Exec(strings.ToLower(o), h.ID)
 879		if err != nil {
 880			elog.Printf("error saving ont: %s", err)
 881			return err
 882		}
 883	}
 884	if p := h.Place; p != nil {
 885		j, err := jsonify(p)
 886		if err == nil {
 887			_, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "place", j)
 888		}
 889		if err != nil {
 890			elog.Printf("error saving place: %s", err)
 891			return err
 892		}
 893	}
 894	if t := h.Time; t != nil {
 895		j, err := jsonify(t)
 896		if err == nil {
 897			_, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "time", j)
 898		}
 899		if err != nil {
 900			elog.Printf("error saving time: %s", err)
 901			return err
 902		}
 903	}
 904	if m := h.Mentions; len(m) > 0 {
 905		j, err := jsonify(m)
 906		if err == nil {
 907			_, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "mentions", j)
 908		}
 909		if err != nil {
 910			elog.Printf("error saving mentions: %s", err)
 911			return err
 912		}
 913	}
 914	return nil
 915}
 916
 917var baxonker sync.Mutex
 918
 919func addreaction(user *WhatAbout, xid string, who, react string) {
 920	baxonker.Lock()
 921	defer baxonker.Unlock()
 922	h := getxonk(user.ID, xid)
 923	if h == nil {
 924		return
 925	}
 926	h.Badonks = append(h.Badonks, Badonk{Who: who, What: react})
 927	j, _ := jsonify(h.Badonks)
 928	db := opendatabase()
 929	tx, _ := db.Begin()
 930	_, _ = tx.Stmt(stmtDeleteOneMeta).Exec(h.ID, "badonks")
 931	_, _ = tx.Stmt(stmtSaveMeta).Exec(h.ID, "badonks", j)
 932	tx.Commit()
 933}
 934
 935func deleteextras(tx *sql.Tx, honkid int64, everything bool) error {
 936	_, err := tx.Stmt(stmtDeleteDonks).Exec(honkid)
 937	if err != nil {
 938		return err
 939	}
 940	_, err = tx.Stmt(stmtDeleteOnts).Exec(honkid)
 941	if err != nil {
 942		return err
 943	}
 944	if everything {
 945		_, err = tx.Stmt(stmtDeleteAllMeta).Exec(honkid)
 946	} else {
 947		_, err = tx.Stmt(stmtDeleteSomeMeta).Exec(honkid)
 948	}
 949	if err != nil {
 950		return err
 951	}
 952	return nil
 953}
 954
 955func jsonify(what interface{}) (string, error) {
 956	var buf bytes.Buffer
 957	e := json.NewEncoder(&buf)
 958	e.SetEscapeHTML(false)
 959	e.SetIndent("", "")
 960	err := e.Encode(what)
 961	return buf.String(), err
 962}
 963
 964func unjsonify(s string, dest interface{}) error {
 965	d := json.NewDecoder(strings.NewReader(s))
 966	err := d.Decode(dest)
 967	return err
 968}
 969
 970func getxonker(what, flav string) string {
 971	var res string
 972	row := stmtGetXonker.QueryRow(what, flav)
 973	row.Scan(&res)
 974	return res
 975}
 976
 977func savexonker(what, value, flav, when string) {
 978	stmtSaveXonker.Exec(what, value, flav, when)
 979}
 980
 981func savehonker(user *WhatAbout, url, name, flavor, combos, mj string) (int64, error) {
 982	var owner string
 983	if url[0] == '#' {
 984		flavor = "peep"
 985		if name == "" {
 986			name = url[1:]
 987		}
 988		owner = url
 989	} else {
 990		info, err := investigate(url)
 991		if err != nil {
 992			ilog.Printf("failed to investigate honker: %s", err)
 993			return 0, err
 994		}
 995		url = info.XID
 996		if name == "" {
 997			name = info.Name
 998		}
 999		owner = info.Owner
1000	}
1001
1002	var x string
1003	db := opendatabase()
1004	row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('sub', 'unsub', 'peep')", url, user.ID)
1005	err := row.Scan(&x)
1006	if err != sql.ErrNoRows {
1007		if err != nil {
1008			elog.Printf("honker scan err: %s", err)
1009		} else {
1010			err = fmt.Errorf("it seems you are already subscribed to them")
1011		}
1012		return 0, err
1013	}
1014
1015	res, err := stmtSaveHonker.Exec(user.ID, name, url, flavor, combos, owner, mj)
1016	if err != nil {
1017		elog.Print(err)
1018		return 0, err
1019	}
1020	honkerid, _ := res.LastInsertId()
1021	return honkerid, nil
1022}
1023
1024func cleanupdb(arg string) {
1025	db := opendatabase()
1026	days, err := strconv.Atoi(arg)
1027	var sqlargs []interface{}
1028	var where string
1029	if err != nil {
1030		honker := arg
1031		expdate := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
1032		where = "dt < ? and honker = ?"
1033		sqlargs = append(sqlargs, expdate)
1034		sqlargs = append(sqlargs, honker)
1035	} else {
1036		expdate := time.Now().Add(-time.Duration(days) * 24 * time.Hour).UTC().Format(dbtimeformat)
1037		where = "dt < ? and convoy not in (select convoy from honks where flags & 4 or whofore = 2 or whofore = 3)"
1038		sqlargs = append(sqlargs, expdate)
1039	}
1040	doordie(db, "delete from honks where flags & 4 = 0 and whofore = 0 and "+where, sqlargs...)
1041	doordie(db, "delete from donks where honkid > 0 and honkid not in (select honkid from honks)")
1042	doordie(db, "delete from onts where honkid not in (select honkid from honks)")
1043	doordie(db, "delete from honkmeta where honkid not in (select honkid from honks)")
1044
1045	doordie(db, "delete from filemeta where fileid not in (select fileid from donks)")
1046	for _, u := range allusers() {
1047		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)
1048	}
1049
1050	filexids := make(map[string]bool)
1051	blobdb := openblobdb()
1052	rows, err := blobdb.Query("select xid from filedata")
1053	if err != nil {
1054		elog.Fatal(err)
1055	}
1056	for rows.Next() {
1057		var xid string
1058		err = rows.Scan(&xid)
1059		if err != nil {
1060			elog.Fatal(err)
1061		}
1062		filexids[xid] = true
1063	}
1064	rows.Close()
1065	rows, err = db.Query("select xid from filemeta")
1066	for rows.Next() {
1067		var xid string
1068		err = rows.Scan(&xid)
1069		if err != nil {
1070			elog.Fatal(err)
1071		}
1072		delete(filexids, xid)
1073	}
1074	rows.Close()
1075	tx, err := blobdb.Begin()
1076	if err != nil {
1077		elog.Fatal(err)
1078	}
1079	for xid, _ := range filexids {
1080		_, err = tx.Exec("delete from filedata where xid = ?", xid)
1081		if err != nil {
1082			elog.Fatal(err)
1083		}
1084	}
1085	err = tx.Commit()
1086	if err != nil {
1087		elog.Fatal(err)
1088	}
1089}
1090
1091var stmtHonkers, stmtDubbers, stmtNamedDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateHonker *sql.Stmt
1092var stmtDeleteHonker *sql.Stmt
1093var stmtAnyXonk, stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1094var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1095var stmtHonksFromLongAgo *sql.Stmt
1096var stmtHonksByHonker, stmtSaveHonk, stmtUserByName, stmtUserByNumber *sql.Stmt
1097var stmtEventHonks, stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk *sql.Stmt
1098var stmtFindFile, stmtFindFileId, stmtGetFileData, stmtSaveFileData, stmtSaveFile *sql.Stmt
1099var stmtCheckFileData *sql.Stmt
1100var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover, stmtOneHonker *sql.Stmt
1101var stmtUntagged, stmtDeleteHonk, stmtDeleteDonks, stmtDeleteOnts, stmtSaveZonker *sql.Stmt
1102var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker, stmtDeleteOldXonkers *sql.Stmt
1103var stmtAllOnts, stmtSaveOnt, stmtUpdateFlags, stmtClearFlags *sql.Stmt
1104var stmtHonksForUserFirstClass *sql.Stmt
1105var stmtSaveMeta, stmtDeleteAllMeta, stmtDeleteOneMeta, stmtDeleteSomeMeta, stmtUpdateHonk *sql.Stmt
1106var stmtHonksISaved, stmtGetFilters, stmtSaveFilter, stmtDeleteFilter *sql.Stmt
1107var stmtGetTracks *sql.Stmt
1108var stmtSaveChonk, stmtLoadChonks, stmtGetChatters *sql.Stmt
1109var stmtDeliquentCheck, stmtDeliquentUpdate *sql.Stmt
1110
1111func preparetodie(db *sql.DB, s string) *sql.Stmt {
1112	stmt, err := db.Prepare(s)
1113	if err != nil {
1114		elog.Fatalf("error %s: %s", err, s)
1115	}
1116	return stmt
1117}
1118
1119func prepareStatements(db *sql.DB) {
1120	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")
1121	stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner, meta, folxid) values (?, ?, ?, ?, ?, ?, ?, '')")
1122	stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ?, folxid = ? where userid = ? and name = ? and xid = ? and flavor = ?")
1123	stmtUpdateHonker = preparetodie(db, "update honkers set name = ?, combos = ?, meta = ? where honkerid = ? and userid = ?")
1124	stmtDeleteHonker = preparetodie(db, "delete from honkers where honkerid = ?")
1125	stmtOneHonker = preparetodie(db, "select xid from honkers where name = ? and userid = ?")
1126	stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1127	stmtNamedDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and name = ? and flavor = 'dub'")
1128
1129	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 "
1130	limit := " order by honks.honkid desc limit 250"
1131	smalllimit := " order by honks.honkid desc limit ?"
1132	butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1133	stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1134	stmtAnyXonk = preparetodie(db, selecthonks+"where xid = ? order by honks.honkid asc")
1135	stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
1136	stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+smalllimit)
1137	stmtEventHonks = preparetodie(db, selecthonks+"where (whofore = 2 or honks.userid = ?) and what = 'event'"+smalllimit)
1138	stmtUserHonks = preparetodie(db, selecthonks+"where honks.honkid > ? and (whofore = 2 or whofore = ?) and username = ? and dt > ?"+smalllimit)
1139	myhonkers := " and honker in (select xid from honkers where userid = ? and (flavor = 'sub' or flavor = 'peep' or flavor = 'presub') and combos not like '% - %')"
1140	stmtHonksForUser = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ?"+myhonkers+butnotthose+limit)
1141	stmtHonksForUserFirstClass = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and (what <> 'tonk')"+myhonkers+butnotthose+limit)
1142	stmtHonksForMe = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1143	stmtHonksFromLongAgo = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and dt < ? and whofore = 2"+butnotthose+limit)
1144	stmtHonksISaved = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and flags & 4 order by honks.honkid desc")
1145	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)
1146	stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.honkid > ? and honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1147	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)
1148	stmtHonksByConvoy = preparetodie(db, selecthonks+"where honks.honkid > ? and (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1149	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)
1150
1151	stmtSaveMeta = preparetodie(db, "insert into honkmeta (honkid, genus, json) values (?, ?, ?)")
1152	stmtDeleteAllMeta = preparetodie(db, "delete from honkmeta where honkid = ?")
1153	stmtDeleteSomeMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus not in ('oldrev')")
1154	stmtDeleteOneMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus = ?")
1155	stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1156	stmtDeleteHonk = preparetodie(db, "delete from honks where honkid = ?")
1157	stmtUpdateHonk = preparetodie(db, "update honks set precis = ?, noise = ?, format = ?, whofore = ?, dt = ? where honkid = ?")
1158	stmtSaveOnt = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
1159	stmtDeleteOnts = preparetodie(db, "delete from onts where honkid = ?")
1160	stmtSaveDonk = preparetodie(db, "insert into donks (honkid, chonkid, fileid) values (?, ?, ?)")
1161	stmtDeleteDonks = preparetodie(db, "delete from donks where honkid = ?")
1162	stmtSaveFile = preparetodie(db, "insert into filemeta (xid, name, description, url, media, local) values (?, ?, ?, ?, ?, ?)")
1163	blobdb := openblobdb()
1164	stmtSaveFileData = preparetodie(blobdb, "insert into filedata (xid, media, hash, content) values (?, ?, ?, ?)")
1165	stmtCheckFileData = preparetodie(blobdb, "select xid from filedata where hash = ?")
1166	stmtGetFileData = preparetodie(blobdb, "select media, content from filedata where xid = ?")
1167	stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1168	stmtFindFile = preparetodie(db, "select fileid, xid from filemeta where url = ? and local = 1")
1169	stmtFindFileId = preparetodie(db, "select xid, local, description from filemeta where fileid = ? and url = ? and local = 1")
1170	stmtUserByName = preparetodie(db, "select userid, username, displayname, about, pubkey, seckey, options from users where username = ? and userid > 0")
1171	stmtUserByNumber = preparetodie(db, "select userid, username, displayname, about, pubkey, seckey, options from users where userid = ?")
1172	stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner, meta, folxid) values (?, ?, ?, ?, '', '', '', ?)")
1173	stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, userid, rcpt, msg) values (?, ?, ?, ?, ?)")
1174	stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1175	stmtLoadDoover = preparetodie(db, "select tries, userid, rcpt, msg from doovers where dooverid = ?")
1176	stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1177	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")
1178	stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1179	stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1180	stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1181	stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1182	stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor, dt) values (?, ?, ?, ?)")
1183	stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ? and dt < ?")
1184	stmtDeleteOldXonkers = preparetodie(db, "delete from xonkers where flavor = ? and dt < ?")
1185	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")
1186	stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
1187	stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
1188	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")
1189	stmtGetFilters = preparetodie(db, "select hfcsid, json from hfcs where userid = ?")
1190	stmtSaveFilter = preparetodie(db, "insert into hfcs (userid, json) values (?, ?)")
1191	stmtDeleteFilter = preparetodie(db, "delete from hfcs where userid = ? and hfcsid = ?")
1192	stmtGetTracks = preparetodie(db, "select fetches from tracks where xid = ?")
1193	stmtSaveChonk = preparetodie(db, "insert into chonks (userid, xid, who, target, dt, noise, format) values (?, ?, ?, ?, ?, ?, ?)")
1194	stmtLoadChonks = preparetodie(db, "select chonkid, userid, xid, who, target, dt, noise, format from chonks where userid = ? and dt > ? order by chonkid asc")
1195	stmtGetChatters = preparetodie(db, "select distinct(target) from chonks where userid = ?")
1196	stmtDeliquentCheck = preparetodie(db, "select dooverid, msg from doovers where userid = ? and rcpt = ?")
1197	stmtDeliquentUpdate = preparetodie(db, "update doovers set msg = ? where dooverid = ?")
1198}