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