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/gencache"
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 = 7
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 = serverURL("/%s/%s", userSep, user.Name)
56 err = unjsonify(options, &user.Options)
57 if err != nil {
58 elog.Printf("error processing user options: %s", err)
59 }
60 user.ChatPubKey.key, _ = b64tokey(user.Options.ChatPubKey)
61 user.ChatSecKey.key, _ = b64tokey(user.Options.ChatSecKey)
62 } else {
63 user.URL = serverURL("/%s", user.Name)
64 }
65 if user.Options.Reaction == "" {
66 user.Options.Reaction = "none"
67 }
68
69 return user, nil
70}
71
72var somenamedusers = gencache.New(gencache.Options[string, *WhatAbout]{Fill: func(name string) (*WhatAbout, bool) {
73 row := stmtUserByName.QueryRow(name)
74 user, err := userfromrow(row)
75 if err != nil {
76 return nil, false
77 }
78 var marker mz.Marker
79 marker.HashLinker = ontoreplacer
80 marker.AtLinker = attoreplacer
81 user.HTAbout = template.HTML(marker.Mark(user.About))
82 user.Onts = marker.HashTags
83 return user, true
84}})
85
86var somenumberedusers = gencache.New(gencache.Options[int64, *WhatAbout]{Fill: func(userid int64) (*WhatAbout, bool) {
87 row := stmtUserByNumber.QueryRow(userid)
88 user, err := userfromrow(row)
89 if err != nil {
90 return nil, false
91 }
92 // don't touch attoreplacer, which introduces a loop
93 // finger -> getjunk -> keys -> users
94 return user, true
95}})
96
97func getserveruser() *WhatAbout {
98 user, ok := somenumberedusers.Get(serverUID)
99 if !ok {
100 elog.Panicf("lost server user")
101 }
102 return user
103}
104
105func butwhatabout(name string) (*WhatAbout, error) {
106 user, ok := somenamedusers.Get(name)
107 if !ok {
108 return nil, fmt.Errorf("no user: %s", name)
109 }
110 return user, nil
111}
112
113var honkerinvalidator gencache.Invalidator[int64]
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 "seealso":
511 h.SeeAlso = j
512 case "onties":
513 h.Onties = j
514 case "link":
515 h.Link = j
516 case "legalname":
517 h.LegalName = j
518 case "oldrev":
519 default:
520 elog.Printf("unknown meta genus: %s", genus)
521 }
522 }
523 rows.Close()
524}
525
526func donksforchonks(chonks []*Chonk) {
527 db := opendatabase()
528 var ids []string
529 chmap := make(map[int64]*Chonk)
530 for _, ch := range chonks {
531 ids = append(ids, fmt.Sprintf("%d", ch.ID))
532 chmap[ch.ID] = ch
533 }
534 idset := strings.Join(ids, ",")
535 // grab donks
536 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)
537 rows, err := db.Query(q)
538 if err != nil {
539 elog.Printf("error querying donks: %s", err)
540 return
541 }
542 defer rows.Close()
543 for rows.Next() {
544 var chid int64
545 d := new(Donk)
546 err = rows.Scan(&chid, &d.FileID, &d.XID, &d.Name, &d.Desc, &d.URL, &d.Media, &d.Local)
547 if err != nil {
548 elog.Printf("error scanning donk: %s", err)
549 continue
550 }
551 ch := chmap[chid]
552 ch.Donks = append(ch.Donks, d)
553 }
554}
555
556func savefile(name string, desc string, url string, media string, local bool, data []byte) (int64, error) {
557 fileid, _, err := savefileandxid(name, desc, url, media, local, data)
558 return fileid, err
559}
560
561func hashfiledata(data []byte) string {
562 h := sha512.New512_256()
563 h.Write(data)
564 return fmt.Sprintf("%x", h.Sum(nil))
565}
566
567func savefileandxid(name string, desc string, url string, media string, local bool, data []byte) (int64, string, error) {
568 var xid string
569 if local {
570 hash := hashfiledata(data)
571 row := stmtCheckFileData.QueryRow(hash)
572 err := row.Scan(&xid)
573 if err == sql.ErrNoRows {
574 xid = xfiltrate()
575 switch media {
576 case "image/png":
577 xid += ".png"
578 case "image/jpeg":
579 xid += ".jpg"
580 case "image/svg+xml":
581 xid += ".svg"
582 case "application/pdf":
583 xid += ".pdf"
584 case "text/plain":
585 xid += ".txt"
586 }
587 _, err = stmtSaveFileData.Exec(xid, media, hash, data)
588 if err != nil {
589 return 0, "", err
590 }
591 } else if err != nil {
592 elog.Printf("error checking file hash: %s", err)
593 return 0, "", err
594 }
595 if url == "" {
596 url = serverURL("/d/%s", xid)
597 }
598 }
599
600 res, err := stmtSaveFile.Exec(xid, name, desc, url, media, local)
601 if err != nil {
602 return 0, "", err
603 }
604 fileid, _ := res.LastInsertId()
605 return fileid, xid, nil
606}
607
608func finddonkid(fileid int64, url string) *Donk {
609 donk := new(Donk)
610 row := stmtFindFileId.QueryRow(fileid, url)
611 err := row.Scan(&donk.XID, &donk.Local, &donk.Desc)
612 if err == nil {
613 donk.FileID = fileid
614 return donk
615 }
616 if err != sql.ErrNoRows {
617 elog.Printf("error finding file: %s", err)
618 }
619 return nil
620}
621
622func finddonk(url string) *Donk {
623 donk := new(Donk)
624 row := stmtFindFile.QueryRow(url)
625 err := row.Scan(&donk.FileID, &donk.XID)
626 if err == nil {
627 return donk
628 }
629 if err != sql.ErrNoRows {
630 elog.Printf("error finding file: %s", err)
631 }
632 return nil
633}
634
635func savechonk(ch *Chonk) error {
636 dt := ch.Date.UTC().Format(dbtimeformat)
637 db := opendatabase()
638 tx, err := db.Begin()
639 if err != nil {
640 elog.Printf("can't begin tx: %s", err)
641 return err
642 }
643
644 res, err := tx.Stmt(stmtSaveChonk).Exec(ch.UserID, ch.XID, ch.Who, ch.Target, dt, ch.Noise, ch.Format)
645 if err == nil {
646 ch.ID, _ = res.LastInsertId()
647 for _, d := range ch.Donks {
648 _, err := tx.Stmt(stmtSaveDonk).Exec(-1, ch.ID, d.FileID)
649 if err != nil {
650 elog.Printf("error saving donk: %s", err)
651 break
652 }
653 }
654 chatplusone(tx, ch.UserID)
655 err = tx.Commit()
656 } else {
657 tx.Rollback()
658 }
659 return err
660}
661
662func chatplusone(tx *sql.Tx, userid int64) {
663 user, ok := somenumberedusers.Get(userid)
664 if !ok {
665 return
666 }
667 options := user.Options
668 options.ChatCount += 1
669 j, err := jsonify(options)
670 if err == nil {
671 _, err = tx.Exec("update users set options = ? where username = ?", j, user.Name)
672 }
673 if err != nil {
674 elog.Printf("error plussing chat: %s", err)
675 }
676 somenamedusers.Clear(user.Name)
677 somenumberedusers.Clear(user.ID)
678}
679
680func chatnewnone(userid int64) {
681 user, ok := somenumberedusers.Get(userid)
682 if !ok || user.Options.ChatCount == 0 {
683 return
684 }
685 options := user.Options
686 options.ChatCount = 0
687 j, err := jsonify(options)
688 if err == nil {
689 db := opendatabase()
690 _, err = db.Exec("update users set options = ? where username = ?", j, user.Name)
691 }
692 if err != nil {
693 elog.Printf("error noneing chat: %s", err)
694 }
695 somenamedusers.Clear(user.Name)
696 somenumberedusers.Clear(user.ID)
697}
698
699func meplusone(tx *sql.Tx, userid int64) {
700 user, ok := somenumberedusers.Get(userid)
701 if !ok {
702 return
703 }
704 options := user.Options
705 options.MeCount += 1
706 j, err := jsonify(options)
707 if err == nil {
708 _, err = tx.Exec("update users set options = ? where username = ?", j, user.Name)
709 }
710 if err != nil {
711 elog.Printf("error plussing me: %s", err)
712 }
713 somenamedusers.Clear(user.Name)
714 somenumberedusers.Clear(user.ID)
715}
716
717func menewnone(userid int64) {
718 user, ok := somenumberedusers.Get(userid)
719 if !ok || user.Options.MeCount == 0 {
720 return
721 }
722 options := user.Options
723 options.MeCount = 0
724 j, err := jsonify(options)
725 if err == nil {
726 db := opendatabase()
727 _, err = db.Exec("update users set options = ? where username = ?", j, user.Name)
728 }
729 if err != nil {
730 elog.Printf("error noneing me: %s", err)
731 }
732 somenamedusers.Clear(user.Name)
733 somenumberedusers.Clear(user.ID)
734}
735
736func loadchatter(userid int64) []*Chatter {
737 duedt := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
738 rows, err := stmtLoadChonks.Query(userid, duedt)
739 if err != nil {
740 elog.Printf("error loading chonks: %s", err)
741 return nil
742 }
743 defer rows.Close()
744 chonks := make(map[string][]*Chonk)
745 var allchonks []*Chonk
746 for rows.Next() {
747 ch := new(Chonk)
748 var dt string
749 err = rows.Scan(&ch.ID, &ch.UserID, &ch.XID, &ch.Who, &ch.Target, &dt, &ch.Noise, &ch.Format)
750 if err != nil {
751 elog.Printf("error scanning chonk: %s", err)
752 continue
753 }
754 ch.Date, _ = time.Parse(dbtimeformat, dt)
755 chonks[ch.Target] = append(chonks[ch.Target], ch)
756 allchonks = append(allchonks, ch)
757 }
758 donksforchonks(allchonks)
759 rows.Close()
760 rows, err = stmtGetChatters.Query(userid)
761 if err != nil {
762 elog.Printf("error getting chatters: %s", err)
763 return nil
764 }
765 for rows.Next() {
766 var target string
767 err = rows.Scan(&target)
768 if err != nil {
769 elog.Printf("error scanning chatter: %s", target)
770 continue
771 }
772 if _, ok := chonks[target]; !ok {
773 chonks[target] = []*Chonk{}
774
775 }
776 }
777 var chatter []*Chatter
778 for target, chonks := range chonks {
779 chatter = append(chatter, &Chatter{
780 Target: target,
781 Chonks: chonks,
782 })
783 }
784 sort.Slice(chatter, func(i, j int) bool {
785 a, b := chatter[i], chatter[j]
786 if len(a.Chonks) == 0 || len(b.Chonks) == 0 {
787 if len(a.Chonks) == len(b.Chonks) {
788 return a.Target < b.Target
789 }
790 return len(a.Chonks) > len(b.Chonks)
791 }
792 return a.Chonks[len(a.Chonks)-1].Date.After(b.Chonks[len(b.Chonks)-1].Date)
793 })
794
795 return chatter
796}
797
798func (honk *Honk) Plain() string {
799 return honktoplain(honk, false)
800}
801
802func (honk *Honk) VeryPlain() string {
803 return honktoplain(honk, true)
804}
805
806func honktoplain(honk *Honk, very bool) string {
807 var plain []string
808 var filt htfilter.Filter
809 if !very {
810 filt.WithLinks = true
811 }
812 if honk.Precis != "" {
813 t, _ := filt.TextOnly(honk.Precis)
814 plain = append(plain, t)
815 }
816 if honk.Format == "html" {
817 t, _ := filt.TextOnly(honk.Noise)
818 plain = append(plain, t)
819 } else {
820 plain = append(plain, honk.Noise)
821 }
822 for _, d := range honk.Donks {
823 plain = append(plain, d.Name)
824 plain = append(plain, d.Desc)
825 }
826 for _, o := range honk.Onts {
827 plain = append(plain, o)
828 }
829 return strings.Join(plain, " ")
830}
831
832func savehonk(h *Honk) error {
833 dt := h.Date.UTC().Format(dbtimeformat)
834 aud := strings.Join(h.Audience, " ")
835
836 db := opendatabase()
837 tx, err := db.Begin()
838 if err != nil {
839 elog.Printf("can't begin tx: %s", err)
840 return err
841 }
842 plain := h.Plain()
843
844 res, err := tx.Stmt(stmtSaveHonk).Exec(h.UserID, h.What, h.Honker, h.XID, h.RID, dt, h.URL,
845 aud, h.Noise, h.Convoy, h.Whofore, h.Format, h.Precis,
846 h.Oonker, h.Flags, plain)
847 if err == nil {
848 h.ID, _ = res.LastInsertId()
849 err = saveextras(tx, h)
850 }
851 if err == nil {
852 if h.Whofore == 1 {
853 dlog.Printf("another one for me: %s", h.XID)
854 meplusone(tx, h.UserID)
855 }
856 err = tx.Commit()
857 } else {
858 tx.Rollback()
859 }
860 if err != nil {
861 elog.Printf("error saving honk: %s", err)
862 }
863 honkhonkline()
864 return err
865}
866
867func updatehonk(h *Honk) error {
868 old := getxonk(h.UserID, h.XID)
869 oldrev := OldRevision{Precis: old.Precis, Noise: old.Noise}
870 dt := h.Date.UTC().Format(dbtimeformat)
871
872 db := opendatabase()
873 tx, err := db.Begin()
874 if err != nil {
875 elog.Printf("can't begin tx: %s", err)
876 return err
877 }
878 plain := h.Plain()
879
880 err = deleteextras(tx, h.ID, false)
881 if err == nil {
882 _, err = tx.Stmt(stmtUpdateHonk).Exec(h.Precis, h.Noise, h.Format, h.Whofore, dt, plain, h.ID)
883 }
884 if err == nil {
885 err = saveextras(tx, h)
886 }
887 if err == nil {
888 var j string
889 j, err = jsonify(&oldrev)
890 if err == nil {
891 _, err = tx.Stmt(stmtSaveMeta).Exec(old.ID, "oldrev", j)
892 }
893 if err != nil {
894 elog.Printf("error saving oldrev: %s", err)
895 }
896 }
897 if err == nil {
898 err = tx.Commit()
899 } else {
900 tx.Rollback()
901 }
902 if err != nil {
903 elog.Printf("error updating honk %d: %s", h.ID, err)
904 }
905 return err
906}
907
908func deletehonk(honkid int64) error {
909 db := opendatabase()
910 tx, err := db.Begin()
911 if err != nil {
912 elog.Printf("can't begin tx: %s", err)
913 return err
914 }
915
916 err = deleteextras(tx, honkid, true)
917 if err == nil {
918 _, err = tx.Stmt(stmtDeleteHonk).Exec(honkid)
919 }
920 if err == nil {
921 err = tx.Commit()
922 } else {
923 tx.Rollback()
924 }
925 if err != nil {
926 elog.Printf("error deleting honk %d: %s", honkid, err)
927 }
928 return err
929}
930
931func saveextras(tx *sql.Tx, h *Honk) error {
932 for _, d := range h.Donks {
933 _, err := tx.Stmt(stmtSaveDonk).Exec(h.ID, -1, d.FileID)
934 if err != nil {
935 elog.Printf("error saving donk: %s", err)
936 return err
937 }
938 }
939 for _, o := range h.Onts {
940 _, err := tx.Stmt(stmtSaveOnt).Exec(strings.ToLower(o), h.ID)
941 if err != nil {
942 elog.Printf("error saving ont: %s", err)
943 return err
944 }
945 }
946 if p := h.Place; p != nil {
947 j, err := jsonify(p)
948 if err == nil {
949 _, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "place", j)
950 }
951 if err != nil {
952 elog.Printf("error saving place: %s", err)
953 return err
954 }
955 }
956 if t := h.Time; t != nil {
957 j, err := jsonify(t)
958 if err == nil {
959 _, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "time", j)
960 }
961 if err != nil {
962 elog.Printf("error saving time: %s", err)
963 return err
964 }
965 }
966 if m := h.Mentions; len(m) > 0 {
967 j, err := jsonify(m)
968 if err == nil {
969 _, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "mentions", j)
970 }
971 if err != nil {
972 elog.Printf("error saving mentions: %s", err)
973 return err
974 }
975 }
976 if onties := h.Onties; onties != "" {
977 _, err := tx.Stmt(stmtSaveMeta).Exec(h.ID, "onties", onties)
978 if err != nil {
979 elog.Printf("error saving onties: %s", err)
980 return err
981 }
982 }
983 if legalname := h.LegalName; legalname != "" {
984 _, err := tx.Stmt(stmtSaveMeta).Exec(h.ID, "legalname", legalname)
985 if err != nil {
986 elog.Printf("error saving legalname: %s", err)
987 return err
988 }
989 }
990 if seealso := h.SeeAlso; seealso != "" {
991 _, err := tx.Stmt(stmtSaveMeta).Exec(h.ID, "seealso", seealso)
992 if err != nil {
993 elog.Printf("error saving seealso: %s", err)
994 return err
995 }
996 }
997 if link := h.Link; link != "" {
998 _, err := tx.Stmt(stmtSaveMeta).Exec(h.ID, "link", link)
999 if err != nil {
1000 elog.Printf("error saving link: %s", err)
1001 return err
1002 }
1003 }
1004 return nil
1005}
1006
1007var baxonker sync.Mutex
1008
1009func addreaction(user *WhatAbout, xid string, who, react string) {
1010 baxonker.Lock()
1011 defer baxonker.Unlock()
1012 h := getxonk(user.ID, xid)
1013 if h == nil {
1014 return
1015 }
1016 h.Badonks = append(h.Badonks, Badonk{Who: who, What: react})
1017 j, _ := jsonify(h.Badonks)
1018 db := opendatabase()
1019 tx, _ := db.Begin()
1020 _, _ = tx.Stmt(stmtDeleteOneMeta).Exec(h.ID, "badonks")
1021 _, _ = tx.Stmt(stmtSaveMeta).Exec(h.ID, "badonks", j)
1022 tx.Commit()
1023}
1024
1025func deleteextras(tx *sql.Tx, honkid int64, everything bool) error {
1026 _, err := tx.Stmt(stmtDeleteDonks).Exec(honkid)
1027 if err != nil {
1028 return err
1029 }
1030 _, err = tx.Stmt(stmtDeleteOnts).Exec(honkid)
1031 if err != nil {
1032 return err
1033 }
1034 if everything {
1035 _, err = tx.Stmt(stmtDeleteAllMeta).Exec(honkid)
1036 } else {
1037 _, err = tx.Stmt(stmtDeleteSomeMeta).Exec(honkid)
1038 }
1039 if err != nil {
1040 return err
1041 }
1042 return nil
1043}
1044
1045func jsonify(what interface{}) (string, error) {
1046 var buf bytes.Buffer
1047 e := json.NewEncoder(&buf)
1048 e.SetEscapeHTML(false)
1049 e.SetIndent("", "")
1050 err := e.Encode(what)
1051 return buf.String(), err
1052}
1053
1054func unjsonify(s string, dest interface{}) error {
1055 d := json.NewDecoder(strings.NewReader(s))
1056 err := d.Decode(dest)
1057 return err
1058}
1059
1060func getxonker(what, flav string) string {
1061 var res string
1062 row := stmtGetXonker.QueryRow(what, flav)
1063 row.Scan(&res)
1064 return res
1065}
1066
1067func savexonker(what, value, flav, when string) {
1068 stmtSaveXonker.Exec(what, value, flav, when)
1069}
1070
1071func savehonker(user *WhatAbout, url, name, flavor, combos, mj string) (int64, string, error) {
1072 var owner string
1073 if url[0] == '#' {
1074 flavor = "peep"
1075 if name == "" {
1076 name = url[1:]
1077 }
1078 owner = url
1079 } else if strings.HasSuffix(url, ".rss") {
1080 flavor = "peep"
1081 if name == "" {
1082 name = url[strings.LastIndexByte(url, '/')+1:]
1083 }
1084 owner = url
1085
1086 } else {
1087 info, err := investigate(url)
1088 if err != nil {
1089 ilog.Printf("failed to investigate honker: %s", err)
1090 return 0, "", err
1091 }
1092 url = info.XID
1093 if name == "" {
1094 name = info.Name
1095 }
1096 owner = info.Owner
1097 }
1098
1099 var x string
1100 db := opendatabase()
1101 row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('sub', 'unsub', 'peep')", url, user.ID)
1102 err := row.Scan(&x)
1103 if err != sql.ErrNoRows {
1104 if err != nil {
1105 elog.Printf("honker scan err: %s", err)
1106 } else {
1107 err = fmt.Errorf("it seems you are already subscribed to them")
1108 }
1109 return 0, "", err
1110 }
1111
1112 res, err := stmtSaveHonker.Exec(user.ID, name, url, flavor, combos, owner, mj)
1113 if err != nil {
1114 elog.Print(err)
1115 return 0, "", err
1116 }
1117 honkerid, _ := res.LastInsertId()
1118 return honkerid, flavor, nil
1119}
1120
1121func cleanupdb(arg string) {
1122 db := opendatabase()
1123 days, err := strconv.Atoi(arg)
1124 var sqlargs []interface{}
1125 var where string
1126 if err != nil {
1127 honker := arg
1128 expdate := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
1129 where = "dt < ? and honker = ?"
1130 sqlargs = append(sqlargs, expdate)
1131 sqlargs = append(sqlargs, honker)
1132 } else {
1133 expdate := time.Now().Add(-time.Duration(days) * 24 * time.Hour).UTC().Format(dbtimeformat)
1134 where = "dt < ? and convoy not in (select convoy from honks where flags & 4 or whofore = 2 or whofore = 3)"
1135 sqlargs = append(sqlargs, expdate)
1136 }
1137 doordie(db, "delete from honks where flags & 4 = 0 and whofore = 0 and "+where, sqlargs...)
1138 doordie(db, "delete from donks where honkid > 0 and honkid not in (select honkid from honks)")
1139 doordie(db, "delete from onts where honkid not in (select honkid from honks)")
1140 doordie(db, "delete from honkmeta where honkid not in (select honkid from honks)")
1141
1142 doordie(db, "delete from filemeta where fileid not in (select fileid from donks)")
1143 for _, u := range allusers() {
1144 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)
1145 }
1146
1147 filexids := make(map[string]bool)
1148 g_blobdb = openblobdb()
1149 rows, err := g_blobdb.Query("select xid from filedata")
1150 if err != nil {
1151 elog.Fatal(err)
1152 }
1153 for rows.Next() {
1154 var xid string
1155 err = rows.Scan(&xid)
1156 if err != nil {
1157 elog.Fatal(err)
1158 }
1159 filexids[xid] = true
1160 }
1161 rows.Close()
1162 rows, err = db.Query("select xid from filemeta")
1163 for rows.Next() {
1164 var xid string
1165 err = rows.Scan(&xid)
1166 if err != nil {
1167 elog.Fatal(err)
1168 }
1169 delete(filexids, xid)
1170 }
1171 rows.Close()
1172 tx, err := g_blobdb.Begin()
1173 if err != nil {
1174 elog.Fatal(err)
1175 }
1176 for xid := range filexids {
1177 _, err = tx.Exec("delete from filedata where xid = ?", xid)
1178 if err != nil {
1179 elog.Fatal(err)
1180 }
1181 }
1182 err = tx.Commit()
1183 if err != nil {
1184 elog.Fatal(err)
1185 }
1186 closedatabases()
1187}
1188
1189var stmtHonkers, stmtDubbers, stmtNamedDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateHonker *sql.Stmt
1190var stmtDeleteHonker *sql.Stmt
1191var stmtAnyXonk, stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1192var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1193var stmtHonksFromLongAgo *sql.Stmt
1194var stmtHonksByHonker, stmtSaveHonk, stmtUserByName, stmtUserByNumber *sql.Stmt
1195var stmtEventHonks, stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk *sql.Stmt
1196var stmtFindFile, stmtFindFileId, stmtGetFileData, stmtSaveFileData, stmtSaveFile *sql.Stmt
1197var stmtCheckFileData *sql.Stmt
1198var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover, stmtOneHonker *sql.Stmt
1199var stmtUntagged, stmtDeleteHonk, stmtDeleteDonks, stmtDeleteOnts, stmtSaveZonker *sql.Stmt
1200var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker, stmtDeleteOldXonkers *sql.Stmt
1201var stmtAllOnts, stmtSaveOnt, stmtUpdateFlags, stmtClearFlags *sql.Stmt
1202var stmtHonksForUserFirstClass *sql.Stmt
1203var stmtSaveMeta, stmtDeleteAllMeta, stmtDeleteOneMeta, stmtDeleteSomeMeta, stmtUpdateHonk *sql.Stmt
1204var stmtHonksISaved, stmtGetFilters, stmtSaveFilter, stmtDeleteFilter *sql.Stmt
1205var stmtGetTracks *sql.Stmt
1206var stmtSaveChonk, stmtLoadChonks, stmtGetChatters *sql.Stmt
1207var stmtDeliquentCheck, stmtDeliquentUpdate *sql.Stmt
1208
1209func preparetodie(db *sql.DB, s string) *sql.Stmt {
1210 stmt, err := db.Prepare(s)
1211 if err != nil {
1212 elog.Fatalf("error %s: %s", err, s)
1213 }
1214 return stmt
1215}
1216
1217var g_blobdb *sql.DB
1218
1219func closedatabases() {
1220 err := alreadyopendb.Close()
1221 if err != nil {
1222 elog.Printf("error closing database: %s", err)
1223 }
1224 if g_blobdb != nil {
1225 err = g_blobdb.Close()
1226 if err != nil {
1227 elog.Printf("error closing database: %s", err)
1228 }
1229 }
1230}
1231
1232func prepareStatements(db *sql.DB) {
1233 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")
1234 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner, meta, folxid) values (?, ?, ?, ?, ?, ?, ?, '')")
1235 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ?, folxid = ? where userid = ? and name = ? and xid = ? and flavor = ?")
1236 stmtUpdateHonker = preparetodie(db, "update honkers set name = ?, combos = ?, meta = ? where honkerid = ? and userid = ?")
1237 stmtDeleteHonker = preparetodie(db, "delete from honkers where honkerid = ?")
1238 stmtOneHonker = preparetodie(db, "select xid from honkers where name = ? and userid = ?")
1239 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1240 stmtNamedDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and name = ? and flavor = 'dub'")
1241
1242 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 "
1243 limit := " order by honks.honkid desc limit 250"
1244 smalllimit := " order by honks.honkid desc limit ?"
1245 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1246 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1247 stmtAnyXonk = preparetodie(db, selecthonks+"where xid = ? and what <> 'bonk' order by honks.honkid asc")
1248 stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
1249 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+smalllimit)
1250 stmtEventHonks = preparetodie(db, selecthonks+"where (whofore = 2 or honks.userid = ?) and what = 'event'"+smalllimit)
1251 stmtUserHonks = preparetodie(db, selecthonks+"where honks.honkid > ? and (whofore = 2 or whofore = ?) and username = ? and dt > ?"+smalllimit)
1252 myhonkers := " and honker in (select xid from honkers where userid = ? and (flavor = 'sub' or flavor = 'peep' or flavor = 'presub') and combos not like '% - %')"
1253 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ?"+myhonkers+butnotthose+limit)
1254 stmtHonksForUserFirstClass = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and (rid = '' or what = 'bonk')"+myhonkers+butnotthose+limit)
1255 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and whofore = 1"+butnotthose+smalllimit)
1256 stmtHonksFromLongAgo = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and dt < ? and (whofore = 2 or flags & 4)"+butnotthose+limit)
1257 stmtHonksISaved = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and flags & 4 order by honks.honkid desc")
1258 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)
1259 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.honkid > ? and honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1260 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)
1261 stmtHonksByConvoy = preparetodie(db, selecthonks+"where honks.honkid > ? and (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1262 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)
1263
1264 stmtSaveMeta = preparetodie(db, "insert into honkmeta (honkid, genus, json) values (?, ?, ?)")
1265 stmtDeleteAllMeta = preparetodie(db, "delete from honkmeta where honkid = ?")
1266 stmtDeleteSomeMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus not in ('oldrev')")
1267 stmtDeleteOneMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus = ?")
1268 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags, plain) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1269 stmtDeleteHonk = preparetodie(db, "delete from honks where honkid = ?")
1270 stmtUpdateHonk = preparetodie(db, "update honks set precis = ?, noise = ?, format = ?, whofore = ?, dt = ?, plain = ? where honkid = ?")
1271 stmtSaveOnt = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
1272 stmtDeleteOnts = preparetodie(db, "delete from onts where honkid = ?")
1273 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, chonkid, fileid) values (?, ?, ?)")
1274 stmtDeleteDonks = preparetodie(db, "delete from donks where honkid = ?")
1275 stmtSaveFile = preparetodie(db, "insert into filemeta (xid, name, description, url, media, local) values (?, ?, ?, ?, ?, ?)")
1276 g_blobdb = openblobdb()
1277 stmtSaveFileData = preparetodie(g_blobdb, "insert into filedata (xid, media, hash, content) values (?, ?, ?, ?)")
1278 stmtCheckFileData = preparetodie(g_blobdb, "select xid from filedata where hash = ?")
1279 stmtGetFileData = preparetodie(g_blobdb, "select media, content from filedata where xid = ?")
1280 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1281 stmtFindFile = preparetodie(db, "select fileid, xid from filemeta where url = ? and local = 1")
1282 stmtFindFileId = preparetodie(db, "select xid, local, description from filemeta where fileid = ? and url = ? and local = 1")
1283 stmtUserByName = preparetodie(db, "select userid, username, displayname, about, pubkey, seckey, options from users where username = ? and userid > 0")
1284 stmtUserByNumber = preparetodie(db, "select userid, username, displayname, about, pubkey, seckey, options from users where userid = ?")
1285 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner, meta, folxid) values (?, ?, ?, ?, '', '', '', ?)")
1286 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, userid, rcpt, msg) values (?, ?, ?, ?, ?)")
1287 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1288 stmtLoadDoover = preparetodie(db, "select tries, userid, rcpt, msg from doovers where dooverid = ?")
1289 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1290 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")
1291 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1292 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1293 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1294 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1295 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor, dt) values (?, ?, ?, ?)")
1296 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ? and dt < ?")
1297 stmtDeleteOldXonkers = preparetodie(db, "delete from xonkers where flavor = ? and dt < ?")
1298 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")
1299 stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
1300 stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
1301 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")
1302 stmtGetFilters = preparetodie(db, "select hfcsid, json from hfcs where userid = ?")
1303 stmtSaveFilter = preparetodie(db, "insert into hfcs (userid, json) values (?, ?)")
1304 stmtDeleteFilter = preparetodie(db, "delete from hfcs where userid = ? and hfcsid = ?")
1305 stmtGetTracks = preparetodie(db, "select fetches from tracks where xid = ?")
1306 stmtSaveChonk = preparetodie(db, "insert into chonks (userid, xid, who, target, dt, noise, format) values (?, ?, ?, ?, ?, ?, ?)")
1307 stmtLoadChonks = preparetodie(db, "select chonkid, userid, xid, who, target, dt, noise, format from chonks where userid = ? and dt > ? order by chonkid asc")
1308 stmtGetChatters = preparetodie(db, "select distinct(target) from chonks where userid = ?")
1309 stmtDeliquentCheck = preparetodie(db, "select dooverid, msg from doovers where userid = ? and rcpt = ?")
1310 stmtDeliquentUpdate = preparetodie(db, "update doovers set msg = ? where dooverid = ?")
1311}