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