extract.go (24397B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package ap 19 20 import ( 21 "crypto" 22 "crypto/rsa" 23 "crypto/x509" 24 "encoding/pem" 25 "fmt" 26 "net/url" 27 "strings" 28 "time" 29 30 "github.com/superseriousbusiness/activity/pub" 31 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 32 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 33 "github.com/superseriousbusiness/gotosocial/internal/util" 34 ) 35 36 // ExtractPreferredUsername returns a string representation of 37 // an interface's preferredUsername property. Will return an 38 // error if preferredUsername is nil, not a string, or empty. 39 func ExtractPreferredUsername(i WithPreferredUsername) (string, error) { 40 u := i.GetActivityStreamsPreferredUsername() 41 if u == nil || !u.IsXMLSchemaString() { 42 return "", gtserror.New("preferredUsername nil or not a string") 43 } 44 45 if u.GetXMLSchemaString() == "" { 46 return "", gtserror.New("preferredUsername was empty") 47 } 48 49 return u.GetXMLSchemaString(), nil 50 } 51 52 // ExtractName returns the first string representation it 53 // can find of an interface's name property, or an empty 54 // string if this is not found. 55 func ExtractName(i WithName) string { 56 nameProp := i.GetActivityStreamsName() 57 if nameProp == nil { 58 return "" 59 } 60 61 for iter := nameProp.Begin(); iter != nameProp.End(); iter = iter.Next() { 62 // Name may be parsed as IRI, depending on 63 // how it's formatted, so account for this. 64 switch { 65 case iter.IsXMLSchemaString(): 66 return iter.GetXMLSchemaString() 67 case iter.IsIRI(): 68 return iter.GetIRI().String() 69 } 70 } 71 72 return "" 73 } 74 75 // ExtractInReplyToURI extracts the first inReplyTo URI 76 // property it can find from an interface. Will return 77 // nil if no valid URI can be found. 78 func ExtractInReplyToURI(i WithInReplyTo) *url.URL { 79 inReplyToProp := i.GetActivityStreamsInReplyTo() 80 if inReplyToProp == nil { 81 return nil 82 } 83 84 for iter := inReplyToProp.Begin(); iter != inReplyToProp.End(); iter = iter.Next() { 85 iri, err := pub.ToId(iter) 86 if err == nil && iri != nil { 87 // Found one we can use. 88 return iri 89 } 90 } 91 92 return nil 93 } 94 95 // ExtractItemsURIs extracts each URI it can 96 // find for an item from the provided WithItems. 97 func ExtractItemsURIs(i WithItems) []*url.URL { 98 itemsProp := i.GetActivityStreamsItems() 99 if itemsProp == nil { 100 return nil 101 } 102 103 uris := make([]*url.URL, 0, itemsProp.Len()) 104 for iter := itemsProp.Begin(); iter != itemsProp.End(); iter = iter.Next() { 105 uri, err := pub.ToId(iter) 106 if err == nil { 107 // Found one we can use. 108 uris = append(uris, uri) 109 } 110 } 111 112 return uris 113 } 114 115 // ExtractToURIs returns a slice of URIs 116 // that the given WithTo addresses as To. 117 func ExtractToURIs(i WithTo) []*url.URL { 118 toProp := i.GetActivityStreamsTo() 119 if toProp == nil { 120 return nil 121 } 122 123 uris := make([]*url.URL, 0, toProp.Len()) 124 for iter := toProp.Begin(); iter != toProp.End(); iter = iter.Next() { 125 uri, err := pub.ToId(iter) 126 if err == nil { 127 // Found one we can use. 128 uris = append(uris, uri) 129 } 130 } 131 132 return uris 133 } 134 135 // ExtractCcURIs returns a slice of URIs 136 // that the given WithCC addresses as Cc. 137 func ExtractCcURIs(i WithCC) []*url.URL { 138 ccProp := i.GetActivityStreamsCc() 139 if ccProp == nil { 140 return nil 141 } 142 143 urls := make([]*url.URL, 0, ccProp.Len()) 144 for iter := ccProp.Begin(); iter != ccProp.End(); iter = iter.Next() { 145 uri, err := pub.ToId(iter) 146 if err == nil { 147 // Found one we can use. 148 urls = append(urls, uri) 149 } 150 } 151 152 return urls 153 } 154 155 // ExtractAttributedToURI returns the first URI it can find in the 156 // given WithAttributedTo, or an error if no URI can be found. 157 func ExtractAttributedToURI(i WithAttributedTo) (*url.URL, error) { 158 attributedToProp := i.GetActivityStreamsAttributedTo() 159 if attributedToProp == nil { 160 return nil, gtserror.New("attributedToProp was nil") 161 } 162 163 for iter := attributedToProp.Begin(); iter != attributedToProp.End(); iter = iter.Next() { 164 id, err := pub.ToId(iter) 165 if err == nil { 166 return id, nil 167 } 168 } 169 170 return nil, gtserror.New("couldn't find iri for attributed to") 171 } 172 173 // ExtractPublished extracts the published time from the given 174 // WithPublished. Will return an error if the published property 175 // is not set, is not a time.Time, or is zero. 176 func ExtractPublished(i WithPublished) (time.Time, error) { 177 t := time.Time{} 178 179 publishedProp := i.GetActivityStreamsPublished() 180 if publishedProp == nil { 181 return t, gtserror.New("published prop was nil") 182 } 183 184 if !publishedProp.IsXMLSchemaDateTime() { 185 return t, gtserror.New("published prop was not date time") 186 } 187 188 t = publishedProp.Get() 189 if t.IsZero() { 190 return t, gtserror.New("published time was zero") 191 } 192 193 return t, nil 194 } 195 196 // ExtractIconURI extracts the first URI it can find from 197 // the given WithIcon which links to a supported image file. 198 // Input will look something like this: 199 // 200 // "icon": { 201 // "mediaType": "image/jpeg", 202 // "type": "Image", 203 // "url": "http://example.org/path/to/some/file.jpeg" 204 // }, 205 // 206 // If no valid URI can be found, this will return an error. 207 func ExtractIconURI(i WithIcon) (*url.URL, error) { 208 iconProp := i.GetActivityStreamsIcon() 209 if iconProp == nil { 210 return nil, gtserror.New("icon property was nil") 211 } 212 213 // Icon can potentially contain multiple entries, 214 // so we iterate through all of them here in order 215 // to find the first one that meets these criteria: 216 // 217 // 1. Is an image. 218 // 2. Has a URL that we can use to derefereince it. 219 for iter := iconProp.Begin(); iter != iconProp.End(); iter = iter.Next() { 220 if !iter.IsActivityStreamsImage() { 221 continue 222 } 223 224 image := iter.GetActivityStreamsImage() 225 if image == nil { 226 continue 227 } 228 229 imageURL, err := ExtractURL(image) 230 if err == nil && imageURL != nil { 231 return imageURL, nil 232 } 233 } 234 235 return nil, gtserror.New("could not extract valid image URI from icon") 236 } 237 238 // ExtractImageURI extracts the first URI it can find from 239 // the given WithImage which links to a supported image file. 240 // Input will look something like this: 241 // 242 // "image": { 243 // "mediaType": "image/jpeg", 244 // "type": "Image", 245 // "url": "http://example.org/path/to/some/file.jpeg" 246 // }, 247 // 248 // If no valid URI can be found, this will return an error. 249 func ExtractImageURI(i WithImage) (*url.URL, error) { 250 imageProp := i.GetActivityStreamsImage() 251 if imageProp == nil { 252 return nil, gtserror.New("image property was nil") 253 } 254 255 // Image can potentially contain multiple entries, 256 // so we iterate through all of them here in order 257 // to find the first one that meets these criteria: 258 // 259 // 1. Is an image. 260 // 2. Has a URL that we can use to derefereince it. 261 for iter := imageProp.Begin(); iter != imageProp.End(); iter = iter.Next() { 262 if !iter.IsActivityStreamsImage() { 263 continue 264 } 265 266 image := iter.GetActivityStreamsImage() 267 if image == nil { 268 continue 269 } 270 271 imageURL, err := ExtractURL(image) 272 if err == nil && imageURL != nil { 273 return imageURL, nil 274 } 275 } 276 277 return nil, gtserror.New("could not extract valid image URI from image") 278 } 279 280 // ExtractSummary extracts the summary/content warning of 281 // the given WithSummary interface. Will return an empty 282 // string if no summary/content warning was present. 283 func ExtractSummary(i WithSummary) string { 284 summaryProp := i.GetActivityStreamsSummary() 285 if summaryProp == nil { 286 return "" 287 } 288 289 for iter := summaryProp.Begin(); iter != summaryProp.End(); iter = iter.Next() { 290 // Summary may be parsed as IRI, depending on 291 // how it's formatted, so account for this. 292 switch { 293 case iter.IsXMLSchemaString(): 294 return iter.GetXMLSchemaString() 295 case iter.IsIRI(): 296 return iter.GetIRI().String() 297 } 298 } 299 300 return "" 301 } 302 303 // ExtractFields extracts property/value fields from the given 304 // WithAttachment interface. Will return an empty slice if no 305 // property/value fields can be found. Attachments that are not 306 // (well-formed) PropertyValues will be ignored. 307 func ExtractFields(i WithAttachment) []*gtsmodel.Field { 308 attachmentProp := i.GetActivityStreamsAttachment() 309 if attachmentProp == nil { 310 // Nothing to do. 311 return nil 312 } 313 314 l := attachmentProp.Len() 315 if l == 0 { 316 // Nothing to do. 317 return nil 318 } 319 320 fields := make([]*gtsmodel.Field, 0, l) 321 for iter := attachmentProp.Begin(); iter != attachmentProp.End(); iter = iter.Next() { 322 if !iter.IsSchemaPropertyValue() { 323 continue 324 } 325 326 propertyValue := iter.GetSchemaPropertyValue() 327 if propertyValue == nil { 328 continue 329 } 330 331 nameProp := propertyValue.GetActivityStreamsName() 332 if nameProp == nil || nameProp.Len() != 1 { 333 continue 334 } 335 336 name := nameProp.At(0).GetXMLSchemaString() 337 if name == "" { 338 continue 339 } 340 341 valueProp := propertyValue.GetSchemaValue() 342 if valueProp == nil || !valueProp.IsXMLSchemaString() { 343 continue 344 } 345 346 value := valueProp.Get() 347 if value == "" { 348 continue 349 } 350 351 fields = append(fields, >smodel.Field{ 352 Name: name, 353 Value: value, 354 }) 355 } 356 357 return fields 358 } 359 360 // ExtractDiscoverable extracts the Discoverable boolean 361 // of the given WithDiscoverable interface. Will return 362 // an error if Discoverable was nil. 363 func ExtractDiscoverable(i WithDiscoverable) (bool, error) { 364 discoverableProp := i.GetTootDiscoverable() 365 if discoverableProp == nil { 366 return false, gtserror.New("discoverable was nil") 367 } 368 369 return discoverableProp.Get(), nil 370 } 371 372 // ExtractURL extracts the first URI it can find from the 373 // given WithURL interface, or an error if no URL was set. 374 // The ID of a type will not work, this function wants a URI 375 // specifically. 376 func ExtractURL(i WithURL) (*url.URL, error) { 377 urlProp := i.GetActivityStreamsUrl() 378 if urlProp == nil { 379 return nil, gtserror.New("url property was nil") 380 } 381 382 for iter := urlProp.Begin(); iter != urlProp.End(); iter = iter.Next() { 383 if !iter.IsIRI() { 384 continue 385 } 386 387 // Found it. 388 return iter.GetIRI(), nil 389 } 390 391 return nil, gtserror.New("no valid URL property found") 392 } 393 394 // ExtractPublicKey extracts the public key, public key ID, and public 395 // key owner ID from an interface, or an error if something goes wrong. 396 func ExtractPublicKey(i WithPublicKey) ( 397 *rsa.PublicKey, // pubkey 398 *url.URL, // pubkey ID 399 *url.URL, // pubkey owner 400 error, 401 ) { 402 pubKeyProp := i.GetW3IDSecurityV1PublicKey() 403 if pubKeyProp == nil { 404 return nil, nil, nil, gtserror.New("public key property was nil") 405 } 406 407 for iter := pubKeyProp.Begin(); iter != pubKeyProp.End(); iter = iter.Next() { 408 if !iter.IsW3IDSecurityV1PublicKey() { 409 continue 410 } 411 412 pkey := iter.Get() 413 if pkey == nil { 414 continue 415 } 416 417 pubKeyID, err := pub.GetId(pkey) 418 if err != nil { 419 continue 420 } 421 422 pubKeyOwnerProp := pkey.GetW3IDSecurityV1Owner() 423 if pubKeyOwnerProp == nil { 424 continue 425 } 426 427 pubKeyOwner := pubKeyOwnerProp.GetIRI() 428 if pubKeyOwner == nil { 429 continue 430 } 431 432 pubKeyPemProp := pkey.GetW3IDSecurityV1PublicKeyPem() 433 if pubKeyPemProp == nil { 434 continue 435 } 436 437 pkeyPem := pubKeyPemProp.Get() 438 if pkeyPem == "" { 439 continue 440 } 441 442 block, _ := pem.Decode([]byte(pkeyPem)) 443 if block == nil { 444 continue 445 } 446 447 var p crypto.PublicKey 448 switch block.Type { 449 case "PUBLIC KEY": 450 p, err = x509.ParsePKIXPublicKey(block.Bytes) 451 case "RSA PUBLIC KEY": 452 p, err = x509.ParsePKCS1PublicKey(block.Bytes) 453 default: 454 err = fmt.Errorf("unknown block type: %q", block.Type) 455 } 456 if err != nil { 457 err = gtserror.Newf("could not parse public key from block bytes: %w", err) 458 return nil, nil, nil, err 459 } 460 461 if p == nil { 462 return nil, nil, nil, gtserror.New("returned public key was empty") 463 } 464 465 pubKey, ok := p.(*rsa.PublicKey) 466 if !ok { 467 continue 468 } 469 470 return pubKey, pubKeyID, pubKeyOwner, nil 471 } 472 473 return nil, nil, nil, gtserror.New("couldn't find public key") 474 } 475 476 // ExtractContent returns a string representation of the 477 // given interface's Content property, or an empty string 478 // if no Content is found. 479 func ExtractContent(i WithContent) string { 480 contentProperty := i.GetActivityStreamsContent() 481 if contentProperty == nil { 482 return "" 483 } 484 485 for iter := contentProperty.Begin(); iter != contentProperty.End(); iter = iter.Next() { 486 switch { 487 // Content may be parsed as IRI, depending on 488 // how it's formatted, so account for this. 489 case iter.IsXMLSchemaString(): 490 return iter.GetXMLSchemaString() 491 case iter.IsIRI(): 492 return iter.GetIRI().String() 493 } 494 } 495 496 return "" 497 } 498 499 // ExtractAttachment extracts a minimal gtsmodel.Attachment 500 // (just remote URL, description, and blurhash) from the given 501 // Attachmentable interface, or an error if no remote URL is set. 502 func ExtractAttachment(i Attachmentable) (*gtsmodel.MediaAttachment, error) { 503 // Get the URL for the attachment file. 504 // If no URL is set, we can't do anything. 505 remoteURL, err := ExtractURL(i) 506 if err != nil { 507 return nil, gtserror.Newf("error extracting attachment URL: %w", err) 508 } 509 510 return >smodel.MediaAttachment{ 511 RemoteURL: remoteURL.String(), 512 Description: ExtractName(i), 513 Blurhash: ExtractBlurhash(i), 514 Processing: gtsmodel.ProcessingStatusReceived, 515 }, nil 516 } 517 518 // ExtractBlurhash extracts the blurhash string value 519 // from the given WithBlurhash interface, or returns 520 // an empty string if nothing is found. 521 func ExtractBlurhash(i WithBlurhash) string { 522 blurhashProp := i.GetTootBlurhash() 523 if blurhashProp == nil { 524 return "" 525 } 526 527 return blurhashProp.Get() 528 } 529 530 // ExtractHashtags extracts a slice of minimal gtsmodel.Tags 531 // from a WithTag. If an entry in the WithTag is not a hashtag, 532 // it will be quietly ignored. 533 // 534 // TODO: find a better heuristic for determining if something 535 // is a hashtag or not, since looking for type name "Hashtag" 536 // is non-normative. Perhaps look for things that are either 537 // type "Hashtag" or have no type name set at all? 538 func ExtractHashtags(i WithTag) ([]*gtsmodel.Tag, error) { 539 tagsProp := i.GetActivityStreamsTag() 540 if tagsProp == nil { 541 return nil, nil 542 } 543 544 var ( 545 l = tagsProp.Len() 546 tags = make([]*gtsmodel.Tag, 0, l) 547 keys = make(map[string]any, l) // Use map to dedupe items. 548 ) 549 550 for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() { 551 t := iter.GetType() 552 if t == nil { 553 continue 554 } 555 556 if t.GetTypeName() != TagHashtag { 557 continue 558 } 559 560 hashtaggable, ok := t.(Hashtaggable) 561 if !ok { 562 continue 563 } 564 565 tag, err := ExtractHashtag(hashtaggable) 566 if err != nil { 567 continue 568 } 569 570 // Only append this tag if we haven't 571 // seen it already, to avoid duplicates 572 // in the slice. 573 if _, set := keys[tag.URL]; !set { 574 keys[tag.URL] = nil // Value doesn't matter. 575 tags = append(tags, tag) 576 tags = append(tags, tag) 577 tags = append(tags, tag) 578 } 579 } 580 581 return tags, nil 582 } 583 584 // ExtractEmoji extracts a minimal gtsmodel.Tag 585 // from the given Hashtaggable. 586 func ExtractHashtag(i Hashtaggable) (*gtsmodel.Tag, error) { 587 // Extract href/link for this tag. 588 hrefProp := i.GetActivityStreamsHref() 589 if hrefProp == nil || !hrefProp.IsIRI() { 590 return nil, gtserror.New("no href prop") 591 } 592 tagURL := hrefProp.GetIRI().String() 593 594 // Extract name for the tag; trim leading hash 595 // character, so '#example' becomes 'example'. 596 name := ExtractName(i) 597 if name == "" { 598 return nil, gtserror.New("name prop empty") 599 } 600 tagName := strings.TrimPrefix(name, "#") 601 602 return >smodel.Tag{ 603 URL: tagURL, 604 Name: tagName, 605 }, nil 606 } 607 608 // ExtractEmojis extracts a slice of minimal gtsmodel.Emojis 609 // from a WithTag. If an entry in the WithTag is not an emoji, 610 // it will be quietly ignored. 611 func ExtractEmojis(i WithTag) ([]*gtsmodel.Emoji, error) { 612 tagsProp := i.GetActivityStreamsTag() 613 if tagsProp == nil { 614 return nil, nil 615 } 616 617 var ( 618 l = tagsProp.Len() 619 emojis = make([]*gtsmodel.Emoji, 0, l) 620 keys = make(map[string]any, l) // Use map to dedupe items. 621 ) 622 623 for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() { 624 if !iter.IsTootEmoji() { 625 continue 626 } 627 628 tootEmoji := iter.GetTootEmoji() 629 if tootEmoji == nil { 630 continue 631 } 632 633 emoji, err := ExtractEmoji(tootEmoji) 634 if err != nil { 635 return nil, err 636 } 637 638 // Only append this emoji if we haven't 639 // seen it already, to avoid duplicates 640 // in the slice. 641 if _, set := keys[emoji.URI]; !set { 642 keys[emoji.URI] = nil // Value doesn't matter. 643 emojis = append(emojis, emoji) 644 } 645 } 646 647 return emojis, nil 648 } 649 650 // ExtractEmoji extracts a minimal gtsmodel.Emoji 651 // from the given Emojiable. 652 func ExtractEmoji(i Emojiable) (*gtsmodel.Emoji, error) { 653 // Use AP ID as emoji URI. 654 idProp := i.GetJSONLDId() 655 if idProp == nil || !idProp.IsIRI() { 656 return nil, gtserror.New("no id for emoji") 657 } 658 uri := idProp.GetIRI() 659 660 // Extract emoji last updated time (optional). 661 var updatedAt time.Time 662 updatedProp := i.GetActivityStreamsUpdated() 663 if updatedProp != nil && updatedProp.IsXMLSchemaDateTime() { 664 updatedAt = updatedProp.Get() 665 } 666 667 // Extract emoji name aka shortcode. 668 name := ExtractName(i) 669 if name == "" { 670 return nil, gtserror.New("name prop empty") 671 } 672 shortcode := strings.Trim(name, ":") 673 674 // Extract emoji image URL from Icon property. 675 imageRemoteURL, err := ExtractIconURI(i) 676 if err != nil { 677 return nil, gtserror.New("no url for emoji image") 678 } 679 imageRemoteURLStr := imageRemoteURL.String() 680 681 return >smodel.Emoji{ 682 UpdatedAt: updatedAt, 683 Shortcode: shortcode, 684 Domain: uri.Host, 685 ImageRemoteURL: imageRemoteURLStr, 686 URI: uri.String(), 687 Disabled: new(bool), // Assume false by default. 688 VisibleInPicker: new(bool), // Assume false by default. 689 }, nil 690 } 691 692 // ExtractMentions extracts a slice of minimal gtsmodel.Mentions 693 // from a WithTag. If an entry in the WithTag is not a mention, 694 // it will be quietly ignored. 695 func ExtractMentions(i WithTag) ([]*gtsmodel.Mention, error) { 696 tagsProp := i.GetActivityStreamsTag() 697 if tagsProp == nil { 698 return nil, nil 699 } 700 701 var ( 702 l = tagsProp.Len() 703 mentions = make([]*gtsmodel.Mention, 0, l) 704 keys = make(map[string]any, l) // Use map to dedupe items. 705 ) 706 707 for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() { 708 if !iter.IsActivityStreamsMention() { 709 continue 710 } 711 712 asMention := iter.GetActivityStreamsMention() 713 if asMention == nil { 714 continue 715 } 716 717 mention, err := ExtractMention(asMention) 718 if err != nil { 719 return nil, err 720 } 721 722 // Only append this mention if we haven't 723 // seen it already, to avoid duplicates 724 // in the slice. 725 if _, set := keys[mention.TargetAccountURI]; !set { 726 keys[mention.TargetAccountURI] = nil // Value doesn't matter. 727 mentions = append(mentions, mention) 728 } 729 } 730 731 return mentions, nil 732 } 733 734 // ExtractMention extracts a minimal gtsmodel.Mention from a Mentionable. 735 func ExtractMention(i Mentionable) (*gtsmodel.Mention, error) { 736 nameString := ExtractName(i) 737 if nameString == "" { 738 return nil, gtserror.New("name prop empty") 739 } 740 741 // Ensure namestring is valid so we 742 // can handle it properly later on. 743 if _, _, err := util.ExtractNamestringParts(nameString); err != nil { 744 return nil, err 745 } 746 747 // The href prop should be the AP URI 748 // of the target account. 749 hrefProp := i.GetActivityStreamsHref() 750 if hrefProp == nil || !hrefProp.IsIRI() { 751 return nil, gtserror.New("no href prop") 752 } 753 754 return >smodel.Mention{ 755 NameString: nameString, 756 TargetAccountURI: hrefProp.GetIRI().String(), 757 }, nil 758 } 759 760 // ExtractActorURI extracts the first Actor URI 761 // it can find from a WithActor interface. 762 func ExtractActorURI(withActor WithActor) (*url.URL, error) { 763 actorProp := withActor.GetActivityStreamsActor() 764 if actorProp == nil { 765 return nil, gtserror.New("actor property was nil") 766 } 767 768 for iter := actorProp.Begin(); iter != actorProp.End(); iter = iter.Next() { 769 id, err := pub.ToId(iter) 770 if err == nil { 771 // Found one we can use. 772 return id, nil 773 } 774 } 775 776 return nil, gtserror.New("no iri found for actor prop") 777 } 778 779 // ExtractObjectURI extracts the first Object URI 780 // it can find from a WithObject interface. 781 func ExtractObjectURI(withObject WithObject) (*url.URL, error) { 782 objectProp := withObject.GetActivityStreamsObject() 783 if objectProp == nil { 784 return nil, gtserror.New("object property was nil") 785 } 786 787 for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() { 788 id, err := pub.ToId(iter) 789 if err == nil { 790 // Found one we can use. 791 return id, nil 792 } 793 } 794 795 return nil, gtserror.New("no iri found for object prop") 796 } 797 798 // ExtractObjectURIs extracts the URLs of each Object 799 // it can find from a WithObject interface. 800 func ExtractObjectURIs(withObject WithObject) ([]*url.URL, error) { 801 objectProp := withObject.GetActivityStreamsObject() 802 if objectProp == nil { 803 return nil, gtserror.New("object property was nil") 804 } 805 806 urls := make([]*url.URL, 0, objectProp.Len()) 807 for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() { 808 id, err := pub.ToId(iter) 809 if err == nil { 810 // Found one we can use. 811 urls = append(urls, id) 812 } 813 } 814 815 return urls, nil 816 } 817 818 // ExtractVisibility extracts the gtsmodel.Visibility 819 // of a given addressable with a To and CC property. 820 // 821 // ActorFollowersURI is needed to check whether the 822 // visibility is FollowersOnly or not. The passed-in 823 // value should just be the string value representation 824 // of the followers URI of the actor who created the activity, 825 // eg., `https://example.org/users/whoever/followers`. 826 func ExtractVisibility(addressable Addressable, actorFollowersURI string) (gtsmodel.Visibility, error) { 827 var ( 828 to = ExtractToURIs(addressable) 829 cc = ExtractCcURIs(addressable) 830 ) 831 832 if len(to) == 0 && len(cc) == 0 { 833 return "", gtserror.Newf("message wasn't TO or CC anyone") 834 } 835 836 // Assume most restrictive visibility, 837 // and work our way up from there. 838 visibility := gtsmodel.VisibilityDirect 839 840 if isFollowers(to, actorFollowersURI) { 841 // Followers in TO: it's at least followers only. 842 visibility = gtsmodel.VisibilityFollowersOnly 843 } 844 845 if isPublic(cc) { 846 // CC'd to public: it's at least unlocked. 847 visibility = gtsmodel.VisibilityUnlocked 848 } 849 850 if isPublic(to) { 851 // TO'd to public: it's a public post. 852 visibility = gtsmodel.VisibilityPublic 853 } 854 855 return visibility, nil 856 } 857 858 // ExtractSensitive extracts whether or not an item should 859 // be marked as sensitive according to its ActivityStreams 860 // sensitive property. 861 // 862 // If no sensitive property is set on the item at all, or 863 // if this property isn't a boolean, then false will be 864 // returned by default. 865 func ExtractSensitive(withSensitive WithSensitive) bool { 866 sensitiveProp := withSensitive.GetActivityStreamsSensitive() 867 if sensitiveProp == nil { 868 return false 869 } 870 871 for iter := sensitiveProp.Begin(); iter != sensitiveProp.End(); iter = iter.Next() { 872 if iter.IsXMLSchemaBoolean() { 873 return iter.Get() 874 } 875 } 876 877 return false 878 } 879 880 // ExtractSharedInbox extracts the sharedInbox URI property 881 // from an Actor. Returns nil if this property is not set. 882 func ExtractSharedInbox(withEndpoints WithEndpoints) *url.URL { 883 endpointsProp := withEndpoints.GetActivityStreamsEndpoints() 884 if endpointsProp == nil { 885 return nil 886 } 887 888 for iter := endpointsProp.Begin(); iter != endpointsProp.End(); iter = iter.Next() { 889 if !iter.IsActivityStreamsEndpoints() { 890 continue 891 } 892 893 endpoints := iter.Get() 894 if endpoints == nil { 895 continue 896 } 897 898 sharedInboxProp := endpoints.GetActivityStreamsSharedInbox() 899 if sharedInboxProp == nil || !sharedInboxProp.IsIRI() { 900 continue 901 } 902 903 return sharedInboxProp.GetIRI() 904 } 905 906 return nil 907 } 908 909 // isPublic checks if at least one entry in the given 910 // uris slice equals the activitystreams public uri. 911 func isPublic(uris []*url.URL) bool { 912 for _, uri := range uris { 913 if pub.IsPublic(uri.String()) { 914 return true 915 } 916 } 917 918 return false 919 } 920 921 // isFollowers checks if at least one entry in the given 922 // uris slice equals the given followersURI. 923 func isFollowers(uris []*url.URL, followersURI string) bool { 924 for _, uri := range uris { 925 if strings.EqualFold(uri.String(), followersURI) { 926 return true 927 } 928 } 929 930 return false 931 }