all repos — honk @ 5a8d265e7c3e1dfb71c2bfaa2c0f629c9d2441b3

my fork of honk

database.go (view raw)

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