all repos — honk @ a08066aa6fc2e40311303d1dd44f60612b690aaf

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