activity.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 "context"
21 "crypto/tls"
22 "database/sql"
23 "fmt"
24 "html"
25 "io"
26 notrand "math/rand"
27 "net/http"
28 "os"
29 "regexp"
30 "strings"
31 "time"
32
33 "humungus.tedunangst.com/r/webs/gate"
34 "humungus.tedunangst.com/r/webs/gencache"
35 "humungus.tedunangst.com/r/webs/httpsig"
36 "humungus.tedunangst.com/r/webs/junk"
37 "humungus.tedunangst.com/r/webs/templates"
38)
39
40var theonetruename = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
41var thefakename = `application/activity+json`
42var falsenames = []string{
43 `application/ld+json`,
44 `application/activity+json`,
45}
46var itiswhatitis = "https://www.w3.org/ns/activitystreams"
47var thewholeworld = "https://www.w3.org/ns/activitystreams#Public"
48
49var fastTimeout time.Duration = 5
50var slowTimeout time.Duration = 30
51
52func friendorfoe(ct string) bool {
53 ct = strings.ToLower(ct)
54 for _, at := range falsenames {
55 if strings.HasPrefix(ct, at) {
56 return true
57 }
58 }
59 return false
60}
61
62var honkTransport = http.Transport{
63 MaxIdleConns: 120,
64 MaxConnsPerHost: 4,
65}
66
67var honkClient = http.Client{
68 Transport: &honkTransport,
69}
70
71func gogglesDoNothing() {
72 honkTransport.TLSClientConfig = &tls.Config{
73 InsecureSkipVerify: true,
74 }
75}
76
77func PostJunk(keyname string, key httpsig.PrivateKey, url string, j junk.Junk) error {
78 return PostMsg(keyname, key, url, j.ToBytes())
79}
80
81func PostMsg(keyname string, key httpsig.PrivateKey, url string, msg []byte) error {
82 req, err := http.NewRequest("POST", url, bytes.NewReader(msg))
83 if err != nil {
84 return err
85 }
86 req.Header.Set("User-Agent", "honksnonk/5.0; "+serverName)
87 req.Header.Set("Content-Type", theonetruename)
88 httpsig.SignRequest(keyname, key, req, msg)
89 ctx, cancel := context.WithTimeout(context.Background(), 2*slowTimeout*time.Second)
90 defer cancel()
91 req = req.WithContext(ctx)
92 resp, err := honkClient.Do(req)
93 if err != nil {
94 return err
95 }
96 defer resp.Body.Close()
97 switch resp.StatusCode {
98 case 200:
99 case 201:
100 case 202:
101 default:
102 var buf [4096]byte
103 n, _ := resp.Body.Read(buf[:])
104 dlog.Printf("post failure message: %s", buf[n:])
105 return fmt.Errorf("http post status: %d", resp.StatusCode)
106 }
107 ilog.Printf("successful post: %s %d", url, resp.StatusCode)
108 return nil
109}
110
111func GetJunk(userid int64, url string) (junk.Junk, error) {
112 return GetJunkTimeout(userid, url, slowTimeout*time.Second, nil)
113}
114
115func GetJunkFast(userid int64, url string) (junk.Junk, error) {
116 return GetJunkTimeout(userid, url, fastTimeout*time.Second, nil)
117}
118
119func GetJunkHardMode(userid int64, url string) (junk.Junk, error) {
120 j, err := GetJunk(userid, url)
121 if err != nil {
122 emsg := err.Error()
123 if emsg == "http get status: 502" || strings.Contains(emsg, "timeout") {
124 ilog.Printf("trying again after error: %s", emsg)
125 time.Sleep(time.Duration(60+notrand.Int63n(60)) * time.Second)
126 j, err = GetJunk(userid, url)
127 if err != nil {
128 ilog.Printf("still couldn't get it")
129 } else {
130 ilog.Printf("retry success!")
131 }
132 }
133 }
134 return j, err
135}
136
137var flightdeck = gate.NewSerializer()
138
139func GetJunkTimeout(userid int64, url string, timeout time.Duration, final *string) (junk.Junk, error) {
140 if rejectorigin(userid, url, false) {
141 return nil, fmt.Errorf("rejected origin: %s", url)
142 }
143 if final != nil {
144 *final = url
145 }
146 sign := func(req *http.Request) error {
147 ki := ziggy(userid)
148 if ki != nil {
149 httpsig.SignRequest(ki.keyname, ki.seckey, req, nil)
150 }
151 return nil
152 }
153 if develMode {
154 sign = nil
155 }
156 client := honkClient
157 client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
158 if len(via) >= 5 {
159 return fmt.Errorf("stopped after 5 redirects")
160 }
161 if final != nil {
162 *final = req.URL.String()
163 }
164 if sign != nil {
165 sign(req)
166 }
167 return nil
168 }
169 fn := func() (interface{}, error) {
170 at := theonetruename
171 if strings.Contains(url, ".well-known/webfinger?resource") {
172 at = "application/jrd+json"
173 }
174 j, err := junk.Get(url, junk.GetArgs{
175 Accept: at,
176 Agent: "honksnonk/5.0; " + serverName,
177 Timeout: timeout,
178 Client: &client,
179 Fixup: sign,
180 })
181 return j, err
182 }
183
184 ji, err := flightdeck.Call(url, fn)
185 if err != nil {
186 return nil, err
187 }
188 j := ji.(junk.Junk)
189 return j, nil
190}
191
192func fetchsome(url string) ([]byte, error) {
193 req, err := http.NewRequest("GET", url, nil)
194 if err != nil {
195 ilog.Printf("error fetching %s: %s", url, err)
196 return nil, err
197 }
198 req.Header.Set("User-Agent", "honksnonk/5.0; "+serverName)
199 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
200 defer cancel()
201 req = req.WithContext(ctx)
202 resp, err := honkClient.Do(req)
203 if err != nil {
204 ilog.Printf("error fetching %s: %s", url, err)
205 return nil, err
206 }
207 defer resp.Body.Close()
208 switch resp.StatusCode {
209 case 200:
210 case 201:
211 case 202:
212 default:
213 return nil, fmt.Errorf("http get not 200: %d %s", resp.StatusCode, url)
214 }
215 var buf bytes.Buffer
216 limiter := io.LimitReader(resp.Body, 10*1024*1024)
217 io.Copy(&buf, limiter)
218 return buf.Bytes(), nil
219}
220
221func savedonk(url string, name, desc, media string, localize bool) *Donk {
222 if url == "" {
223 return nil
224 }
225 if donk := finddonk(url); donk != nil {
226 return donk
227 }
228 ilog.Printf("saving donk: %s", url)
229 data := []byte{}
230 if localize {
231 fn := func() (interface{}, error) {
232 return fetchsome(url)
233 }
234 ii, err := flightdeck.Call(url, fn)
235 if err != nil {
236 ilog.Printf("error fetching donk: %s", err)
237 localize = false
238 goto saveit
239 }
240 data = ii.([]byte)
241
242 if len(data) == 10*1024*1024 {
243 ilog.Printf("truncation likely")
244 }
245 if strings.HasPrefix(media, "image") {
246 img, err := shrinkit(data)
247 if err != nil {
248 ilog.Printf("unable to decode image: %s", err)
249 localize = false
250 data = []byte{}
251 goto saveit
252 }
253 data = img.Data
254 media = "image/" + img.Format
255 } else if media == "application/pdf" {
256 if len(data) > 1000000 {
257 ilog.Printf("not saving large pdf")
258 localize = false
259 data = []byte{}
260 }
261 } else if len(data) > 100000 {
262 ilog.Printf("not saving large attachment")
263 localize = false
264 data = []byte{}
265 }
266 }
267saveit:
268 fileid, err := savefile(name, desc, url, media, localize, data)
269 if err != nil {
270 elog.Printf("error saving file %s: %s", url, err)
271 return nil
272 }
273 donk := new(Donk)
274 donk.FileID = fileid
275 return donk
276}
277
278func iszonked(userid int64, xid string) bool {
279 var id int64
280 row := stmtFindZonk.QueryRow(userid, xid)
281 err := row.Scan(&id)
282 if err == nil {
283 return true
284 }
285 if err != sql.ErrNoRows {
286 ilog.Printf("error querying zonk: %s", err)
287 }
288 return false
289}
290
291func needxonk(user *WhatAbout, x *Honk) bool {
292 if rejectxonk(x) {
293 return false
294 }
295 return needxonkid(user, x.XID)
296}
297func needbonkid(user *WhatAbout, xid string) bool {
298 return needxonkidX(user, xid, true)
299}
300func needxonkid(user *WhatAbout, xid string) bool {
301 return needxonkidX(user, xid, false)
302}
303func needxonkidX(user *WhatAbout, xid string, isannounce bool) bool {
304 if !strings.HasPrefix(xid, "https://") {
305 return false
306 }
307 if strings.HasPrefix(xid, user.URL+"/") {
308 return false
309 }
310 if rejectorigin(user.ID, xid, isannounce) {
311 ilog.Printf("rejecting origin: %s", xid)
312 return false
313 }
314 if iszonked(user.ID, xid) {
315 ilog.Printf("already zonked: %s", xid)
316 return false
317 }
318 var id int64
319 row := stmtFindXonk.QueryRow(user.ID, xid)
320 err := row.Scan(&id)
321 if err == nil {
322 return false
323 }
324 if err != sql.ErrNoRows {
325 ilog.Printf("error querying xonk: %s", err)
326 }
327 return true
328}
329
330func eradicatexonk(userid int64, xid string) {
331 xonk := getxonk(userid, xid)
332 if xonk != nil {
333 deletehonk(xonk.ID)
334 _, err := stmtSaveZonker.Exec(userid, xid, "zonk")
335 if err != nil {
336 elog.Printf("error eradicating: %s", err)
337 }
338 }
339}
340
341func savexonk(x *Honk) {
342 ilog.Printf("saving xonk: %s", x.XID)
343 go handles(x.Honker)
344 go handles(x.Oonker)
345 savehonk(x)
346}
347
348type Box struct {
349 In string
350 Out string
351 Shared string
352}
353
354var boxofboxes = gencache.New(gencache.Options[string, *Box]{Fill: func(ident string) (*Box, bool) {
355 var info string
356 row := stmtGetXonker.QueryRow(ident, "boxes")
357 err := row.Scan(&info)
358 if err != nil {
359 dlog.Printf("need to get boxes for %s", ident)
360 var j junk.Junk
361 j, err = GetJunk(readyLuserOne, ident)
362 if err != nil {
363 dlog.Printf("error getting boxes: %s", err)
364 return nil, false
365 }
366 allinjest(originate(ident), j)
367 row = stmtGetXonker.QueryRow(ident, "boxes")
368 err = row.Scan(&info)
369 }
370 if err == nil {
371 m := strings.Split(info, " ")
372 b := &Box{In: m[0], Out: m[1], Shared: m[2]}
373 return b, true
374 }
375 return nil, false
376}})
377
378func gimmexonks(user *WhatAbout, outbox string) {
379 dlog.Printf("getting outbox: %s", outbox)
380 j, err := GetJunk(user.ID, outbox)
381 if err != nil {
382 ilog.Printf("error getting outbox: %s", err)
383 return
384 }
385 t, _ := j.GetString("type")
386 origin := originate(outbox)
387 if t == "OrderedCollection" {
388 items, _ := j.GetArray("orderedItems")
389 if items == nil {
390 items, _ = j.GetArray("items")
391 }
392 if items == nil {
393 obj, ok := j.GetMap("first")
394 if ok {
395 items, _ = obj.GetArray("orderedItems")
396 } else {
397 page1, ok := j.GetString("first")
398 if ok {
399 j, err = GetJunk(user.ID, page1)
400 if err != nil {
401 ilog.Printf("error getting page1: %s", err)
402 return
403 }
404 items, _ = j.GetArray("orderedItems")
405 }
406 }
407 }
408 if len(items) > 20 {
409 items = items[0:20]
410 }
411 for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
412 items[i], items[j] = items[j], items[i]
413 }
414 for _, item := range items {
415 obj, ok := item.(junk.Junk)
416 if ok {
417 xonksaver(user, obj, origin)
418 continue
419 }
420 xid, ok := item.(string)
421 if ok {
422 if !needxonkid(user, xid) {
423 continue
424 }
425 obj, err = GetJunk(user.ID, xid)
426 if err != nil {
427 ilog.Printf("error getting item: %s", err)
428 continue
429 }
430 xonksaver(user, obj, originate(xid))
431 }
432 }
433 }
434}
435
436func newphone(a []string, obj junk.Junk) []string {
437 for _, addr := range []string{"to", "cc", "attributedTo"} {
438 who, _ := obj.GetString(addr)
439 if who != "" {
440 a = append(a, who)
441 }
442 whos, _ := obj.GetArray(addr)
443 for _, w := range whos {
444 who, _ := w.(string)
445 if who != "" {
446 a = append(a, who)
447 }
448 }
449 }
450 return a
451}
452
453func extractattrto(obj junk.Junk) string {
454 arr := oneforall(obj, "attributedTo")
455 for _, a := range arr {
456 s, ok := a.(string)
457 if ok {
458 return s
459 }
460 o, ok := a.(junk.Junk)
461 if ok {
462 t, _ := o.GetString("type")
463 id, _ := o.GetString("id")
464 if t == "Person" || t == "" {
465 return id
466 }
467 }
468 }
469 return ""
470}
471
472func oneforall(obj junk.Junk, key string) []interface{} {
473 if val, ok := obj.GetMap(key); ok {
474 return []interface{}{val}
475 }
476 if str, ok := obj.GetString(key); ok {
477 return []interface{}{str}
478 }
479 arr, _ := obj.GetArray(key)
480 return arr
481}
482
483func firstofmany(obj junk.Junk, key string) string {
484 if val, _ := obj.GetString(key); val != "" {
485 return val
486 }
487 if arr, _ := obj.GetArray(key); len(arr) > 0 {
488 val, ok := arr[0].(string)
489 if ok {
490 return val
491 }
492 }
493 return ""
494}
495
496var re_mast0link = regexp.MustCompile(`https://[[:alnum:].]+/users/[[:alnum:]]+/statuses/[[:digit:]]+`)
497var re_masto1ink = regexp.MustCompile(`https://([[:alnum:].]+)/@([[:alnum:]]+)(@[[:alnum:].]+)?/([[:digit:]]+)`)
498var re_misslink = regexp.MustCompile(`https://[[:alnum:].]+/notes/[[:alnum:]]+`)
499var re_honklink = regexp.MustCompile(`https://[[:alnum:].]+/u/[[:alnum:]]+/h/[[:alnum:]]+`)
500var re_r0malink = regexp.MustCompile(`https://[[:alnum:].]+/objects/[[:alnum:]-]+`)
501var re_roma1ink = regexp.MustCompile(`https://[[:alnum:].]+/notice/[[:alnum:]]+`)
502var re_qtlinks = regexp.MustCompile(`>https://[^\s<]+<`)
503
504func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk {
505 depth := 0
506 maxdepth := 10
507 currenttid := ""
508 goingup := 0
509 var xonkxonkfn func(junk.Junk, string, bool, string) *Honk
510
511 qutify := func(user *WhatAbout, qurl, content string) string {
512 if depth >= maxdepth {
513 ilog.Printf("in too deep")
514 return content
515 }
516 // well this is gross
517 malcontent := strings.ReplaceAll(content, `</span><span class="ellipsis">`, "")
518 malcontent = strings.ReplaceAll(malcontent, `</span><span class="invisible">`, "")
519 mlinks := re_qtlinks.FindAllString(malcontent, -1)
520 if qurl != "" {
521 mlinks = append(mlinks, ">"+qurl+"<")
522 }
523 for _, m := range mlinks {
524 tryit := false
525 m = m[1 : len(m)-1]
526 if re_mast0link.MatchString(m) || re_misslink.MatchString(m) ||
527 re_honklink.MatchString(m) || re_r0malink.MatchString(m) ||
528 re_roma1ink.MatchString(m) {
529 tryit = true
530 } else if re_masto1ink.MatchString(m) {
531 tryit = true
532 }
533 if tryit {
534 var prefix string
535 if m == qurl {
536 prefix += fmt.Sprintf("<p><a href=\"%s\">%s</a>", m, m)
537 }
538 var final string
539 if x := getxonk(user.ID, m); x != nil {
540 content = fmt.Sprintf("%s%s<blockquote>%s</blockquote>", content, prefix, x.Noise)
541 } else if j, err := GetJunkTimeout(user.ID, m, fastTimeout*time.Second, &final); err == nil {
542 q, ok := j.GetString("content")
543 if ok {
544 content = fmt.Sprintf("%s%s<blockquote>%s</blockquote>", content, prefix, q)
545 }
546 prevdepth := depth
547 depth = maxdepth
548 xonkxonkfn(j, originate(final), false, "")
549 depth = prevdepth
550 }
551 }
552 }
553 return content
554 }
555
556 saveonemore := func(xid string) {
557 dlog.Printf("getting onemore: %s", xid)
558 if depth >= maxdepth {
559 ilog.Printf("in too deep")
560 return
561 }
562 obj, err := GetJunkHardMode(user.ID, xid)
563 if err != nil {
564 ilog.Printf("error getting onemore: %s: %s", xid, err)
565 return
566 }
567 xonkxonkfn(obj, originate(xid), false, "")
568 }
569
570 xonkxonkfn = func(item junk.Junk, origin string, isUpdate bool, bonker string) *Honk {
571 id, _ := item.GetString("id")
572 what := firstofmany(item, "type")
573 dt, ok := item.GetString("published")
574 if !ok {
575 dt = time.Now().Format(time.RFC3339)
576 }
577 if depth >= maxdepth+5 {
578 ilog.Printf("went too deep in xonkxonk")
579 return nil
580 }
581 depth++
582 defer func() { depth-- }()
583
584 var err error
585 var xid, rid, url, convoy string
586 var replies []string
587 var obj junk.Junk
588 waspage := false
589 preferorig := false
590 switch what {
591 case "Delete":
592 obj, ok = item.GetMap("object")
593 if ok {
594 xid, _ = obj.GetString("id")
595 } else {
596 xid, _ = item.GetString("object")
597 }
598 if xid == "" {
599 return nil
600 }
601 if originate(xid) != origin {
602 ilog.Printf("forged delete: %s", xid)
603 return nil
604 }
605 ilog.Printf("eradicating %s", xid)
606 eradicatexonk(user.ID, xid)
607 return nil
608 case "Remove":
609 xid, _ = item.GetString("object")
610 targ, _ := obj.GetString("target")
611 ilog.Printf("remove %s from %s", obj, targ)
612 return nil
613 case "Tombstone":
614 xid, _ = item.GetString("id")
615 if xid == "" {
616 return nil
617 }
618 if originate(xid) != origin {
619 ilog.Printf("forged delete: %s", xid)
620 return nil
621 }
622 ilog.Printf("eradicating %s", xid)
623 eradicatexonk(user.ID, xid)
624 return nil
625 case "Announce":
626 obj, ok = item.GetMap("object")
627 if ok {
628 // peek ahead some
629 what := firstofmany(obj, "type")
630 if what == "Create" || what == "Update" {
631 if what == "Update" {
632 isUpdate = true
633 }
634 inner, ok := obj.GetMap("object")
635 if ok {
636 obj = inner
637 } else {
638 xid, _ = obj.GetString("object")
639 }
640 }
641 if xid == "" {
642 xid, _ = obj.GetString("id")
643 }
644 } else {
645 xid, _ = item.GetString("object")
646 }
647 if !isUpdate && !needbonkid(user, xid) {
648 return nil
649 }
650 bonker, _ = item.GetString("actor")
651 origin = originate(xid)
652 if ok && originate(id) == origin {
653 dlog.Printf("using object in announce for %s", xid)
654 } else {
655 dlog.Printf("getting bonk: %s", xid)
656 obj, err = GetJunkHardMode(user.ID, xid)
657 if err != nil {
658 ilog.Printf("error getting bonk: %s: %s", xid, err)
659 return nil
660 }
661 }
662 return xonkxonkfn(obj, origin, isUpdate, bonker)
663 case "Update":
664 isUpdate = true
665 fallthrough
666 case "Create":
667 obj, ok = item.GetMap("object")
668 if !ok {
669 xid, _ = item.GetString("object")
670 dlog.Printf("getting created honk: %s", xid)
671 if originate(xid) != origin {
672 ilog.Printf("out of bounds %s not from %s", xid, origin)
673 return nil
674 }
675 obj, err = GetJunkHardMode(user.ID, xid)
676 if err != nil {
677 ilog.Printf("error getting creation: %s", err)
678 }
679 }
680 if obj == nil {
681 ilog.Printf("no object for creation %s", id)
682 return nil
683 }
684 return xonkxonkfn(obj, origin, isUpdate, bonker)
685 case "Read":
686 xid, ok = item.GetString("object")
687 if ok {
688 if !needxonkid(user, xid) {
689 dlog.Printf("don't need read obj: %s", xid)
690 return nil
691 }
692 obj, err = GetJunkHardMode(user.ID, xid)
693 if err != nil {
694 ilog.Printf("error getting read: %s", err)
695 return nil
696 }
697 return xonkxonkfn(obj, originate(xid), false, "")
698 }
699 return nil
700 case "Add":
701 xid, ok = item.GetString("object")
702 if ok {
703 // check target...
704 if !needxonkid(user, xid) {
705 dlog.Printf("don't need added obj: %s", xid)
706 return nil
707 }
708 obj, err = GetJunkHardMode(user.ID, xid)
709 if err != nil {
710 ilog.Printf("error getting add: %s", err)
711 return nil
712 }
713 return xonkxonkfn(obj, originate(xid), false, "")
714 }
715 return nil
716 case "Move":
717 obj = item
718 what = "move"
719 case "Page":
720 waspage = true
721 fallthrough
722 case "Audio":
723 fallthrough
724 case "Image":
725 if what == "Image" {
726 preferorig = true
727 }
728 fallthrough
729 case "Video":
730 fallthrough
731 case "Question":
732 fallthrough
733 case "Note":
734 fallthrough
735 case "Article":
736 obj = item
737 what = "honk"
738 case "Event":
739 obj = item
740 what = "event"
741 case "ChatMessage":
742 bonker = ""
743 obj = item
744 what = "chonk"
745 default:
746 ilog.Printf("unknown activity: %s", what)
747 dumpactivity(item)
748 return nil
749 }
750 if bonker != "" {
751 what = "bonk"
752 }
753
754 if obj != nil {
755 xid, _ = obj.GetString("id")
756 }
757
758 if xid == "" {
759 ilog.Printf("don't know what xid is")
760 item.Write(ilog.Writer())
761 return nil
762 }
763 if originate(xid) != origin {
764 if !develMode && origin != "" {
765 ilog.Printf("original sin: %s not from %s", xid, origin)
766 item.Write(ilog.Writer())
767 return nil
768 }
769 }
770
771 var xonk Honk
772 // early init
773 xonk.XID = xid
774 xonk.UserID = user.ID
775 xonk.Honker, _ = item.GetString("actor")
776 if xonk.Honker == "" {
777 xonk.Honker, _ = item.GetString("attributedTo")
778 }
779 if obj != nil {
780 if xonk.Honker == "" {
781 xonk.Honker = extractattrto(obj)
782 }
783 if bonker != "" {
784 xonk.Honker, xonk.Oonker = bonker, xonk.Honker
785 }
786 if xonk.Oonker == xonk.Honker {
787 xonk.Oonker = ""
788 }
789 xonk.Audience = newphone(nil, obj)
790 }
791 xonk.Audience = append(xonk.Audience, xonk.Honker)
792 xonk.Audience = oneofakind(xonk.Audience)
793 xonk.Public = loudandproud(xonk.Audience)
794
795 var mentions []Mention
796 if obj != nil {
797 ot := firstofmany(obj, "type")
798 url, _ = obj.GetString("url")
799 if dt2, ok := obj.GetString("published"); ok {
800 dt = dt2
801 }
802 content, _ := obj.GetString("content")
803 if !strings.HasPrefix(content, "<p>") {
804 content = "<p>" + content
805 }
806 precis, _ := obj.GetString("summary")
807 if name, ok := obj.GetString("name"); ok {
808 if precis != "" {
809 content = precis + "<p>" + content
810 }
811 precis = html.EscapeString(name)
812 }
813 if sens, _ := obj["sensitive"].(bool); sens && precis == "" {
814 precis = "unspecified horror"
815 }
816 if waspage {
817 content += fmt.Sprintf(`<p><a href="%s">%s</a>`, url, url)
818 url = xid
819 }
820 if user.Options.InlineQuotes {
821 qurl, _ := obj.GetString("quoteUrl")
822 content = qutify(user, qurl, content)
823 }
824 rid, ok = obj.GetString("inReplyTo")
825 if !ok {
826 if robj, ok := obj.GetMap("inReplyTo"); ok {
827 rid, _ = robj.GetString("id")
828 }
829 }
830 convoy, _ = obj.GetString("context")
831 if convoy == "" {
832 convoy, _ = obj.GetString("conversation")
833 }
834 if ot == "Question" {
835 if what == "honk" {
836 what = "qonk"
837 }
838 content += "<ul>"
839 ans, _ := obj.GetArray("oneOf")
840 for _, ai := range ans {
841 a, ok := ai.(junk.Junk)
842 if !ok {
843 continue
844 }
845 as, _ := a.GetString("name")
846 content += "<li>" + as
847 }
848 ans, _ = obj.GetArray("anyOf")
849 for _, ai := range ans {
850 a, ok := ai.(junk.Junk)
851 if !ok {
852 continue
853 }
854 as, _ := a.GetString("name")
855 content += "<li>" + as
856 }
857 content += "</ul>"
858 }
859 if ot == "Move" {
860 targ, _ := obj.GetString("target")
861 content += string(templates.Sprintf(`<p>Moved to <a href="%s">%s</a>`, targ, targ))
862 }
863 if len(content) > 90001 {
864 ilog.Printf("content too long. truncating")
865 content = content[:90001]
866 }
867
868 xonk.Noise = content
869 xonk.Precis = precis
870 if rejectxonk(&xonk) {
871 dlog.Printf("fast reject: %s", xid)
872 return nil
873 }
874
875 numatts := 0
876 procatt := func(att junk.Junk) {
877 at, _ := att.GetString("type")
878 mt, _ := att.GetString("mediaType")
879 if mt == "" {
880 mt = "image"
881 }
882 u, ok := att.GetString("url")
883 if !ok {
884 u, ok = att.GetString("href")
885 }
886 if !ok {
887 if ua, ok := att.GetArray("url"); ok && len(ua) > 0 {
888 u, ok = ua[0].(string)
889 if !ok {
890 if uu, ok := ua[0].(junk.Junk); ok {
891 u, _ = uu.GetString("href")
892 if mt == "" {
893 mt, _ = uu.GetString("mediaType")
894 }
895 }
896 }
897 } else if uu, ok := att.GetMap("url"); ok {
898 u, _ = uu.GetString("href")
899 if mt == "" {
900 mt, _ = uu.GetString("mediaType")
901 }
902 }
903 }
904 name, _ := att.GetString("name")
905 desc, _ := att.GetString("summary")
906 desc = html.UnescapeString(desc)
907 if desc == "" {
908 desc = name
909 }
910 localize := false
911 if at == "Document" || at == "Image" {
912 mt = strings.ToLower(mt)
913 dlog.Printf("attachment: %s %s", mt, u)
914 if mt == "text/plain" || mt == "application/pdf" ||
915 strings.HasPrefix(mt, "image") {
916 if numatts > 4 {
917 ilog.Printf("excessive attachment: %s", at)
918 } else {
919 localize = true
920 }
921 }
922 } else if at == "Link" {
923 if waspage {
924 xonk.Noise += fmt.Sprintf(`<p><a href="%s">%s</a>`, u, u)
925 return
926 }
927 if name == "" {
928 name = u
929 }
930 } else {
931 ilog.Printf("unknown attachment: %s", at)
932 }
933 if skipMedia(&xonk) {
934 localize = false
935 }
936 if preferorig && !localize {
937 return
938 }
939 donk := savedonk(u, name, desc, mt, localize)
940 if donk != nil {
941 xonk.Donks = append(xonk.Donks, donk)
942 }
943 numatts++
944 }
945 if img, ok := obj.GetMap("image"); ok {
946 procatt(img)
947 }
948 if preferorig {
949 atts, _ := obj.GetArray("url")
950 for _, atti := range atts {
951 att, ok := atti.(junk.Junk)
952 if !ok {
953 ilog.Printf("attachment that wasn't map?")
954 continue
955 }
956 procatt(att)
957 }
958 if numatts == 0 {
959 preferorig = false
960 }
961 }
962 if !preferorig {
963 atts := oneforall(obj, "attachment")
964 for _, atti := range atts {
965 att, ok := atti.(junk.Junk)
966 if !ok {
967 ilog.Printf("attachment that wasn't map?")
968 continue
969 }
970 procatt(att)
971 }
972 }
973 proctag := func(tag junk.Junk) {
974 tt, _ := tag.GetString("type")
975 name, _ := tag.GetString("name")
976 desc, _ := tag.GetString("summary")
977 desc = html.UnescapeString(desc)
978 if desc == "" {
979 desc = name
980 }
981 if tt == "Emoji" {
982 icon, _ := tag.GetMap("icon")
983 mt, _ := icon.GetString("mediaType")
984 if mt == "" {
985 mt = "image/png"
986 }
987 u, _ := icon.GetString("url")
988 donk := savedonk(u, name, desc, mt, true)
989 if donk != nil {
990 xonk.Donks = append(xonk.Donks, donk)
991 }
992 }
993 if tt == "Hashtag" {
994 if name == "" || name == "#" {
995 // skip it
996 } else {
997 if name[0] != '#' {
998 name = "#" + name
999 }
1000 xonk.Onts = append(xonk.Onts, name)
1001 }
1002 }
1003 if tt == "Place" {
1004 p := new(Place)
1005 p.Name = name
1006 p.Latitude, _ = tag.GetNumber("latitude")
1007 p.Longitude, _ = tag.GetNumber("longitude")
1008 p.Url, _ = tag.GetString("url")
1009 xonk.Place = p
1010 }
1011 if tt == "Mention" {
1012 var m Mention
1013 m.Who, _ = tag.GetString("name")
1014 m.Where, _ = tag.GetString("href")
1015 mentions = append(mentions, m)
1016 }
1017 }
1018 tags := oneforall(obj, "tag")
1019 for _, tagi := range tags {
1020 tag, ok := tagi.(junk.Junk)
1021 if !ok {
1022 continue
1023 }
1024 proctag(tag)
1025 }
1026 if starttime, ok := obj.GetString("startTime"); ok {
1027 if start, err := time.Parse(time.RFC3339, starttime); err == nil {
1028 t := new(Time)
1029 t.StartTime = start
1030 endtime, _ := obj.GetString("endTime")
1031 t.EndTime, _ = time.Parse(time.RFC3339, endtime)
1032 dura, _ := obj.GetString("duration")
1033 if strings.HasPrefix(dura, "PT") {
1034 dura = strings.ToLower(dura[2:])
1035 d, _ := time.ParseDuration(dura)
1036 t.Duration = Duration(d)
1037 }
1038 xonk.Time = t
1039 }
1040 }
1041 if loca, ok := obj.GetMap("location"); ok {
1042 if tt, _ := loca.GetString("type"); tt == "Place" {
1043 p := new(Place)
1044 p.Name, _ = loca.GetString("name")
1045 p.Latitude, _ = loca.GetNumber("latitude")
1046 p.Longitude, _ = loca.GetNumber("longitude")
1047 p.Url, _ = loca.GetString("url")
1048 xonk.Place = p
1049 }
1050 }
1051
1052 xonk.Onts = oneofakind(xonk.Onts)
1053 replyobj, ok := obj.GetMap("replies")
1054 if ok {
1055 items, ok := replyobj.GetArray("items")
1056 if !ok {
1057 first, ok := replyobj.GetMap("first")
1058 if ok {
1059 items, _ = first.GetArray("items")
1060 }
1061 }
1062 for _, repl := range items {
1063 s, ok := repl.(string)
1064 if ok {
1065 replies = append(replies, s)
1066 }
1067 }
1068 }
1069
1070 }
1071
1072 if currenttid == "" {
1073 currenttid = convoy
1074 }
1075
1076 // init xonk
1077 xonk.What = what
1078 xonk.RID = rid
1079 xonk.Date, _ = time.Parse(time.RFC3339, dt)
1080 xonk.URL = url
1081 xonk.Format = "html"
1082 xonk.Convoy = convoy
1083 xonk.Mentions = mentions
1084 for _, m := range mentions {
1085 if m.Where == user.URL {
1086 xonk.Whofore = 1
1087 }
1088 }
1089 imaginate(&xonk)
1090
1091 if what == "chonk" {
1092 target, _ := obj.GetString("to")
1093 if target == user.URL {
1094 target = xonk.Honker
1095 }
1096 ch := Chonk{
1097 UserID: xonk.UserID,
1098 XID: xid,
1099 Who: xonk.Honker,
1100 Target: target,
1101 Date: xonk.Date,
1102 Noise: xonk.Noise,
1103 Format: xonk.Format,
1104 Donks: xonk.Donks,
1105 }
1106 savechonk(&ch)
1107 return nil
1108 }
1109
1110 if isUpdate {
1111 dlog.Printf("something has changed! %s", xonk.XID)
1112 prev := getxonk(user.ID, xonk.XID)
1113 if prev == nil {
1114 ilog.Printf("didn't find old version for update: %s", xonk.XID)
1115 isUpdate = false
1116 } else {
1117 xonk.ID = prev.ID
1118 updatehonk(&xonk)
1119 }
1120 }
1121 if !isUpdate && needxonk(user, &xonk) {
1122 if rid != "" && xonk.Public {
1123 if needxonkid(user, rid) {
1124 goingup++
1125 saveonemore(rid)
1126 goingup--
1127 }
1128 if convoy == "" {
1129 xx := getxonk(user.ID, rid)
1130 if xx != nil {
1131 convoy = xx.Convoy
1132 }
1133 }
1134 }
1135 if convoy == "" {
1136 convoy = currenttid
1137 }
1138 if convoy == "" {
1139 convoy = xonk.XID
1140 currenttid = convoy
1141 }
1142 xonk.Convoy = convoy
1143 savexonk(&xonk)
1144 }
1145 if goingup == 0 {
1146 for _, replid := range replies {
1147 if needxonkid(user, replid) {
1148 dlog.Printf("missing a reply: %s", replid)
1149 saveonemore(replid)
1150 }
1151 }
1152 }
1153 return &xonk
1154 }
1155
1156 return xonkxonkfn(item, origin, false, "")
1157}
1158
1159func dumpactivity(item junk.Junk) {
1160 fd, err := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
1161 if err != nil {
1162 elog.Printf("error opening inbox! %s", err)
1163 return
1164 }
1165 defer fd.Close()
1166 item.Write(fd)
1167 io.WriteString(fd, "\n")
1168}
1169
1170func rubadubdub(user *WhatAbout, req junk.Junk) {
1171 actor, _ := req.GetString("actor")
1172 j := junk.New()
1173 j["@context"] = itiswhatitis
1174 j["id"] = user.URL + "/dub/" + xfiltrate()
1175 j["type"] = "Accept"
1176 j["actor"] = user.URL
1177 j["to"] = actor
1178 j["published"] = time.Now().UTC().Format(time.RFC3339)
1179 j["object"] = req
1180
1181 deliverate(user.ID, actor, j.ToBytes())
1182}
1183
1184func itakeitallback(user *WhatAbout, xid string, owner string, folxid string) {
1185 j := junk.New()
1186 j["@context"] = itiswhatitis
1187 j["id"] = user.URL + "/unsub/" + folxid
1188 j["type"] = "Undo"
1189 j["actor"] = user.URL
1190 j["to"] = owner
1191 f := junk.New()
1192 f["id"] = user.URL + "/sub/" + folxid
1193 f["type"] = "Follow"
1194 f["actor"] = user.URL
1195 f["to"] = owner
1196 f["object"] = xid
1197 j["object"] = f
1198 j["published"] = time.Now().UTC().Format(time.RFC3339)
1199
1200 deliverate(user.ID, owner, j.ToBytes())
1201}
1202
1203func subsub(user *WhatAbout, xid string, owner string, folxid string) {
1204 if xid == "" {
1205 ilog.Printf("can't subscribe to empty")
1206 return
1207 }
1208 j := junk.New()
1209 j["@context"] = itiswhatitis
1210 j["id"] = user.URL + "/sub/" + folxid
1211 j["type"] = "Follow"
1212 j["actor"] = user.URL
1213 j["to"] = owner
1214 j["object"] = xid
1215 j["published"] = time.Now().UTC().Format(time.RFC3339)
1216
1217 deliverate(user.ID, owner, j.ToBytes())
1218}
1219
1220func activatedonks(donks []*Donk) []junk.Junk {
1221 var atts []junk.Junk
1222 for _, d := range donks {
1223 if re_emus.MatchString(d.Name) {
1224 continue
1225 }
1226 jd := junk.New()
1227 jd["mediaType"] = d.Media
1228 jd["name"] = d.Name
1229 jd["summary"] = html.EscapeString(d.Desc)
1230 jd["type"] = "Document"
1231 jd["url"] = d.URL
1232 atts = append(atts, jd)
1233 }
1234 return atts
1235}
1236
1237// returns activity, object
1238func jonkjonk(user *WhatAbout, h *Honk) (junk.Junk, junk.Junk) {
1239 dt := h.Date.Format(time.RFC3339)
1240 var jo junk.Junk
1241 j := junk.New()
1242 j["id"] = user.URL + "/" + h.What + "/" + shortxid(h.XID)
1243 j["actor"] = user.URL
1244 j["published"] = dt
1245 j["to"] = h.Audience[0]
1246 if len(h.Audience) > 1 {
1247 j["cc"] = h.Audience[1:]
1248 }
1249
1250 switch h.What {
1251 case "update":
1252 fallthrough
1253 case "event":
1254 fallthrough
1255 case "honk":
1256 j["type"] = "Create"
1257 jo = junk.New()
1258 jo["id"] = h.XID
1259 jo["type"] = "Note"
1260 if h.What == "event" {
1261 jo["type"] = "Event"
1262 }
1263 if h.What == "update" {
1264 j["type"] = "Update"
1265 jo["updated"] = dt
1266 }
1267 jo["published"] = dt
1268 jo["url"] = h.XID
1269 jo["attributedTo"] = user.URL
1270 if h.RID != "" {
1271 jo["inReplyTo"] = h.RID
1272 }
1273 if h.Convoy != "" {
1274 jo["context"] = h.Convoy
1275 jo["conversation"] = h.Convoy
1276 }
1277 jo["to"] = h.Audience[0]
1278 if len(h.Audience) > 1 {
1279 jo["cc"] = h.Audience[1:]
1280 }
1281 if !h.Public {
1282 jo["directMessage"] = true
1283 }
1284 h.Noise = re_retag.ReplaceAllString(h.Noise, "")
1285 translate(h)
1286 redoimages(h)
1287 if h.Precis != "" {
1288 jo["sensitive"] = true
1289 }
1290
1291 var replies []string
1292 for _, reply := range h.Replies {
1293 replies = append(replies, reply.XID)
1294 }
1295 if len(replies) > 0 {
1296 jr := junk.New()
1297 jr["type"] = "Collection"
1298 jr["totalItems"] = len(replies)
1299 jr["items"] = replies
1300 jo["replies"] = jr
1301 }
1302
1303 var tags []junk.Junk
1304 for _, m := range h.Mentions {
1305 t := junk.New()
1306 t["type"] = "Mention"
1307 t["name"] = m.Who
1308 t["href"] = m.Where
1309 tags = append(tags, t)
1310 }
1311 for _, o := range h.Onts {
1312 t := junk.New()
1313 t["type"] = "Hashtag"
1314 o = strings.ToLower(o)
1315 t["href"] = fmt.Sprintf("https://%s/o/%s", serverName, o[1:])
1316 t["name"] = o
1317 tags = append(tags, t)
1318 }
1319 for _, e := range herdofemus(h.Noise) {
1320 t := junk.New()
1321 t["id"] = e.ID
1322 t["type"] = "Emoji"
1323 t["name"] = e.Name
1324 i := junk.New()
1325 i["type"] = "Image"
1326 i["mediaType"] = e.Type
1327 i["url"] = e.ID
1328 t["icon"] = i
1329 tags = append(tags, t)
1330 }
1331 for _, e := range fixupflags(h) {
1332 t := junk.New()
1333 t["id"] = e.ID
1334 t["type"] = "Emoji"
1335 t["name"] = e.Name
1336 i := junk.New()
1337 i["type"] = "Image"
1338 i["mediaType"] = "image/png"
1339 i["url"] = e.ID
1340 t["icon"] = i
1341 tags = append(tags, t)
1342 }
1343 if len(tags) > 0 {
1344 jo["tag"] = tags
1345 }
1346 if p := h.Place; p != nil {
1347 t := junk.New()
1348 t["type"] = "Place"
1349 if p.Name != "" {
1350 t["name"] = p.Name
1351 }
1352 if p.Latitude != 0 {
1353 t["latitude"] = p.Latitude
1354 }
1355 if p.Longitude != 0 {
1356 t["longitude"] = p.Longitude
1357 }
1358 if p.Url != "" {
1359 t["url"] = p.Url
1360 }
1361 jo["location"] = t
1362 }
1363 if t := h.Time; t != nil {
1364 jo["startTime"] = t.StartTime.Format(time.RFC3339)
1365 if t.Duration != 0 {
1366 jo["duration"] = "PT" + strings.ToUpper(t.Duration.String())
1367 }
1368 }
1369 atts := activatedonks(h.Donks)
1370 if len(atts) > 0 {
1371 jo["attachment"] = atts
1372 }
1373 jo["summary"] = h.Precis
1374 jo["content"] = h.Noise
1375 j["object"] = jo
1376 case "bonk":
1377 j["type"] = "Announce"
1378 if h.Convoy != "" {
1379 j["context"] = h.Convoy
1380 }
1381 j["object"] = h.XID
1382 case "unbonk":
1383 b := junk.New()
1384 b["id"] = user.URL + "/" + "bonk" + "/" + shortxid(h.XID)
1385 b["type"] = "Announce"
1386 b["actor"] = user.URL
1387 if h.Convoy != "" {
1388 b["context"] = h.Convoy
1389 }
1390 b["object"] = h.XID
1391 j["type"] = "Undo"
1392 j["object"] = b
1393 case "zonk":
1394 j["type"] = "Delete"
1395 j["object"] = h.XID
1396 case "ack":
1397 j["type"] = "Read"
1398 j["object"] = h.XID
1399 if h.Convoy != "" {
1400 j["context"] = h.Convoy
1401 }
1402 case "react":
1403 j["type"] = "EmojiReact"
1404 j["object"] = h.XID
1405 if h.Convoy != "" {
1406 j["context"] = h.Convoy
1407 }
1408 j["content"] = h.Noise
1409 case "deack":
1410 b := junk.New()
1411 b["id"] = user.URL + "/" + "ack" + "/" + shortxid(h.XID)
1412 b["type"] = "Read"
1413 b["actor"] = user.URL
1414 b["object"] = h.XID
1415 if h.Convoy != "" {
1416 b["context"] = h.Convoy
1417 }
1418 j["type"] = "Undo"
1419 j["object"] = b
1420 }
1421
1422 return j, jo
1423}
1424
1425var oldjonks = gencache.New(gencache.Options[string, []byte]{Fill: func(xid string) ([]byte, bool) {
1426 row := stmtAnyXonk.QueryRow(xid)
1427 honk := scanhonk(row)
1428 if honk == nil || !honk.Public {
1429 return nil, true
1430 }
1431 user, _ := butwhatabout(honk.Username)
1432 rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy, 0)
1433 reversehonks(rawhonks)
1434 for _, h := range rawhonks {
1435 if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
1436 honk.Replies = append(honk.Replies, h)
1437 }
1438 }
1439 donksforhonks([]*Honk{honk})
1440 _, j := jonkjonk(user, honk)
1441 if j == nil {
1442 elog.Fatalf("what just happened? %v", honk)
1443 }
1444 j["@context"] = itiswhatitis
1445
1446 return j.ToBytes(), true
1447}, Limit: 128})
1448
1449func gimmejonk(xid string) ([]byte, bool) {
1450 j, ok := oldjonks.Get(xid)
1451 return j, ok
1452}
1453
1454func boxuprcpts(user *WhatAbout, addresses []string, useshared bool) map[string]bool {
1455 rcpts := make(map[string]bool)
1456 for _, a := range addresses {
1457 if a == "" || a == thewholeworld || a == user.URL || strings.HasSuffix(a, "/followers") {
1458 continue
1459 }
1460 if a[0] == '%' {
1461 rcpts[a] = true
1462 continue
1463 }
1464 box, ok := boxofboxes.Get(a)
1465 if ok && useshared && box.Shared != "" {
1466 rcpts["%"+box.Shared] = true
1467 } else {
1468 rcpts[a] = true
1469 }
1470 }
1471 return rcpts
1472}
1473
1474func chonkifymsg(user *WhatAbout, ch *Chonk) []byte {
1475 dt := ch.Date.Format(time.RFC3339)
1476 aud := []string{ch.Target}
1477
1478 jo := junk.New()
1479 jo["id"] = ch.XID
1480 jo["type"] = "ChatMessage"
1481 jo["published"] = dt
1482 jo["attributedTo"] = user.URL
1483 jo["to"] = aud
1484 jo["content"] = ch.HTML
1485 atts := activatedonks(ch.Donks)
1486 if len(atts) > 0 {
1487 jo["attachment"] = atts
1488 }
1489 var tags []junk.Junk
1490 for _, e := range herdofemus(ch.Noise) {
1491 t := junk.New()
1492 t["id"] = e.ID
1493 t["type"] = "Emoji"
1494 t["name"] = e.Name
1495 i := junk.New()
1496 i["type"] = "Image"
1497 i["mediaType"] = e.Type
1498 i["url"] = e.ID
1499 t["icon"] = i
1500 tags = append(tags, t)
1501 }
1502 if len(tags) > 0 {
1503 jo["tag"] = tags
1504 }
1505
1506 j := junk.New()
1507 j["@context"] = itiswhatitis
1508 j["id"] = user.URL + "/" + "honk" + "/" + shortxid(ch.XID)
1509 j["type"] = "Create"
1510 j["actor"] = user.URL
1511 j["published"] = dt
1512 j["to"] = aud
1513 j["object"] = jo
1514
1515 return j.ToBytes()
1516}
1517
1518func sendchonk(user *WhatAbout, ch *Chonk) {
1519 msg := chonkifymsg(user, ch)
1520
1521 rcpts := make(map[string]bool)
1522 rcpts[ch.Target] = true
1523 for a := range rcpts {
1524 go deliverate(user.ID, a, msg)
1525 }
1526}
1527
1528func honkworldwide(user *WhatAbout, honk *Honk) {
1529 jonk, _ := jonkjonk(user, honk)
1530 jonk["@context"] = itiswhatitis
1531 msg := jonk.ToBytes()
1532
1533 rcpts := boxuprcpts(user, honk.Audience, honk.Public)
1534
1535 if honk.Public {
1536 for _, h := range getdubs(user.ID) {
1537 if h.XID == user.URL {
1538 continue
1539 }
1540 box, ok := boxofboxes.Get(h.XID)
1541 if ok && box.Shared != "" {
1542 rcpts["%"+box.Shared] = true
1543 } else {
1544 rcpts[h.XID] = true
1545 }
1546 }
1547 for _, f := range getbacktracks(honk.XID) {
1548 if f[0] == '%' {
1549 rcpts[f] = true
1550 } else {
1551 box, ok := boxofboxes.Get(f)
1552 if ok && box.Shared != "" {
1553 rcpts["%"+box.Shared] = true
1554 } else {
1555 rcpts[f] = true
1556 }
1557 }
1558 }
1559 }
1560 for a := range rcpts {
1561 go deliverate(user.ID, a, msg)
1562 }
1563 if honk.Public && len(honk.Onts) > 0 {
1564 collectiveaction(honk)
1565 }
1566}
1567
1568func collectiveaction(honk *Honk) {
1569 user := getserveruser()
1570 for _, ont := range honk.Onts {
1571 dubs := getnameddubs(readyLuserOne, ont)
1572 if len(dubs) == 0 {
1573 continue
1574 }
1575 j := junk.New()
1576 j["@context"] = itiswhatitis
1577 j["type"] = "Add"
1578 j["id"] = user.URL + "/add/" + shortxid(ont+honk.XID)
1579 j["actor"] = user.URL
1580 j["object"] = honk.XID
1581 j["target"] = fmt.Sprintf("https://%s/o/%s", serverName, ont[1:])
1582 rcpts := make(map[string]bool)
1583 for _, dub := range dubs {
1584 box, ok := boxofboxes.Get(dub.XID)
1585 if ok && box.Shared != "" {
1586 rcpts["%"+box.Shared] = true
1587 } else {
1588 rcpts[dub.XID] = true
1589 }
1590 }
1591 msg := j.ToBytes()
1592 for a := range rcpts {
1593 go deliverate(user.ID, a, msg)
1594 }
1595 }
1596}
1597
1598func junkuser(user *WhatAbout) junk.Junk {
1599 j := junk.New()
1600 j["@context"] = itiswhatitis
1601 j["id"] = user.URL
1602 j["inbox"] = user.URL + "/inbox"
1603 j["outbox"] = user.URL + "/outbox"
1604 j["name"] = user.Display
1605 j["preferredUsername"] = user.Name
1606 j["summary"] = user.HTAbout
1607 var tags []junk.Junk
1608 for _, o := range user.Onts {
1609 t := junk.New()
1610 t["type"] = "Hashtag"
1611 o = strings.ToLower(o)
1612 t["href"] = fmt.Sprintf("https://%s/o/%s", serverName, o[1:])
1613 t["name"] = o
1614 tags = append(tags, t)
1615 }
1616 if len(tags) > 0 {
1617 j["tag"] = tags
1618 }
1619
1620 if user.ID > 0 {
1621 j["type"] = "Person"
1622 j["url"] = user.URL
1623 j["followers"] = user.URL + "/followers"
1624 j["following"] = user.URL + "/following"
1625 a := junk.New()
1626 a["type"] = "Image"
1627 a["mediaType"] = "image/png"
1628 a["url"] = avatarURL(user)
1629 j["icon"] = a
1630 if ban := user.Options.Banner; ban != "" {
1631 a := junk.New()
1632 a["type"] = "Image"
1633 a["mediaType"] = "image/jpg"
1634 a["url"] = ban
1635 j["image"] = a
1636 }
1637 } else {
1638 j["type"] = "Service"
1639 }
1640 k := junk.New()
1641 k["id"] = user.URL + "#key"
1642 k["owner"] = user.URL
1643 k["publicKeyPem"] = user.Key
1644 j["publicKey"] = k
1645
1646 return j
1647}
1648
1649var oldjonkers = gencache.New(gencache.Options[string, []byte]{Fill: func(name string) ([]byte, bool) {
1650 user, err := butwhatabout(name)
1651 if err != nil {
1652 return nil, false
1653 }
1654 j := junkuser(user)
1655 return j.ToBytes(), true
1656}, Duration: 1 * time.Minute})
1657
1658func asjonker(name string) ([]byte, bool) {
1659 j, ok := oldjonkers.Get(name)
1660 return j, ok
1661}
1662
1663var handfull = gencache.New(gencache.Options[string, string]{Fill: func(name string) (string, bool) {
1664 m := strings.Split(name, "@")
1665 if len(m) != 2 {
1666 dlog.Printf("bad fish name: %s", name)
1667 return "", true
1668 }
1669 var href string
1670 row := stmtGetXonker.QueryRow(name, "fishname")
1671 err := row.Scan(&href)
1672 if err == nil {
1673 return href, true
1674 }
1675 dlog.Printf("fishing for %s", name)
1676 j, err := GetJunkFast(readyLuserOne, fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1677 if err != nil {
1678 ilog.Printf("failed to go fish %s: %s", name, err)
1679 return "", true
1680 }
1681 links, _ := j.GetArray("links")
1682 for _, li := range links {
1683 l, ok := li.(junk.Junk)
1684 if !ok {
1685 continue
1686 }
1687 href, _ := l.GetString("href")
1688 rel, _ := l.GetString("rel")
1689 t, _ := l.GetString("type")
1690 if rel == "self" && friendorfoe(t) {
1691 when := time.Now().UTC().Format(dbtimeformat)
1692 _, err := stmtSaveXonker.Exec(name, href, "fishname", when)
1693 if err != nil {
1694 elog.Printf("error saving fishname: %s", err)
1695 }
1696 return href, true
1697 }
1698 }
1699 return href, true
1700}, Duration: 1 * time.Minute})
1701
1702func gofish(name string) string {
1703 if name[0] == '@' {
1704 name = name[1:]
1705 }
1706 href, _ := handfull.Get(name)
1707 return href
1708}
1709
1710func investigate(name string) (*SomeThing, error) {
1711 if name == "" {
1712 return nil, fmt.Errorf("no name")
1713 }
1714 if name[0] == '@' {
1715 name = gofish(name)
1716 }
1717 if name == "" {
1718 return nil, fmt.Errorf("no name")
1719 }
1720 obj, err := GetJunkFast(readyLuserOne, name)
1721 if err != nil {
1722 return nil, err
1723 }
1724 allinjest(originate(name), obj)
1725 return somethingabout(obj)
1726}
1727
1728func somethingabout(obj junk.Junk) (*SomeThing, error) {
1729 info := new(SomeThing)
1730 t, _ := obj.GetString("type")
1731 isowned := false
1732 switch t {
1733 case "Person":
1734 fallthrough
1735 case "Group":
1736 fallthrough
1737 case "Organization":
1738 fallthrough
1739 case "Application":
1740 fallthrough
1741 case "Service":
1742 info.What = SomeActor
1743 case "OrderedCollection":
1744 isowned = true
1745 fallthrough
1746 case "Collection":
1747 info.What = SomeCollection
1748 default:
1749 return nil, fmt.Errorf("unknown object type")
1750 }
1751 info.XID, _ = obj.GetString("id")
1752 info.Name, _ = obj.GetString("preferredUsername")
1753 if info.Name == "" {
1754 info.Name, _ = obj.GetString("name")
1755 }
1756 if isowned {
1757 info.Owner, _ = obj.GetString("attributedTo")
1758 }
1759 if info.Owner == "" {
1760 info.Owner = info.XID
1761 }
1762 return info, nil
1763}
1764
1765func allinjest(origin string, obj junk.Junk) {
1766 keyobj, ok := obj.GetMap("publicKey")
1767 if ok {
1768 ingestpubkey(origin, keyobj)
1769 }
1770 ingestboxes(origin, obj)
1771 ingesthandle(origin, obj)
1772}
1773
1774func ingestpubkey(origin string, obj junk.Junk) {
1775 keyobj, ok := obj.GetMap("publicKey")
1776 if ok {
1777 obj = keyobj
1778 }
1779 keyname, ok := obj.GetString("id")
1780 var data string
1781 row := stmtGetXonker.QueryRow(keyname, "pubkey")
1782 err := row.Scan(&data)
1783 if err == nil {
1784 return
1785 }
1786 if !ok || origin != originate(keyname) {
1787 ilog.Printf("bad key origin %s <> %s", origin, keyname)
1788 return
1789 }
1790 dlog.Printf("ingesting a needed pubkey: %s", keyname)
1791 owner, ok := obj.GetString("owner")
1792 if !ok {
1793 ilog.Printf("error finding %s pubkey owner", keyname)
1794 return
1795 }
1796 data, ok = obj.GetString("publicKeyPem")
1797 if !ok {
1798 ilog.Printf("error finding %s pubkey", keyname)
1799 return
1800 }
1801 if originate(owner) != origin {
1802 ilog.Printf("bad key owner: %s <> %s", owner, origin)
1803 return
1804 }
1805 _, _, err = httpsig.DecodeKey(data)
1806 if err != nil {
1807 ilog.Printf("error decoding %s pubkey: %s", keyname, err)
1808 return
1809 }
1810 when := time.Now().UTC().Format(dbtimeformat)
1811 _, err = stmtSaveXonker.Exec(keyname, data, "pubkey", when)
1812 if err != nil {
1813 elog.Printf("error saving key: %s", err)
1814 }
1815}
1816
1817func ingestboxes(origin string, obj junk.Junk) {
1818 ident, _ := obj.GetString("id")
1819 if ident == "" {
1820 return
1821 }
1822 if originate(ident) != origin {
1823 return
1824 }
1825 var info string
1826 row := stmtGetXonker.QueryRow(ident, "boxes")
1827 err := row.Scan(&info)
1828 if err == nil {
1829 return
1830 }
1831 dlog.Printf("ingesting boxes: %s", ident)
1832 inbox, _ := obj.GetString("inbox")
1833 outbox, _ := obj.GetString("outbox")
1834 sbox, _ := obj.GetString("endpoints", "sharedInbox")
1835 if inbox != "" {
1836 when := time.Now().UTC().Format(dbtimeformat)
1837 m := strings.Join([]string{inbox, outbox, sbox}, " ")
1838 _, err = stmtSaveXonker.Exec(ident, m, "boxes", when)
1839 if err != nil {
1840 elog.Printf("error saving boxes: %s", err)
1841 }
1842 }
1843}
1844
1845func ingesthandle(origin string, obj junk.Junk) {
1846 xid, _ := obj.GetString("id")
1847 if xid == "" {
1848 return
1849 }
1850 if originate(xid) != origin {
1851 return
1852 }
1853 var handle string
1854 row := stmtGetXonker.QueryRow(xid, "handle")
1855 err := row.Scan(&handle)
1856 if err == nil {
1857 return
1858 }
1859 handle, _ = obj.GetString("preferredUsername")
1860 if handle != "" {
1861 when := time.Now().UTC().Format(dbtimeformat)
1862 _, err = stmtSaveXonker.Exec(xid, handle, "handle", when)
1863 if err != nil {
1864 elog.Printf("error saving handle: %s", err)
1865 }
1866 }
1867}
1868
1869func updateMe(username string) {
1870 user, _ := somenamedusers.Get(username)
1871 dt := time.Now().UTC().Format(time.RFC3339)
1872 j := junk.New()
1873 j["@context"] = itiswhatitis
1874 j["id"] = fmt.Sprintf("%s/upme/%s/%d", user.URL, user.Name, time.Now().Unix())
1875 j["actor"] = user.URL
1876 j["published"] = dt
1877 j["to"] = thewholeworld
1878 j["type"] = "Update"
1879 j["object"] = junkuser(user)
1880
1881 msg := j.ToBytes()
1882
1883 rcpts := make(map[string]bool)
1884 for _, f := range getdubs(user.ID) {
1885 if f.XID == user.URL {
1886 continue
1887 }
1888 box, ok := boxofboxes.Get(f.XID)
1889 if ok && box.Shared != "" {
1890 rcpts["%"+box.Shared] = true
1891 } else {
1892 rcpts[f.XID] = true
1893 }
1894 }
1895 for a := range rcpts {
1896 go deliverate(user.ID, a, msg)
1897 }
1898}
1899
1900func followme(user *WhatAbout, who string, name string, j junk.Junk) {
1901 folxid, _ := j.GetString("id")
1902
1903 ilog.Printf("updating honker follow: %s %s", who, folxid)
1904
1905 var x string
1906 db := opendatabase()
1907 row := db.QueryRow("select xid from honkers where name = ? and xid = ? and userid = ? and flavor in ('dub', 'undub')", name, who, user.ID)
1908 err := row.Scan(&x)
1909 if err != sql.ErrNoRows {
1910 ilog.Printf("duplicate follow request: %s", who)
1911 _, err = stmtUpdateFlavor.Exec("dub", folxid, user.ID, name, who, "undub")
1912 if err != nil {
1913 elog.Printf("error updating honker: %s", err)
1914 }
1915 } else {
1916 stmtSaveDub.Exec(user.ID, name, who, "dub", folxid)
1917 }
1918 go rubadubdub(user, j)
1919}
1920
1921func unfollowme(user *WhatAbout, who string, name string, j junk.Junk) {
1922 var folxid string
1923 if who == "" {
1924 folxid, _ = j.GetString("object")
1925
1926 db := opendatabase()
1927 row := db.QueryRow("select xid, name from honkers where userid = ? and folxid = ? and flavor in ('dub', 'undub')", user.ID, folxid)
1928 err := row.Scan(&who, &name)
1929 if err != nil {
1930 if err != sql.ErrNoRows {
1931 elog.Printf("error scanning honker: %s", err)
1932 }
1933 return
1934 }
1935 }
1936
1937 ilog.Printf("updating honker undo: %s %s", who, folxid)
1938 _, err := stmtUpdateFlavor.Exec("undub", folxid, user.ID, name, who, "dub")
1939 if err != nil {
1940 elog.Printf("error updating honker: %s", err)
1941 return
1942 }
1943}
1944
1945func followyou(user *WhatAbout, honkerid int64, sync bool) {
1946 var url, owner string
1947 db := opendatabase()
1948 row := db.QueryRow("select xid, owner from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep', 'presub', 'sub')",
1949 honkerid, user.ID)
1950 err := row.Scan(&url, &owner)
1951 if err != nil {
1952 elog.Printf("can't get honker xid: %s", err)
1953 return
1954 }
1955 folxid := xfiltrate()
1956 ilog.Printf("subscribing to %s", url)
1957 _, err = db.Exec("update honkers set flavor = ?, folxid = ? where honkerid = ?", "presub", folxid, honkerid)
1958 if err != nil {
1959 elog.Printf("error updating honker: %s", err)
1960 return
1961 }
1962 if sync {
1963 subsub(user, url, owner, folxid)
1964 } else {
1965 go subsub(user, url, owner, folxid)
1966 }
1967
1968}
1969func unfollowyou(user *WhatAbout, honkerid int64, sync bool) {
1970 db := opendatabase()
1971 row := db.QueryRow("select xid, owner, folxid, flavor from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep', 'presub', 'sub')",
1972 honkerid, user.ID)
1973 var url, owner, folxid, flavor string
1974 err := row.Scan(&url, &owner, &folxid, &flavor)
1975 if err != nil {
1976 elog.Printf("can't get honker xid: %s", err)
1977 return
1978 }
1979 if flavor == "peep" {
1980 return
1981 }
1982 ilog.Printf("unsubscribing from %s", url)
1983 _, err = db.Exec("update honkers set flavor = ? where honkerid = ?", "unsub", honkerid)
1984 if err != nil {
1985 elog.Printf("error updating honker: %s", err)
1986 return
1987 }
1988 if sync {
1989 itakeitallback(user, url, owner, folxid)
1990 } else {
1991 go itakeitallback(user, url, owner, folxid)
1992 }
1993}
1994
1995func followyou2(user *WhatAbout, j junk.Junk) {
1996 who, _ := j.GetString("actor")
1997
1998 ilog.Printf("updating honker accept: %s", who)
1999 db := opendatabase()
2000 row := db.QueryRow("select name, folxid from honkers where userid = ? and xid = ? and flavor in ('presub', 'sub')",
2001 user.ID, who)
2002 var name, folxid string
2003 err := row.Scan(&name, &folxid)
2004 if err != nil {
2005 elog.Printf("can't get honker name: %s", err)
2006 return
2007 }
2008 _, err = stmtUpdateFlavor.Exec("sub", folxid, user.ID, name, who, "presub")
2009 if err != nil {
2010 elog.Printf("error updating honker: %s", err)
2011 return
2012 }
2013}
2014
2015func nofollowyou2(user *WhatAbout, j junk.Junk) {
2016 who, _ := j.GetString("actor")
2017
2018 ilog.Printf("updating honker reject: %s", who)
2019 db := opendatabase()
2020 row := db.QueryRow("select name, folxid from honkers where userid = ? and xid = ? and flavor in ('presub', 'sub')",
2021 user.ID, who)
2022 var name, folxid string
2023 err := row.Scan(&name, &folxid)
2024 if err != nil {
2025 elog.Printf("can't get honker name: %s", err)
2026 return
2027 }
2028 _, err = stmtUpdateFlavor.Exec("unsub", folxid, user.ID, name, who, "presub")
2029 _, err = stmtUpdateFlavor.Exec("unsub", folxid, user.ID, name, who, "sub")
2030 if err != nil {
2031 elog.Printf("error updating honker: %s", err)
2032 return
2033 }
2034}