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