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