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