all repos — honk @ c8fa51ec3cb7addf83cbd703585e4500ab64076c

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