fun.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 "crypto/rand"
20 "crypto/sha512"
21 "fmt"
22 "html/template"
23 "io"
24 "net/http"
25 "net/url"
26 "os"
27 "path"
28 "regexp"
29 "strconv"
30 "strings"
31 "sync"
32 "time"
33
34 "github.com/dustin/go-humanize"
35 "golang.org/x/net/html"
36 "humungus.tedunangst.com/r/webs/gencache"
37 "humungus.tedunangst.com/r/webs/htfilter"
38 "humungus.tedunangst.com/r/webs/httpsig"
39 "humungus.tedunangst.com/r/webs/login"
40 "humungus.tedunangst.com/r/webs/mz"
41 "humungus.tedunangst.com/r/webs/templates"
42)
43
44var allowedclasses = make(map[string]bool)
45
46func init() {
47 allowedclasses["kw"] = true
48 allowedclasses["bi"] = true
49 allowedclasses["st"] = true
50 allowedclasses["nm"] = true
51 allowedclasses["tp"] = true
52 allowedclasses["op"] = true
53 allowedclasses["cm"] = true
54 allowedclasses["al"] = true
55 allowedclasses["dl"] = true
56}
57
58var relingo = make(map[string]string)
59
60func loadLingo() {
61 for _, l := range []string{"honked", "bonked", "honked back", "qonked", "evented"} {
62 v := l
63 k := "lingo-" + strings.ReplaceAll(l, " ", "")
64 getconfig(k, &v)
65 relingo[l] = v
66 }
67}
68
69func prettifydate(d time.Time) string {
70 var customMags = []humanize.RelTimeMagnitude{
71 {time.Second, "now", time.Second},
72 {2 * time.Second, "1s %s", 1},
73 {time.Minute, "%ds %s", time.Second},
74 {2 * time.Minute, "1m %s", 1},
75 {time.Hour, "%dm %s", time.Minute},
76 {2 * time.Hour, "1h %s", 1},
77 {humanize.Day, "%dh %s", time.Hour},
78 {2 * humanize.Day, "1d %s", 1},
79 {humanize.Week, "%dd %s", humanize.Day},
80 {2 * humanize.Week, "1w %s", 1},
81 {humanize.Month, "%dw %s", humanize.Week},
82 }
83
84 since := time.Since(d)
85 // More than a month, return the actual date.
86 if since.Hours() > 730 {
87 return d.Format("02 Jan 2006 15:04")
88 }
89
90 return humanize.CustomRelTime(d, time.Now(), "", "from now", customMags)
91}
92func reverbolate(userid UserID, honks []*Honk) {
93 var handlers sync.WaitGroup
94 user, _ := somenumberedusers.Get(userid)
95 for i := range honks {
96 h := honks[i]
97 // idk where else to put this
98 h.DatePretty = prettifydate(h.Date)
99 h.What += "ed"
100 if h.What == "honked" && h.RID != "" {
101 h.What = "honked back"
102 h.Style += " subtle"
103 }
104 if !h.Public {
105 h.Style += " limited"
106 }
107 if h.Whofore == 1 {
108 h.Style += " atme"
109 }
110 translate(h)
111 local := false
112 if h.Whofore == 2 || h.Whofore == 3 {
113 local = true
114 }
115 if local && h.What != "bonked" {
116 h.Noise = re_memes.ReplaceAllString(h.Noise, "")
117 }
118 handlers.Add(1)
119 go func() {
120 h.Username, h.Handle = handles(h.Honker)
121 if !local {
122 short := shortname(userid, h.Honker)
123 if short != "" {
124 h.Username = short
125 } else {
126 h.Username = h.Handle
127 if len(h.Username) > 20 {
128 h.Username = h.Username[:20] + ".."
129 }
130 }
131 }
132 if user != nil {
133 hset := []string{}
134 if h.Honker != user.URL {
135 hset = append(hset, "@"+h.Handle)
136 }
137 if user.Options.MentionAll {
138 for _, a := range h.Audience {
139 if a == h.Honker || a == user.URL {
140 continue
141 }
142 _, hand := handles(a)
143 if hand != "" {
144 hand = "@" + hand
145 hset = append(hset, hand)
146 }
147 }
148 }
149 h.Handles = strings.Join(hset, " ")
150 }
151 if h.URL == "" {
152 h.URL = h.XID
153 }
154 if h.Oonker != "" {
155 _, h.Oondle = handles(h.Oonker)
156 }
157 handlers.Done()
158 }()
159 h.Precis = demoji(h.Precis)
160 h.Noise = demoji(h.Noise)
161 h.Open = "open"
162 var misto string
163 for _, m := range h.Mentions {
164 if m.Where != h.Honker && !m.IsPresent(h.Noise) {
165 misto += string(templates.Sprintf(" <a href=\"%sh?xid=%s\">%s</a>", serverPrefix, url.QueryEscape(m.Where), m.Who))
166 }
167 }
168 var mistag string
169 for _, o := range h.Onts {
170 if !OntIsPresent(o, h.Noise) {
171 mistag += " " + o
172 }
173 }
174 if len(misto) > 0 || len(mistag) > 0 {
175 if len(misto) > 0 {
176 misto = fmt.Sprintf("(%s)<p>", misto[1:])
177 }
178 if len(mistag) > 0 {
179 mistag = fmt.Sprintf("<p>(%s)", mistag[1:])
180 }
181 h.Noise = misto + h.Noise + mistag
182 }
183
184 zap := make(map[string]bool)
185 {
186 var htf htfilter.Filter
187 htf.Imager = replaceimgsand(zap, false, h)
188 htf.SpanClasses = allowedclasses
189 htf.BaseURL, _ = url.Parse(h.XID)
190 emuxifier := func(e string) string {
191 for _, d := range h.Donks {
192 if d.Name == e {
193 zap[d.XID] = true
194 if d.Local {
195 return fmt.Sprintf(`<img class="emu" title="%s" src="/d/%s">`, d.Name, d.XID)
196 }
197 }
198 }
199 if local && h.What != "bonked" {
200 emu, _ := emucache.Get(e)
201 if emu != nil {
202 return fmt.Sprintf(`<img class="emu" title="%s" src="%s">`, emu.Name, emu.ID)
203 }
204 }
205 return e
206 }
207 htf.FilterText = func(w io.Writer, data string) {
208 data = htfilter.EscapeText(data)
209 data = re_emus.ReplaceAllStringFunc(data, emuxifier)
210 io.WriteString(w, data)
211 }
212 if user != nil {
213 htf.RetargetLink = func(href string) string {
214 h2 := strings.ReplaceAll(href, "/@", "/users/")
215 for _, m := range h.Mentions {
216 if h2 == m.Where || href == m.Where {
217 return "/h?xid=" + url.QueryEscape(m.Where)
218 }
219 }
220 return href
221 }
222 }
223 p, _ := htf.String(h.Precis)
224 n, _ := htf.String(h.Noise)
225 h.Precis = string(p)
226 h.Noise = string(n)
227 }
228 j := 0
229 for i := 0; i < len(h.Donks); i++ {
230 if !zap[h.Donks[i].XID] {
231 h.Donks[j] = h.Donks[i]
232 j++
233 }
234 }
235 h.Donks = h.Donks[:j]
236 }
237
238 unsee(honks, userid)
239
240 for _, h := range honks {
241 renderflags(h)
242
243 h.HTPrecis = template.HTML(h.Precis)
244 h.HTML = template.HTML(h.Noise)
245 if redo := relingo[h.What]; redo != "" {
246 h.What = redo
247 }
248 }
249}
250
251func replaceimgsand(zap map[string]bool, absolute bool, honk *Honk) func(node *html.Node) string {
252 return func(node *html.Node) string {
253 src := htfilter.GetAttr(node, "src")
254 alt := htfilter.GetAttr(node, "alt")
255 //title := GetAttr(node, "title")
256 if htfilter.HasClass(node, "Emoji") && alt != "" {
257 return alt
258 }
259 base := path.Base(src)
260 didx, _ := strconv.Atoi(base)
261 var d *Donk
262 if strings.HasPrefix(src, serverPrefix) && didx > 0 && didx <= len(honk.Donks) {
263 d = honk.Donks[didx-1]
264 } else {
265 d = finddonk(src)
266 }
267 if d != nil {
268 zap[d.XID] = true
269 base := ""
270 if absolute {
271 base = serverURL("")
272 }
273 return string(templates.Sprintf(`<img alt="%s" title="%s" src="%s/d/%s">`, alt, alt, base, d.XID))
274 }
275 return string(templates.Sprintf(`<img alt="%s" src="<a href="%s">%s</a>">`, alt, src, src))
276 }
277}
278
279func translatechonk(ch *Chonk) {
280 noise := ch.Noise
281 if ch.Format == "markdown" {
282 var marker mz.Marker
283 noise = marker.Mark(noise)
284 }
285 var htf htfilter.Filter
286 htf.SpanClasses = allowedclasses
287 htf.BaseURL, _ = url.Parse(ch.XID)
288 ch.HTML, _ = htf.String(noise)
289}
290
291func filterchonk(ch *Chonk) {
292 translatechonk(ch)
293
294 noise := string(ch.HTML)
295
296 local := originate(ch.XID) == serverName
297
298 zap := make(map[string]bool)
299 emuxifier := func(e string) string {
300 for _, d := range ch.Donks {
301 if d.Name == e {
302 zap[d.XID] = true
303 if d.Local {
304 return fmt.Sprintf(`<img class="emu" title="%s" src="/d/%s">`, d.Name, d.XID)
305 }
306 }
307 }
308 if local {
309 emu, _ := emucache.Get(e)
310 if emu != nil {
311 return fmt.Sprintf(`<img class="emu" title="%s" src="%s">`, emu.Name, emu.ID)
312 }
313 }
314 return e
315 }
316 noise = re_emus.ReplaceAllStringFunc(noise, emuxifier)
317 j := 0
318 for i := 0; i < len(ch.Donks); i++ {
319 if !zap[ch.Donks[i].XID] {
320 ch.Donks[j] = ch.Donks[i]
321 j++
322 }
323 }
324 ch.Donks = ch.Donks[:j]
325
326 if strings.HasPrefix(noise, "<p>") {
327 noise = noise[3:]
328 }
329 ch.HTML = template.HTML(noise)
330 if short := shortname(ch.UserID, ch.Who); short != "" {
331 ch.Handle = short
332 } else {
333 ch.Handle, _ = handles(ch.Who)
334 }
335
336}
337
338func inlineimgsfor(honk *Honk) func(node *html.Node) string {
339 return func(node *html.Node) string {
340 src := htfilter.GetAttr(node, "src")
341 alt := htfilter.GetAttr(node, "alt")
342 base := path.Base(src)
343 didx, _ := strconv.Atoi(base)
344 if strings.HasPrefix(src, serverPrefix) && didx > 0 && didx <= len(honk.Donks) {
345 dlog.Printf("skipping inline image %s", src)
346 return ""
347 }
348 d := savedonk(src, "image", alt, "image", true)
349 if d != nil {
350 honk.Donks = append(honk.Donks, d)
351 }
352 dlog.Printf("inline img with src: %s", src)
353 return ""
354 }
355}
356
357func imaginate(honk *Honk) {
358 var htf htfilter.Filter
359 htf.Imager = inlineimgsfor(honk)
360 htf.BaseURL, _ = url.Parse(honk.XID)
361 htf.String(honk.Noise)
362}
363
364var re_dangerous = regexp.MustCompile("^[a-zA-Z]{2}:")
365
366func precipitate(honk *Honk) {
367 noise := honk.Noise
368 if re_dangerous.MatchString(noise) {
369 idx := strings.Index(noise, "\n")
370 if idx == -1 {
371 honk.Precis = noise
372 noise = ""
373 } else {
374 honk.Precis = noise[:idx]
375 noise = noise[idx+1:]
376 }
377 var marker mz.Marker
378 marker.Short = true
379 honk.Precis = marker.Mark(strings.TrimSpace(honk.Precis))
380 honk.Noise = noise
381 }
382}
383
384func translate(honk *Honk) {
385 if honk.Format == "html" {
386 return
387 }
388 noise := honk.Noise
389
390 var marker mz.Marker
391 marker.HashLinker = ontoreplacer
392 marker.AtLinker = attoreplacer
393 marker.AllowImages = true
394 noise = strings.TrimSpace(noise)
395 noise = marker.Mark(noise)
396 honk.Noise = noise
397 honk.Onts = append(honk.Onts, marker.HashTags...)
398 honk.Mentions = bunchofgrapes(marker.Mentions)
399 for _, t := range oneofakind(strings.Split(honk.Onties, " ")) {
400 if t[0] != '#' {
401 t = "#" + t
402 }
403 honk.Onts = append(honk.Onts, t)
404 }
405 honk.Onts = oneofakind(honk.Onts)
406 honk.Mentions = append(honk.Mentions, bunchofgrapes(oneofakind(strings.Split(honk.SeeAlso, " ")))...)
407}
408
409func redoimages(honk *Honk) {
410 zap := make(map[string]bool)
411 {
412 var htf htfilter.Filter
413 htf.Imager = replaceimgsand(zap, true, honk)
414 htf.SpanClasses = allowedclasses
415 htf.BaseURL, _ = url.Parse(honk.XID)
416 p, _ := htf.String(honk.Precis)
417 n, _ := htf.String(honk.Noise)
418 honk.Precis = string(p)
419 honk.Noise = string(n)
420 }
421 j := 0
422 for i := 0; i < len(honk.Donks); i++ {
423 if !zap[honk.Donks[i].XID] {
424 honk.Donks[j] = honk.Donks[i]
425 j++
426 }
427 }
428 honk.Donks = honk.Donks[:j]
429
430 honk.Noise = re_memes.ReplaceAllString(honk.Noise, "")
431 honk.Noise = strings.Replace(honk.Noise, "<a href=", "<a class=\"mention u-url\" href=", -1)
432}
433
434func xcelerate(b []byte) string {
435 letters := "BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz1234567891234567891234"
436 for i, c := range b {
437 b[i] = letters[c&63]
438 }
439 s := string(b)
440 return s
441}
442
443func shortxid(xid string) string {
444 h := sha512.New512_256()
445 io.WriteString(h, xid)
446 return xcelerate(h.Sum(nil)[:20])
447}
448
449func xfiltrate() string {
450 var b [18]byte
451 rand.Read(b[:])
452 return xcelerate(b[:])
453}
454
455func grapevine(mentions []Mention) []string {
456 var s []string
457 for _, m := range mentions {
458 s = append(s, m.Where)
459 }
460 return s
461}
462
463func bunchofgrapes(m []string) []Mention {
464 var mentions []Mention
465 for i := range m {
466 who := m[i]
467 if strings.HasPrefix(who, "@https://") {
468 mentions = append(mentions, Mention{Who: who, Where: who[1:]})
469 continue
470 }
471 where := gofish(who)
472 if where != "" {
473 mentions = append(mentions, Mention{Who: who, Where: where})
474 }
475 }
476 return mentions
477}
478
479type Emu struct {
480 ID string
481 Name string
482 Type string
483}
484
485var re_emus = regexp.MustCompile(`:[[:alnum:]_-]+:`)
486
487var emucache = gencache.New(gencache.Options[string, *Emu]{Fill: func(ename string) (*Emu, bool) {
488 fname := ename[1 : len(ename)-1]
489 exts := []string{".png", ".gif"}
490 for _, ext := range exts {
491 _, err := os.Stat(dataDir + "/emus/" + fname + ext)
492 if err != nil {
493 continue
494 }
495 url := serverURL("/emu/%s%s", fname, ext)
496 if develMode {
497 url = fmt.Sprintf("/emu/%s%s", fname, ext)
498 }
499 return &Emu{ID: url, Name: ename, Type: "image/" + ext[1:]}, true
500 }
501 return nil, true
502}, Duration: 10 * time.Second})
503
504func herdofemus(noise string) []*Emu {
505 m := re_emus.FindAllString(noise, -1)
506 m = oneofakind(m)
507 var emus []*Emu
508 for _, e := range m {
509 emu, _ := emucache.Get(e)
510 if emu == nil {
511 continue
512 }
513 emus = append(emus, emu)
514 }
515 return emus
516}
517
518var re_memes = regexp.MustCompile("meme: ?([^\n]+)")
519var re_avatar = regexp.MustCompile("avatar: ?([^\n]+)")
520var re_banner = regexp.MustCompile("banner: ?([^\n]+)")
521var re_convoy = regexp.MustCompile("convoy: ?([^\n]+)")
522var re_convalidate = regexp.MustCompile("^(https?|tag|data):")
523
524func memetize(honk *Honk) {
525 repl := func(x string) string {
526 name := x[5:]
527 if name[0] == ' ' {
528 name = name[1:]
529 }
530 fd, err := os.Open(dataDir + "/memes/" + name)
531 if err != nil {
532 ilog.Printf("no meme for %s", name)
533 return x
534 }
535 var peek [512]byte
536 n, _ := fd.Read(peek[:])
537 ct := http.DetectContentType(peek[:n])
538 fd.Close()
539
540 url := serverURL("/meme/%s", name)
541 fileid, err := savefile(name, name, url, ct, false, nil, nil)
542 if err != nil {
543 elog.Printf("error saving meme: %s", err)
544 return x
545 }
546 d := &Donk{
547 FileID: fileid,
548 Name: name,
549 Media: ct,
550 URL: url,
551 Local: false,
552 }
553 honk.Donks = append(honk.Donks, d)
554 return ""
555 }
556 honk.Noise = re_memes.ReplaceAllStringFunc(honk.Noise, repl)
557}
558
559var re_quickmention = regexp.MustCompile("(^|[ \n])@[[:alnum:]_]+([ \n:;.,']|$)")
560
561func quickrename(s string, userid UserID) string {
562 nonstop := true
563 for nonstop {
564 nonstop = false
565 s = re_quickmention.ReplaceAllStringFunc(s, func(m string) string {
566 prefix := ""
567 if m[0] == ' ' || m[0] == '\n' {
568 prefix = m[:1]
569 m = m[1:]
570 }
571 prefix += "@"
572 m = m[1:]
573 tail := ""
574 if last := m[len(m)-1]; last == ' ' || last == '\n' ||
575 last == ':' || last == ';' ||
576 last == '.' || last == ',' || last == '\'' {
577 tail = m[len(m)-1:]
578 m = m[:len(m)-1]
579 }
580
581 xid := fullname(m, userid)
582
583 if xid != "" {
584 _, name := handles(xid)
585 if name != "" {
586 nonstop = true
587 m = name
588 }
589 }
590 return prefix + m + tail
591 })
592 }
593 return s
594}
595
596var shortnames = gencache.New(gencache.Options[UserID, map[string]string]{Fill: func(userid UserID) (map[string]string, bool) {
597 honkers := gethonkers(userid)
598 m := make(map[string]string)
599 for _, h := range honkers {
600 m[h.XID] = h.Name
601 }
602 return m, true
603}, Invalidator: &honkerinvalidator})
604
605func shortname(userid UserID, xid string) string {
606 m, ok := shortnames.Get(userid)
607 if ok {
608 return m[xid]
609 }
610 return ""
611}
612
613var fullnames = gencache.New(gencache.Options[UserID, map[string]string]{Fill: func(userid UserID) (map[string]string, bool) {
614 honkers := gethonkers(userid)
615 m := make(map[string]string)
616 for _, h := range honkers {
617 m[h.Name] = h.XID
618 }
619 return m, true
620}, Invalidator: &honkerinvalidator})
621
622func fullname(name string, userid UserID) string {
623 m, ok := fullnames.Get(userid)
624 if ok {
625 return m[name]
626 }
627 return ""
628}
629
630func attoreplacer(m string) string {
631 fill := `<span class="h-card"><a class="u-url mention" href="%s">%s</a></span>`
632 if strings.HasPrefix(m, "@https://") {
633 return fmt.Sprintf(fill, html.EscapeString(m[1:]), html.EscapeString(m))
634 }
635 where := gofish(m)
636 if where == "" {
637 return m
638 }
639 who := m[0 : 1+strings.IndexByte(m[1:], '@')]
640 return fmt.Sprintf(fill, html.EscapeString(where), html.EscapeString(who))
641}
642
643func ontoreplacer(h string) string {
644 return fmt.Sprintf(`<a class="mention hashtag" href="%s">%s</a>`,
645 serverURL("/o/%s", strings.ToLower(h[1:])), h)
646}
647
648var re_unurl = regexp.MustCompile("https://([^/]+).*/([^/]+)")
649var re_urlhost = regexp.MustCompile("https://([^/ #)?]+)")
650
651func originate(u string) string {
652 m := re_urlhost.FindStringSubmatch(u)
653 if len(m) > 1 {
654 return m[1]
655 }
656 return ""
657}
658
659var allhandles = gencache.New(gencache.Options[string, string]{Fill: func(xid string) (string, bool) {
660 handle := getxonker(xid, "handle")
661 if handle == "" {
662 dlog.Printf("need to get a handle: %s", xid)
663 info, err := investigate(xid)
664 if err != nil {
665 m := re_unurl.FindStringSubmatch(xid)
666 if len(m) > 2 {
667 handle = m[2]
668 } else {
669 handle = xid
670 }
671 } else {
672 handle = info.Name
673 }
674 }
675 return handle, true
676}})
677
678// handle, handle@host
679func handles(xid string) (string, string) {
680 if xid == "" || xid == thewholeworld || strings.HasSuffix(xid, "/followers") {
681 return "", ""
682 }
683 handle, _ := allhandles.Get(xid)
684 if handle == xid {
685 return xid, xid
686 }
687 return handle, handle + "@" + originate(xid)
688}
689
690func butnottooloud(aud []string) {
691 for i, a := range aud {
692 if strings.HasSuffix(a, "/followers") {
693 aud[i] = ""
694 }
695 }
696}
697
698func loudandproud(aud []string) bool {
699 for _, a := range aud {
700 if a == thewholeworld {
701 return true
702 }
703 }
704 return false
705}
706
707func firstclass(honk *Honk) bool {
708 return honk.Audience[0] == thewholeworld
709}
710
711func oneofakind(a []string) []string {
712 seen := make(map[string]bool)
713 seen[""] = true
714 j := 0
715 for _, s := range a {
716 if !seen[s] {
717 seen[s] = true
718 a[j] = s
719 j++
720 }
721 }
722 if j < len(a)/2 {
723 rv := make([]string, j)
724 copy(rv, a[:j])
725 return rv
726 }
727 return a[:j]
728}
729
730var ziggies = gencache.New(gencache.Options[UserID, *KeyInfo]{Fill: func(userid UserID) (*KeyInfo, bool) {
731 user, ok := somenumberedusers.Get(userid)
732 if !ok {
733 return nil, false
734 }
735 ki := new(KeyInfo)
736 ki.keyname = user.URL + "#key"
737 ki.seckey = user.SecKey
738 return ki, true
739}})
740
741func ziggy(userid UserID) *KeyInfo {
742 ki, _ := ziggies.Get(userid)
743 return ki
744}
745
746var zaggies = gencache.New(gencache.Options[string, httpsig.PublicKey]{Fill: func(keyname string) (httpsig.PublicKey, bool) {
747 data := getxonker(keyname, "pubkey")
748 if data == "" {
749 dlog.Printf("hitting the webs for missing pubkey: %s", keyname)
750 j, err := GetJunk(readyLuserOne, keyname)
751 if err != nil {
752 ilog.Printf("error getting %s pubkey: %s", keyname, err)
753 when := time.Now().UTC().Format(dbtimeformat)
754 stmtSaveXonker.Exec(keyname, "failed", "pubkey", when)
755 return httpsig.PublicKey{}, true
756 }
757 allinjest(originate(keyname), j)
758 data = getxonker(keyname, "pubkey")
759 if data == "" {
760 ilog.Printf("key not found after ingesting")
761 when := time.Now().UTC().Format(dbtimeformat)
762 stmtSaveXonker.Exec(keyname, "failed", "pubkey", when)
763 return httpsig.PublicKey{}, true
764 }
765 }
766 if data == "failed" {
767 ilog.Printf("lookup previously failed key %s", keyname)
768 return httpsig.PublicKey{}, true
769 }
770 _, key, err := httpsig.DecodeKey(data)
771 if err != nil {
772 ilog.Printf("error decoding %s pubkey: %s", keyname, err)
773 return key, true
774 }
775 return key, true
776}, Limit: 512})
777
778func zaggy(keyname string) (httpsig.PublicKey, error) {
779 key, _ := zaggies.Get(keyname)
780 return key, nil
781}
782
783func savingthrow(keyname string) {
784 when := time.Now().Add(-30 * time.Minute).UTC().Format(dbtimeformat)
785 stmtDeleteXonker.Exec(keyname, "pubkey", when)
786 zaggies.Clear(keyname)
787}
788
789func keymatch(keyname string, actor string) string {
790 origin := originate(actor)
791 if origin == originate(keyname) {
792 return origin
793 }
794 return ""
795}
796
797func avatator(n string, uinfo *login.UserInfo) []byte {
798 var a []byte
799 if isurl(n) {
800 if uinfo != nil {
801 j, err := GetJunkFast(UserID(uinfo.UserID), n)
802 if err != nil {
803 dlog.Println("avatating: getting junk:", err)
804 a = avatateautogen(n)
805 }
806 pfpurl, _ := j.GetString("icon", "url")
807 res, err := http.Get(pfpurl)
808 if err != nil {
809 dlog.Println("avatating: getting pfp url:", err)
810 a = avatateautogen(n)
811 } else {
812 defer res.Body.Close()
813
814 pfpbytes, err := io.ReadAll(res.Body)
815 if err != nil {
816 dlog.Println("avatating: bruh shits clapped:", err)
817 a = avatateautogen(n)
818 }
819 a = pfpbytes
820 }
821 } else {
822 a = avatateautogen(n)
823 }
824 } else {
825 a = avatateautogen(n)
826 }
827
828 return a
829}
830
831func xfildate() string {
832 var b [21]byte
833 rand.Read(b[:])
834 now := time.Now().Unix() / 60 / 60 / 24
835 b[2] = byte(now & 63)
836 b[1] = byte((now / 64) & 63)
837 b[0] = byte((now / 64 / 64) & 63)
838 return xcelerate(b[:])
839}