all repos — honk @ 6ee07c9220079022bb9bad6666e1a7be5642062d

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