all repos — honk @ 47529122da6f06a62dd609325abdee656ba7de64

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