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" {
832 preferorig = true
833 }
834 fallthrough
835 case "Video":
836 fallthrough
837 case "Question":
838 fallthrough
839 case "Note":
840 fallthrough
841 case "Article":
842 obj = item
843 what = "honk"
844 case "Event":
845 obj = item
846 what = "event"
847 case "ChatMessage":
848 bonker = ""
849 obj = item
850 what = "chonk"
851 case "Like":
852 return nil
853 case "Dislike":
854 return nil
855 default:
856 ilog.Printf("unknown activity: %s", what)
857 dumpactivity(item)
858 return nil
859 }
860 if bonker != "" {
861 what = "bonk"
862 }
863
864 if obj != nil {
865 xid, _ = obj.GetString("id")
866 }
867
868 if xid == "" {
869 ilog.Printf("don't know what xid is")
870 item.Write(ilog.Writer())
871 return nil
872 }
873 if originate(xid) != origin {
874 if !develMode && origin != "" {
875 ilog.Printf("original sin: %s not from %s", xid, origin)
876 item.Write(ilog.Writer())
877 return nil
878 }
879 }
880
881 var xonk Honk
882 // early init
883 xonk.XID = xid
884 xonk.UserID = user.ID
885 xonk.Honker, _ = item.GetString("actor")
886 if xonk.Honker == "" {
887 xonk.Honker = extractattrto(item)
888 }
889 if myown && xonk.Honker != user.URL {
890 ilog.Printf("not allowing local impersonation: %s <> %s", xonk.Honker, user.URL)
891 item.Write(ilog.Writer())
892 return nil
893 }
894 if originate(xonk.Honker) != origin {
895 ilog.Printf("out of bounds honker %s from %s", xonk.Honker, origin)
896 item.Write(ilog.Writer())
897 return nil
898 }
899 if obj != nil {
900 if xonk.Honker == "" {
901 xonk.Honker = extractattrto(obj)
902 }
903 if bonker != "" {
904 xonk.Honker, xonk.Oonker = bonker, xonk.Honker
905 }
906 if xonk.Oonker == xonk.Honker {
907 xonk.Oonker = ""
908 }
909 xonk.Audience = newphone(nil, obj)
910 }
911 xonk.Audience = append(xonk.Audience, xonk.Honker)
912 xonk.Audience = oneofakind(xonk.Audience)
913 for i, a := range xonk.Audience {
914 if a == tinyworld {
915 xonk.Audience[i] = thewholeworld
916 }
917 }
918 xonk.Public = loudandproud(xonk.Audience)
919
920 var mentions []Mention
921 if obj != nil {
922 ot := firstofmany(obj, "type")
923 url, _ = obj.GetString("url")
924 if dt2, ok := obj.GetString("published"); ok {
925 dt = dt2
926 }
927 content, _ := obj.GetString("content")
928 if !strings.HasPrefix(content, "<p>") {
929 content = "<p>" + content
930 }
931 precis, _ := obj.GetString("summary")
932 if name, ok := obj.GetString("name"); ok {
933 if precis != "" {
934 content = precis + "<p>" + content
935 }
936 precis = html.EscapeString(name)
937 }
938 if sens, _ := obj["sensitive"].(bool); sens && precis == "" {
939 precis = "unspecified horror"
940 }
941 if waspage {
942 content += fmt.Sprintf(`<p><a href="%s">%s</a>`, url, url)
943 url = xid
944 }
945 if user.Options.InlineQuotes {
946 qurl, _ := obj.GetString("quoteUrl")
947 content = qutify(user, qurl, content)
948 }
949 rid, ok = obj.GetString("inReplyTo")
950 if !ok {
951 if robj, ok := obj.GetMap("inReplyTo"); ok {
952 rid, _ = robj.GetString("id")
953 }
954 }
955 convoy, _ = obj.GetString("context")
956 if convoy == "" {
957 convoy, _ = obj.GetString("conversation")
958 }
959 if ot == "Question" {
960 if what == "honk" {
961 what = "qonk"
962 }
963 content += "<ul>"
964 ans, _ := obj.GetArray("oneOf")
965 for _, ai := range ans {
966 a, ok := ai.(junk.Junk)
967 if !ok {
968 continue
969 }
970 as, _ := a.GetString("name")
971 content += "<li>" + as
972 }
973 ans, _ = obj.GetArray("anyOf")
974 for _, ai := range ans {
975 a, ok := ai.(junk.Junk)
976 if !ok {
977 continue
978 }
979 as, _ := a.GetString("name")
980 content += "<li>" + as
981 }
982 content += "</ul>"
983 }
984 if ot == "Move" {
985 targ, _ := obj.GetString("target")
986 content += string(templates.Sprintf(`<p>Moved to <a href="%s">%s</a>`, targ, targ))
987 }
988 if len(content) > 90001 {
989 ilog.Printf("content too long. truncating")
990 content = content[:90001]
991 }
992
993 xonk.Noise = content
994 xonk.Precis = precis
995 if rejectxonk(&xonk) {
996 dlog.Printf("fast reject: %s", xid)
997 return nil
998 }
999
1000 numatts := 0
1001 procatt := func(att junk.Junk) {
1002 at, _ := att.GetString("type")
1003 mt, _ := att.GetString("mediaType")
1004 if mt == "" {
1005 mt = "image"
1006 }
1007 u, ok := att.GetString("url")
1008 if !ok {
1009 u, ok = att.GetString("href")
1010 }
1011 if !ok {
1012 if ua, ok := att.GetArray("url"); ok && len(ua) > 0 {
1013 u, ok = ua[0].(string)
1014 if !ok {
1015 if uu, ok := ua[0].(junk.Junk); ok {
1016 u, _ = uu.GetString("href")
1017 if mt == "" {
1018 mt, _ = uu.GetString("mediaType")
1019 }
1020 }
1021 }
1022 } else if uu, ok := att.GetMap("url"); ok {
1023 u, _ = uu.GetString("href")
1024 if mt == "" {
1025 mt, _ = uu.GetString("mediaType")
1026 }
1027 }
1028 }
1029 name, _ := att.GetString("name")
1030 desc, _ := att.GetString("summary")
1031 desc = html.UnescapeString(desc)
1032 if desc == "" {
1033 desc = name
1034 }
1035 localize := false
1036 if at == "Document" || at == "Image" {
1037 mt = strings.ToLower(mt)
1038 dlog.Printf("attachment: %s %s", mt, u)
1039 if mt == "text/plain" || mt == "application/pdf" ||
1040 strings.HasPrefix(mt, "image") {
1041 if numatts > 4 {
1042 ilog.Printf("excessive attachment: %s", at)
1043 } else {
1044 localize = true
1045 }
1046 }
1047 } else if at == "Link" {
1048 if waspage {
1049 xonk.Noise += fmt.Sprintf(`<p><a href="%s">%s</a>`, u, u)
1050 return
1051 }
1052 if name == "" {
1053 name = u
1054 }
1055 } else {
1056 ilog.Printf("unknown attachment: %s", at)
1057 }
1058 if skipMedia(&xonk) {
1059 localize = false
1060 }
1061 if preferorig && !localize {
1062 return
1063 }
1064 donk := savedonk(u, name, desc, mt, localize)
1065 if donk != nil {
1066 xonk.Donks = append(xonk.Donks, donk)
1067 }
1068 numatts++
1069 }
1070 if img, ok := obj.GetMap("image"); ok {
1071 procatt(img)
1072 }
1073 if preferorig {
1074 atts, _ := obj.GetArray("url")
1075 for _, atti := range atts {
1076 att, ok := atti.(junk.Junk)
1077 if !ok {
1078 ilog.Printf("attachment that wasn't map?")
1079 continue
1080 }
1081 procatt(att)
1082 }
1083 if numatts == 0 {
1084 preferorig = false
1085 }
1086 }
1087 if !preferorig {
1088 atts := oneforall(obj, "attachment")
1089 for _, atti := range atts {
1090 att, ok := atti.(junk.Junk)
1091 if !ok {
1092 ilog.Printf("attachment that wasn't map?")
1093 continue
1094 }
1095 procatt(att)
1096 }
1097 }
1098 proctag := func(tag junk.Junk) {
1099 tt, _ := tag.GetString("type")
1100 name, _ := tag.GetString("name")
1101 desc, _ := tag.GetString("summary")
1102 desc = html.UnescapeString(desc)
1103 if desc == "" {
1104 desc = name
1105 }
1106 if tt == "Emoji" {
1107 icon, _ := tag.GetMap("icon")
1108 mt, _ := icon.GetString("mediaType")
1109 if mt == "" {
1110 mt = "image/png"
1111 }
1112 u, _ := icon.GetString("url")
1113 donk := savedonk(u, name, desc, mt, true)
1114 if donk != nil {
1115 xonk.Donks = append(xonk.Donks, donk)
1116 }
1117 }
1118 if tt == "Hashtag" {
1119 if name == "" || name == "#" {
1120 // skip it
1121 } else {
1122 if name[0] != '#' {
1123 name = "#" + name
1124 }
1125 xonk.Onts = append(xonk.Onts, name)
1126 }
1127 }
1128 if tt == "Place" {
1129 p := new(Place)
1130 p.Name = name
1131 p.Latitude, _ = tag.GetNumber("latitude")
1132 p.Longitude, _ = tag.GetNumber("longitude")
1133 p.Url, _ = tag.GetString("url")
1134 xonk.Place = p
1135 }
1136 if tt == "Mention" {
1137 var m Mention
1138 m.Who, _ = tag.GetString("name")
1139 m.Where, _ = tag.GetString("href")
1140 mentions = append(mentions, m)
1141 }
1142 }
1143 tags := oneforall(obj, "tag")
1144 for _, tagi := range tags {
1145 tag, ok := tagi.(junk.Junk)
1146 if !ok {
1147 continue
1148 }
1149 proctag(tag)
1150 }
1151 if starttime, ok := obj.GetString("startTime"); ok {
1152 if start, err := time.Parse(time.RFC3339, starttime); err == nil {
1153 t := new(Time)
1154 t.StartTime = start
1155 endtime, _ := obj.GetString("endTime")
1156 t.EndTime, _ = time.Parse(time.RFC3339, endtime)
1157 dura, _ := obj.GetString("duration")
1158 if strings.HasPrefix(dura, "PT") {
1159 dura = strings.ToLower(dura[2:])
1160 d, _ := time.ParseDuration(dura)
1161 t.Duration = Duration(d)
1162 }
1163 xonk.Time = t
1164 }
1165 }
1166 if loca, ok := obj.GetMap("location"); ok {
1167 if tt, _ := loca.GetString("type"); tt == "Place" {
1168 p := new(Place)
1169 p.Name, _ = loca.GetString("name")
1170 p.Latitude, _ = loca.GetNumber("latitude")
1171 p.Longitude, _ = loca.GetNumber("longitude")
1172 p.Url, _ = loca.GetString("url")
1173 xonk.Place = p
1174 }
1175 }
1176
1177 xonk.Onts = oneofakind(xonk.Onts)
1178 replyobj, ok := obj.GetMap("replies")
1179 if ok {
1180 items, ok := replyobj.GetArray("items")
1181 if !ok {
1182 first, ok := replyobj.GetMap("first")
1183 if ok {
1184 items, _ = first.GetArray("items")
1185 }
1186 }
1187 for _, repl := range items {
1188 s, ok := repl.(string)
1189 if ok {
1190 replies = append(replies, s)
1191 }
1192 }
1193 }
1194
1195 }
1196
1197 if currenttid == "" {
1198 currenttid = convoy
1199 }
1200
1201 // init xonk
1202 xonk.What = what
1203 xonk.RID = rid
1204 xonk.Date, _ = time.Parse(time.RFC3339, dt)
1205 if originate(url) == originate(xonk.XID) {
1206 xonk.URL = url
1207 }
1208 xonk.Format = "html"
1209 xonk.Convoy = convoy
1210 xonk.Mentions = mentions
1211 if myown {
1212 if xonk.Public {
1213 xonk.Whofore = 2
1214 } else {
1215 xonk.Whofore = 3
1216 }
1217 } else {
1218 for _, m := range mentions {
1219 if m.Where == user.URL {
1220 xonk.Whofore = 1
1221 }
1222 }
1223 }
1224 imaginate(&xonk)
1225
1226 if what == "chonk" {
1227 // undo damage above
1228 xonk.Noise = strings.TrimPrefix(xonk.Noise, "<p>")
1229 target := firstofmany(obj, "to")
1230 if target == user.URL {
1231 target = xonk.Honker
1232 }
1233 enc, _ := obj.GetString(chatKeyProp)
1234 if enc != "" {
1235 var dec string
1236 if pubkey, ok := getchatkey(xonk.Honker); ok {
1237 dec, err = decryptString(xonk.Noise, user.ChatSecKey, pubkey)
1238 if err != nil {
1239 ilog.Printf("failed to decrypt chonk")
1240 }
1241 }
1242 if err == nil {
1243 dlog.Printf("successful decrypt from %s", xonk.Honker)
1244 xonk.Noise = dec
1245 }
1246 }
1247 ch := Chonk{
1248 UserID: xonk.UserID,
1249 XID: xid,
1250 Who: xonk.Honker,
1251 Target: target,
1252 Date: xonk.Date,
1253 Noise: xonk.Noise,
1254 Format: xonk.Format,
1255 Donks: xonk.Donks,
1256 }
1257 savechonk(&ch)
1258 return nil
1259 }
1260
1261 if isUpdate {
1262 dlog.Printf("something has changed! %s", xonk.XID)
1263 prev := getxonk(user.ID, xonk.XID)
1264 if prev == nil {
1265 ilog.Printf("didn't find old version for update: %s", xonk.XID)
1266 isUpdate = false
1267 } else {
1268 xonk.ID = prev.ID
1269 updatehonk(&xonk)
1270 }
1271 }
1272 if !isUpdate && (myown || needxonk(user, &xonk)) {
1273 if rid != "" && xonk.Public {
1274 if needxonkid(user, rid) {
1275 goingup++
1276 saveonemore(rid)
1277 goingup--
1278 }
1279 if convoy == "" {
1280 xx := getxonk(user.ID, rid)
1281 if xx != nil {
1282 convoy = xx.Convoy
1283 }
1284 }
1285 }
1286 if convoy == "" {
1287 convoy = currenttid
1288 }
1289 if convoy == "" {
1290 convoy = xonk.XID
1291 currenttid = convoy
1292 }
1293 xonk.Convoy = convoy
1294 savexonk(&xonk)
1295 }
1296 if goingup == 0 {
1297 for _, replid := range replies {
1298 if needxonkid(user, replid) {
1299 dlog.Printf("missing a reply: %s", replid)
1300 saveonemore(replid)
1301 }
1302 }
1303 }
1304 return &xonk
1305 }
1306
1307 return xonkxonkfn2(item, origin, false, "", myown)
1308}
1309
1310func dumpactivity(item junk.Junk) {
1311 fd, err := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
1312 if err != nil {
1313 elog.Printf("error opening inbox! %s", err)
1314 return
1315 }
1316 defer fd.Close()
1317 item.Write(fd)
1318 io.WriteString(fd, "\n")
1319}
1320
1321func rubadubdub(user *WhatAbout, req junk.Junk) {
1322 actor, _ := req.GetString("actor")
1323 j := junk.New()
1324 j["@context"] = itiswhatitis
1325 j["id"] = user.URL + "/dub/" + xfiltrate()
1326 j["type"] = "Accept"
1327 j["actor"] = user.URL
1328 j["to"] = actor
1329 j["published"] = time.Now().UTC().Format(time.RFC3339)
1330 j["object"] = req
1331
1332 deliverate(user.ID, actor, j.ToBytes())
1333}
1334
1335func itakeitallback(user *WhatAbout, xid string, owner string, folxid string) {
1336 j := junk.New()
1337 j["@context"] = itiswhatitis
1338 j["id"] = user.URL + "/unsub/" + folxid
1339 j["type"] = "Undo"
1340 j["actor"] = user.URL
1341 j["to"] = owner
1342 f := junk.New()
1343 f["id"] = user.URL + "/sub/" + folxid
1344 f["type"] = "Follow"
1345 f["actor"] = user.URL
1346 f["to"] = owner
1347 f["object"] = xid
1348 j["object"] = f
1349 j["published"] = time.Now().UTC().Format(time.RFC3339)
1350
1351 deliverate(user.ID, owner, j.ToBytes())
1352}
1353
1354func subsub(user *WhatAbout, xid string, owner string, folxid string) {
1355 if xid == "" {
1356 ilog.Printf("can't subscribe to empty")
1357 return
1358 }
1359 j := junk.New()
1360 j["@context"] = itiswhatitis
1361 j["id"] = user.URL + "/sub/" + folxid
1362 j["type"] = "Follow"
1363 j["actor"] = user.URL
1364 j["to"] = owner
1365 j["object"] = xid
1366 j["published"] = time.Now().UTC().Format(time.RFC3339)
1367
1368 deliverate(user.ID, owner, j.ToBytes())
1369}
1370
1371func activatedonks(donks []*Donk) []junk.Junk {
1372 var atts []junk.Junk
1373 for _, d := range donks {
1374 if re_emus.MatchString(d.Name) {
1375 continue
1376 }
1377 jd := junk.New()
1378 jd["mediaType"] = d.Media
1379 jd["name"] = d.Name
1380 jd["summary"] = html.EscapeString(d.Desc)
1381 jd["type"] = "Document"
1382 jd["url"] = d.URL
1383 atts = append(atts, jd)
1384 }
1385 return atts
1386}
1387
1388// returns activity, object
1389func jonkjonk(user *WhatAbout, h *Honk) (junk.Junk, junk.Junk) {
1390 dt := h.Date.Format(time.RFC3339)
1391 var jo junk.Junk
1392 j := junk.New()
1393 j["id"] = user.URL + "/" + h.What + "/" + shortxid(h.XID)
1394 j["actor"] = h.Honker
1395 j["published"] = dt
1396 if h.Public && h.Honker == user.URL {
1397 h.Audience = append(h.Audience, user.URL+"/followers")
1398 }
1399 j["to"] = h.Audience[0]
1400 if len(h.Audience) > 1 {
1401 j["cc"] = h.Audience[1:]
1402 }
1403
1404 switch h.What {
1405 case "update":
1406 fallthrough
1407 case "event":
1408 fallthrough
1409 case "honk":
1410 j["type"] = "Create"
1411 jo = junk.New()
1412 jo["id"] = h.XID
1413 jo["type"] = "Note"
1414 if h.What == "event" {
1415 jo["type"] = "Event"
1416 }
1417 if h.What == "update" {
1418 j["type"] = "Update"
1419 jo["updated"] = dt
1420 }
1421 jo["published"] = dt
1422 jo["url"] = h.XID
1423 jo["attributedTo"] = h.Honker
1424 if h.RID != "" {
1425 jo["inReplyTo"] = h.RID
1426 }
1427 if h.Convoy != "" {
1428 jo["context"] = h.Convoy
1429 jo["conversation"] = h.Convoy
1430 }
1431 jo["to"] = h.Audience[0]
1432 if len(h.Audience) > 1 {
1433 jo["cc"] = h.Audience[1:]
1434 }
1435 if !h.Public {
1436 jo["directMessage"] = true
1437 }
1438 translate(h)
1439 redoimages(h)
1440 if h.Precis != "" {
1441 jo["sensitive"] = true
1442 }
1443
1444 var replies []string
1445 for _, reply := range h.Replies {
1446 replies = append(replies, reply.XID)
1447 }
1448 if len(replies) > 0 {
1449 jr := junk.New()
1450 jr["type"] = "Collection"
1451 jr["totalItems"] = len(replies)
1452 jr["items"] = replies
1453 jo["replies"] = jr
1454 }
1455
1456 var tags []junk.Junk
1457 for _, m := range h.Mentions {
1458 t := junk.New()
1459 t["type"] = "Mention"
1460 t["name"] = m.Who
1461 t["href"] = m.Where
1462 tags = append(tags, t)
1463 }
1464 for _, o := range h.Onts {
1465 t := junk.New()
1466 t["type"] = "Hashtag"
1467 o = strings.ToLower(o)
1468 t["href"] = serverURL("/o/%s", o[1:])
1469 t["name"] = o
1470 tags = append(tags, t)
1471 }
1472 for _, e := range herdofemus(h.Noise) {
1473 t := junk.New()
1474 t["id"] = e.ID
1475 t["type"] = "Emoji"
1476 t["name"] = e.Name
1477 i := junk.New()
1478 i["type"] = "Image"
1479 i["mediaType"] = e.Type
1480 i["url"] = e.ID
1481 t["icon"] = i
1482 tags = append(tags, t)
1483 }
1484 for _, e := range fixupflags(h) {
1485 t := junk.New()
1486 t["id"] = e.ID
1487 t["type"] = "Emoji"
1488 t["name"] = e.Name
1489 i := junk.New()
1490 i["type"] = "Image"
1491 i["mediaType"] = "image/png"
1492 i["url"] = e.ID
1493 t["icon"] = i
1494 tags = append(tags, t)
1495 }
1496 if len(tags) > 0 {
1497 jo["tag"] = tags
1498 }
1499 if p := h.Place; p != nil {
1500 t := junk.New()
1501 t["type"] = "Place"
1502 if p.Name != "" {
1503 t["name"] = p.Name
1504 }
1505 if p.Latitude != 0 {
1506 t["latitude"] = p.Latitude
1507 }
1508 if p.Longitude != 0 {
1509 t["longitude"] = p.Longitude
1510 }
1511 if p.Url != "" {
1512 t["url"] = p.Url
1513 }
1514 jo["location"] = t
1515 }
1516 if t := h.Time; t != nil {
1517 jo["startTime"] = t.StartTime.Format(time.RFC3339)
1518 if t.Duration != 0 {
1519 jo["duration"] = "PT" + strings.ToUpper(t.Duration.String())
1520 }
1521 }
1522 atts := activatedonks(h.Donks)
1523 if h.Link != "" {
1524 jo["type"] = "Page"
1525 jl := junk.New()
1526 jl["type"] = "Link"
1527 jl["href"] = h.Link
1528 atts = append(atts, jl)
1529 }
1530 if len(atts) > 0 {
1531 jo["attachment"] = atts
1532 }
1533 if h.LegalName != "" {
1534 jo["name"] = h.LegalName
1535 }
1536 if h.Precis != "" {
1537 jo["summary"] = h.Precis
1538 }
1539 jo["content"] = h.Noise
1540 j["object"] = jo
1541 case "bonk":
1542 j["type"] = "Announce"
1543 if h.Convoy != "" {
1544 j["context"] = h.Convoy
1545 }
1546 j["object"] = h.XID
1547 case "unbonk":
1548 b := junk.New()
1549 b["id"] = user.URL + "/" + "bonk" + "/" + shortxid(h.XID)
1550 b["type"] = "Announce"
1551 b["actor"] = user.URL
1552 if h.Convoy != "" {
1553 b["context"] = h.Convoy
1554 }
1555 b["object"] = h.XID
1556 j["type"] = "Undo"
1557 j["object"] = b
1558 case "zonk":
1559 j["type"] = "Delete"
1560 j["object"] = h.XID
1561 case "ack":
1562 j["type"] = "Read"
1563 j["object"] = h.XID
1564 if h.Convoy != "" {
1565 j["context"] = h.Convoy
1566 }
1567 case "react":
1568 j["type"] = "EmojiReact"
1569 j["object"] = h.XID
1570 if h.Convoy != "" {
1571 j["context"] = h.Convoy
1572 }
1573 j["content"] = h.Noise
1574 case "deack":
1575 b := junk.New()
1576 b["id"] = user.URL + "/" + "ack" + "/" + shortxid(h.XID)
1577 b["type"] = "Read"
1578 b["actor"] = user.URL
1579 b["object"] = h.XID
1580 if h.Convoy != "" {
1581 b["context"] = h.Convoy
1582 }
1583 j["type"] = "Undo"
1584 j["object"] = b
1585 }
1586
1587 return j, jo
1588}
1589
1590var oldjonks = gencache.New(gencache.Options[string, []byte]{Fill: func(xid string) ([]byte, bool) {
1591 row := stmtAnyXonk.QueryRow(xid)
1592 honk := scanhonk(row)
1593 if honk == nil || !honk.Public {
1594 return nil, true
1595 }
1596 user, _ := butwhatabout(honk.Username)
1597 rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy, 0)
1598 reversehonks(rawhonks)
1599 for _, h := range rawhonks {
1600 if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
1601 honk.Replies = append(honk.Replies, h)
1602 }
1603 }
1604 donksforhonks([]*Honk{honk})
1605 _, j := jonkjonk(user, honk)
1606 if j == nil {
1607 elog.Fatalf("what just happened? %v", honk)
1608 }
1609 j["@context"] = itiswhatitis
1610
1611 return j.ToBytes(), true
1612}, Limit: 128})
1613
1614func gimmejonk(xid string) ([]byte, bool) {
1615 j, ok := oldjonks.Get(xid)
1616 return j, ok
1617}
1618
1619func boxuprcpts(user *WhatAbout, addresses []string, useshared bool) map[string]bool {
1620 rcpts := make(map[string]bool)
1621 for _, a := range addresses {
1622 if a == "" || a == thewholeworld || a == user.URL || strings.HasSuffix(a, "/followers") {
1623 continue
1624 }
1625 if a[0] == '%' {
1626 rcpts[a] = true
1627 continue
1628 }
1629 box, ok := boxofboxes.Get(a)
1630 if ok && useshared && box.Shared != "" {
1631 rcpts["%"+box.Shared] = true
1632 } else {
1633 rcpts[a] = true
1634 }
1635 }
1636 return rcpts
1637}
1638
1639func chonkifymsg(user *WhatAbout, rcpt string, ch *Chonk) []byte {
1640 dt := ch.Date.Format(time.RFC3339)
1641 aud := []string{ch.Target}
1642
1643 jo := junk.New()
1644 jo["id"] = ch.XID
1645 jo["type"] = "ChatMessage"
1646 jo["published"] = dt
1647 jo["attributedTo"] = user.URL
1648 jo["to"] = aud
1649 content := string(ch.HTML)
1650 if user.ChatSecKey.key != nil {
1651 if pubkey, ok := getchatkey(rcpt); ok {
1652 var err error
1653 content, err = encryptString(content, user.ChatSecKey, pubkey)
1654 if err != nil {
1655 ilog.Printf("failure encrypting chonk: %s", err)
1656 }
1657 jo[chatKeyProp] = user.Options.ChatPubKey
1658 }
1659 }
1660 jo["content"] = content
1661
1662 atts := activatedonks(ch.Donks)
1663 if len(atts) > 0 {
1664 jo["attachment"] = atts
1665 }
1666 var tags []junk.Junk
1667 for _, e := range herdofemus(ch.Noise) {
1668 t := junk.New()
1669 t["id"] = e.ID
1670 t["type"] = "Emoji"
1671 t["name"] = e.Name
1672 i := junk.New()
1673 i["type"] = "Image"
1674 i["mediaType"] = e.Type
1675 i["url"] = e.ID
1676 t["icon"] = i
1677 tags = append(tags, t)
1678 }
1679 if len(tags) > 0 {
1680 jo["tag"] = tags
1681 }
1682
1683 j := junk.New()
1684 j["@context"] = itiswhatitis
1685 j["id"] = user.URL + "/" + "honk" + "/" + shortxid(ch.XID)
1686 j["type"] = "Create"
1687 j["actor"] = user.URL
1688 j["published"] = dt
1689 j["to"] = aud
1690 j["object"] = jo
1691
1692 return j.ToBytes()
1693}
1694
1695func sendchonk(user *WhatAbout, ch *Chonk) {
1696 rcpts := make(map[string]bool)
1697 rcpts[ch.Target] = true
1698 for a := range rcpts {
1699 msg := chonkifymsg(user, a, ch)
1700 go deliverate(user.ID, a, msg)
1701 }
1702}
1703
1704func honkworldwide(user *WhatAbout, honk *Honk) {
1705 jonk, _ := jonkjonk(user, honk)
1706 jonk["@context"] = itiswhatitis
1707 msg := jonk.ToBytes()
1708
1709 rcpts := boxuprcpts(user, honk.Audience, honk.Public)
1710
1711 if honk.Public {
1712 for _, h := range getdubs(user.ID) {
1713 if h.XID == user.URL {
1714 continue
1715 }
1716 box, ok := boxofboxes.Get(h.XID)
1717 if ok && box.Shared != "" {
1718 rcpts["%"+box.Shared] = true
1719 } else {
1720 rcpts[h.XID] = true
1721 }
1722 }
1723 for _, f := range getbacktracks(honk.XID) {
1724 if f[0] == '%' {
1725 rcpts[f] = true
1726 } else {
1727 box, ok := boxofboxes.Get(f)
1728 if ok && box.Shared != "" {
1729 rcpts["%"+box.Shared] = true
1730 } else {
1731 rcpts[f] = true
1732 }
1733 }
1734 }
1735 }
1736 for a := range rcpts {
1737 go deliverate(user.ID, a, msg)
1738 }
1739 if honk.Public && len(honk.Onts) > 0 {
1740 collectiveaction(honk)
1741 }
1742}
1743
1744func collectiveaction(honk *Honk) {
1745 user := getserveruser()
1746 for _, ont := range honk.Onts {
1747 dubs := getnameddubs(readyLuserOne, ont)
1748 if len(dubs) == 0 {
1749 continue
1750 }
1751 j := junk.New()
1752 j["@context"] = itiswhatitis
1753 j["type"] = "Add"
1754 j["id"] = user.URL + "/add/" + shortxid(ont+honk.XID)
1755 j["actor"] = user.URL
1756 j["object"] = honk.XID
1757 j["target"] = serverURL("/o/%s", ont[1:])
1758 rcpts := make(map[string]bool)
1759 for _, dub := range dubs {
1760 box, ok := boxofboxes.Get(dub.XID)
1761 if ok && box.Shared != "" {
1762 rcpts["%"+box.Shared] = true
1763 } else {
1764 rcpts[dub.XID] = true
1765 }
1766 }
1767 msg := j.ToBytes()
1768 for a := range rcpts {
1769 go deliverate(user.ID, a, msg)
1770 }
1771 }
1772}
1773
1774func junkuser(user *WhatAbout) junk.Junk {
1775 j := junk.New()
1776 j["@context"] = []string{itiswhatitis, papersplease}
1777 j["id"] = user.URL
1778 j["inbox"] = user.URL + "/inbox"
1779 j["outbox"] = user.URL + "/outbox"
1780 if user.Options.CustomDisplay != "" {
1781 user.Display = user.Options.CustomDisplay
1782 }
1783
1784 j["name"] = user.Display
1785 j["preferredUsername"] = user.Name
1786 j["summary"] = user.HTAbout
1787 var tags []junk.Junk
1788 for _, o := range user.Onts {
1789 t := junk.New()
1790 t["type"] = "Hashtag"
1791 o = strings.ToLower(o)
1792 t["href"] = serverURL("/o/%s", o[1:])
1793 t["name"] = o
1794 tags = append(tags, t)
1795 }
1796 if len(tags) > 0 {
1797 j["tag"] = tags
1798 }
1799
1800 if user.ID > 0 {
1801 j["type"] = "Person"
1802 j["url"] = fmt.Sprintf("https://%s/@%s", serverName, user.Name)
1803 j["followers"] = user.URL + "/followers"
1804 j["following"] = user.URL + "/following"
1805 a := junk.New()
1806 a["type"] = "Image"
1807 a["mediaType"] = "image/png"
1808 a["url"] = avatarURL(user)
1809 j["icon"] = a
1810 if ban := user.Options.Banner; ban != "" {
1811 a := junk.New()
1812 a["type"] = "Image"
1813 a["mediaType"] = "image/jpg"
1814 a["url"] = ban
1815 j["image"] = a
1816 }
1817 } else {
1818 j["type"] = "Service"
1819 }
1820 k := junk.New()
1821 k["id"] = user.URL + "#key"
1822 k["owner"] = user.URL
1823 k["publicKeyPem"] = user.Key
1824 j["publicKey"] = k
1825 j[chatKeyProp] = user.Options.ChatPubKey
1826
1827 return j
1828}
1829
1830var oldjonkers = gencache.New(gencache.Options[string, []byte]{Fill: func(name string) ([]byte, bool) {
1831 user, err := butwhatabout(name)
1832 if err != nil {
1833 return nil, false
1834 }
1835 j := junkuser(user)
1836 return j.ToBytes(), true
1837}, Duration: 1 * time.Minute})
1838
1839func asjonker(name string) ([]byte, bool) {
1840 j, ok := oldjonkers.Get(name)
1841 return j, ok
1842}
1843
1844var handfull = gencache.New(gencache.Options[string, string]{Fill: func(name string) (string, bool) {
1845 m := strings.Split(name, "@")
1846 if len(m) != 2 {
1847 dlog.Printf("bad fish name: %s", name)
1848 return "", true
1849 }
1850 var href string
1851 row := stmtGetXonker.QueryRow(name, "fishname")
1852 err := row.Scan(&href)
1853 if err == nil {
1854 return href, true
1855 }
1856 dlog.Printf("fishing for %s", name)
1857 j, err := GetJunkFast(readyLuserOne, fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1858 if err != nil {
1859 ilog.Printf("failed to go fish %s: %s", name, err)
1860 return "", true
1861 }
1862 links, _ := j.GetArray("links")
1863 for _, li := range links {
1864 l, ok := li.(junk.Junk)
1865 if !ok {
1866 continue
1867 }
1868 href, _ := l.GetString("href")
1869 rel, _ := l.GetString("rel")
1870 t, _ := l.GetString("type")
1871 if rel == "self" && friendorfoe(t) {
1872 when := time.Now().UTC().Format(dbtimeformat)
1873 _, err := stmtSaveXonker.Exec(name, href, "fishname", when)
1874 if err != nil {
1875 elog.Printf("error saving fishname: %s", err)
1876 }
1877 return href, true
1878 }
1879 }
1880 return href, true
1881}, Duration: 1 * time.Minute})
1882
1883func gofish(name string) string {
1884 if name[0] == '@' {
1885 name = name[1:]
1886 }
1887 href, _ := handfull.Get(name)
1888 return href
1889}
1890
1891func investigate(name string) (*SomeThing, error) {
1892 if name == "" {
1893 return nil, fmt.Errorf("no name")
1894 }
1895 if name[0] == '@' {
1896 name = gofish(name)
1897 }
1898 if name == "" {
1899 return nil, fmt.Errorf("no name")
1900 }
1901 obj, err := GetJunkFast(readyLuserOne, name)
1902 if err != nil {
1903 return nil, err
1904 }
1905 allinjest(originate(name), obj)
1906 return somethingabout(obj)
1907}
1908
1909func somethingabout(obj junk.Junk) (*SomeThing, error) {
1910 info := new(SomeThing)
1911 t, _ := obj.GetString("type")
1912 isowned := false
1913 switch t {
1914 case "Person":
1915 fallthrough
1916 case "Group":
1917 fallthrough
1918 case "Organization":
1919 fallthrough
1920 case "Application":
1921 fallthrough
1922 case "Service":
1923 info.What = SomeActor
1924 case "OrderedCollection":
1925 isowned = true
1926 fallthrough
1927 case "Collection":
1928 info.What = SomeCollection
1929 default:
1930 return nil, fmt.Errorf("unknown object type")
1931 }
1932 info.XID, _ = obj.GetString("id")
1933 info.Name, _ = obj.GetString("preferredUsername")
1934 if info.Name == "" {
1935 info.Name, _ = obj.GetString("name")
1936 }
1937 if isowned {
1938 info.Owner, _ = obj.GetString("attributedTo")
1939 }
1940 if info.Owner == "" {
1941 info.Owner = info.XID
1942 }
1943 return info, nil
1944}
1945
1946func allinjest(origin string, obj junk.Junk) {
1947 ident, _ := obj.GetString("id")
1948 if ident == "" {
1949 return
1950 }
1951 if originate(ident) != origin {
1952 return
1953 }
1954 keyobj, ok := obj.GetMap("publicKey")
1955 if ok {
1956 ingestpubkey(origin, keyobj)
1957 }
1958 ingestboxes(origin, obj)
1959 ingesthandle(origin, obj)
1960 chatkey, ok := obj.GetString(chatKeyProp)
1961 if ok {
1962 when := time.Now().UTC().Format(dbtimeformat)
1963 _, err := stmtSaveXonker.Exec(ident, chatkey, chatKeyProp, when)
1964 if err != nil {
1965 elog.Printf("error saving chatkey: %s", err)
1966 }
1967 }
1968}
1969
1970func ingestpubkey(origin string, obj junk.Junk) {
1971 keyobj, ok := obj.GetMap("publicKey")
1972 if ok {
1973 obj = keyobj
1974 }
1975 keyname, ok := obj.GetString("id")
1976 var data string
1977 row := stmtGetXonker.QueryRow(keyname, "pubkey")
1978 err := row.Scan(&data)
1979 if err == nil {
1980 return
1981 }
1982 if !ok || origin != originate(keyname) {
1983 ilog.Printf("bad key origin %s <> %s", origin, keyname)
1984 return
1985 }
1986 dlog.Printf("ingesting a needed pubkey: %s", keyname)
1987 owner, ok := obj.GetString("owner")
1988 if !ok {
1989 ilog.Printf("error finding %s pubkey owner", keyname)
1990 return
1991 }
1992 data, ok = obj.GetString("publicKeyPem")
1993 if !ok {
1994 ilog.Printf("error finding %s pubkey", keyname)
1995 return
1996 }
1997 if originate(owner) != origin {
1998 ilog.Printf("bad key owner: %s <> %s", owner, origin)
1999 return
2000 }
2001 _, _, err = httpsig.DecodeKey(data)
2002 if err != nil {
2003 ilog.Printf("error decoding %s pubkey: %s", keyname, err)
2004 return
2005 }
2006 when := time.Now().UTC().Format(dbtimeformat)
2007 _, err = stmtSaveXonker.Exec(keyname, data, "pubkey", when)
2008 if err != nil {
2009 elog.Printf("error saving key: %s", err)
2010 }
2011}
2012
2013func ingestboxes(origin string, obj junk.Junk) {
2014 ident, _ := obj.GetString("id")
2015 if ident == "" {
2016 return
2017 }
2018 if originate(ident) != origin {
2019 return
2020 }
2021 var info string
2022 row := stmtGetXonker.QueryRow(ident, "boxes")
2023 err := row.Scan(&info)
2024 if err == nil {
2025 return
2026 }
2027 dlog.Printf("ingesting boxes: %s", ident)
2028 inbox, _ := obj.GetString("inbox")
2029 outbox, _ := obj.GetString("outbox")
2030 sbox, _ := obj.GetString("endpoints", "sharedInbox")
2031 if inbox != "" {
2032 when := time.Now().UTC().Format(dbtimeformat)
2033 m := strings.Join([]string{inbox, outbox, sbox}, " ")
2034 _, err = stmtSaveXonker.Exec(ident, m, "boxes", when)
2035 if err != nil {
2036 elog.Printf("error saving boxes: %s", err)
2037 }
2038 }
2039}
2040
2041func ingesthandle(origin string, obj junk.Junk) {
2042 xid, _ := obj.GetString("id")
2043 if xid == "" {
2044 return
2045 }
2046 if originate(xid) != origin {
2047 return
2048 }
2049 var handle string
2050 row := stmtGetXonker.QueryRow(xid, "handle")
2051 err := row.Scan(&handle)
2052 if err == nil {
2053 return
2054 }
2055 handle, _ = obj.GetString("preferredUsername")
2056 if handle != "" {
2057 when := time.Now().UTC().Format(dbtimeformat)
2058 _, err = stmtSaveXonker.Exec(xid, handle, "handle", when)
2059 if err != nil {
2060 elog.Printf("error saving handle: %s", err)
2061 }
2062 }
2063}
2064
2065func updateMe(username string) {
2066 user, _ := somenamedusers.Get(username)
2067 dt := time.Now().UTC().Format(time.RFC3339)
2068 j := junk.New()
2069 j["@context"] = itiswhatitis
2070 j["id"] = fmt.Sprintf("%s/upme/%s/%d", user.URL, user.Name, time.Now().Unix())
2071 j["actor"] = user.URL
2072 j["published"] = dt
2073 j["to"] = thewholeworld
2074 j["type"] = "Update"
2075 j["object"] = junkuser(user)
2076
2077 msg := j.ToBytes()
2078
2079 rcpts := make(map[string]bool)
2080 for _, f := range getdubs(user.ID) {
2081 if f.XID == user.URL {
2082 continue
2083 }
2084 box, ok := boxofboxes.Get(f.XID)
2085 if ok && box.Shared != "" {
2086 rcpts["%"+box.Shared] = true
2087 } else {
2088 rcpts[f.XID] = true
2089 }
2090 }
2091 for a := range rcpts {
2092 go deliverate(user.ID, a, msg)
2093 }
2094}
2095
2096func followme(user *WhatAbout, who string, name string, j junk.Junk) {
2097 folxid, _ := j.GetString("id")
2098
2099 ilog.Printf("updating honker follow: %s %s", who, folxid)
2100
2101 var x string
2102 db := opendatabase()
2103 row := db.QueryRow("select xid from honkers where name = ? and xid = ? and userid = ? and flavor in ('dub', 'undub')", name, who, user.ID)
2104 err := row.Scan(&x)
2105 if err != sql.ErrNoRows {
2106 ilog.Printf("duplicate follow request: %s", who)
2107 _, err = stmtUpdateFlavor.Exec("dub", folxid, user.ID, name, who, "undub")
2108 if err != nil {
2109 elog.Printf("error updating honker: %s", err)
2110 }
2111 } else {
2112 stmtSaveDub.Exec(user.ID, name, who, "dub", folxid)
2113 }
2114 go rubadubdub(user, j)
2115}
2116
2117func unfollowme(user *WhatAbout, who string, name string, j junk.Junk) {
2118 var folxid string
2119 if who == "" {
2120 folxid, _ = j.GetString("object")
2121
2122 db := opendatabase()
2123 row := db.QueryRow("select xid, name from honkers where userid = ? and folxid = ? and flavor in ('dub', 'undub')", user.ID, folxid)
2124 err := row.Scan(&who, &name)
2125 if err != nil {
2126 if err != sql.ErrNoRows {
2127 elog.Printf("error scanning honker: %s", err)
2128 }
2129 return
2130 }
2131 }
2132
2133 ilog.Printf("updating honker undo: %s %s", who, folxid)
2134 _, err := stmtUpdateFlavor.Exec("undub", folxid, user.ID, name, who, "dub")
2135 if err != nil {
2136 elog.Printf("error updating honker: %s", err)
2137 return
2138 }
2139}
2140
2141func followyou(user *WhatAbout, honkerid int64, sync bool) {
2142 var url, owner string
2143 db := opendatabase()
2144 row := db.QueryRow("select xid, owner from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep', 'presub', 'sub')",
2145 honkerid, user.ID)
2146 err := row.Scan(&url, &owner)
2147 if err != nil {
2148 elog.Printf("can't get honker xid: %s", err)
2149 return
2150 }
2151 folxid := xfiltrate()
2152 ilog.Printf("subscribing to %s", url)
2153 _, err = db.Exec("update honkers set flavor = ?, folxid = ? where honkerid = ?", "presub", folxid, honkerid)
2154 if err != nil {
2155 elog.Printf("error updating honker: %s", err)
2156 return
2157 }
2158 if sync {
2159 subsub(user, url, owner, folxid)
2160 } else {
2161 go subsub(user, url, owner, folxid)
2162 }
2163
2164}
2165func unfollowyou(user *WhatAbout, honkerid int64, sync bool) {
2166 db := opendatabase()
2167 row := db.QueryRow("select xid, owner, folxid, flavor from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep', 'presub', 'sub')",
2168 honkerid, user.ID)
2169 var url, owner, folxid, flavor string
2170 err := row.Scan(&url, &owner, &folxid, &flavor)
2171 if err != nil {
2172 elog.Printf("can't get honker xid: %s", err)
2173 return
2174 }
2175 if flavor == "peep" {
2176 return
2177 }
2178 ilog.Printf("unsubscribing from %s", url)
2179 _, err = db.Exec("update honkers set flavor = ? where honkerid = ?", "unsub", honkerid)
2180 if err != nil {
2181 elog.Printf("error updating honker: %s", err)
2182 return
2183 }
2184 if sync {
2185 itakeitallback(user, url, owner, folxid)
2186 } else {
2187 go itakeitallback(user, url, owner, folxid)
2188 }
2189}
2190
2191func followyou2(user *WhatAbout, j junk.Junk) {
2192 who, _ := j.GetString("actor")
2193
2194 ilog.Printf("updating honker accept: %s", who)
2195 db := opendatabase()
2196 row := db.QueryRow("select name, folxid from honkers where userid = ? and xid = ? and flavor in ('presub', 'sub')",
2197 user.ID, who)
2198 var name, folxid string
2199 err := row.Scan(&name, &folxid)
2200 if err != nil {
2201 elog.Printf("can't get honker name: %s", err)
2202 return
2203 }
2204 _, err = stmtUpdateFlavor.Exec("sub", folxid, user.ID, name, who, "presub")
2205 if err != nil {
2206 elog.Printf("error updating honker: %s", err)
2207 return
2208 }
2209}
2210
2211func nofollowyou2(user *WhatAbout, j junk.Junk) {
2212 who, _ := j.GetString("actor")
2213
2214 ilog.Printf("updating honker reject: %s", who)
2215 db := opendatabase()
2216 row := db.QueryRow("select name, folxid from honkers where userid = ? and xid = ? and flavor in ('presub', 'sub')",
2217 user.ID, who)
2218 var name, folxid string
2219 err := row.Scan(&name, &folxid)
2220 if err != nil {
2221 elog.Printf("can't get honker name: %s", err)
2222 return
2223 }
2224 _, err = stmtUpdateFlavor.Exec("unsub", folxid, user.ID, name, who, "presub")
2225 _, err = stmtUpdateFlavor.Exec("unsub", folxid, user.ID, name, who, "sub")
2226 if err != nil {
2227 elog.Printf("error updating honker: %s", err)
2228 return
2229 }
2230}