internaltoas.go (49202B)
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 typeutils 19 20 import ( 21 "context" 22 "crypto/x509" 23 "encoding/pem" 24 "errors" 25 "fmt" 26 "net/url" 27 28 "github.com/superseriousbusiness/activity/pub" 29 "github.com/superseriousbusiness/activity/streams" 30 "github.com/superseriousbusiness/activity/streams/vocab" 31 "github.com/superseriousbusiness/gotosocial/internal/config" 32 "github.com/superseriousbusiness/gotosocial/internal/db" 33 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 34 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 35 "github.com/superseriousbusiness/gotosocial/internal/log" 36 ) 37 38 // Converts a gts model account into an Activity Streams person type. 39 func (c *converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) { 40 person := streams.NewActivityStreamsPerson() 41 42 // id should be the activitypub URI of this user 43 // something like https://example.org/users/example_user 44 profileIDURI, err := url.Parse(a.URI) 45 if err != nil { 46 return nil, err 47 } 48 idProp := streams.NewJSONLDIdProperty() 49 idProp.SetIRI(profileIDURI) 50 person.SetJSONLDId(idProp) 51 52 // following 53 // The URI for retrieving a list of accounts this user is following 54 followingURI, err := url.Parse(a.FollowingURI) 55 if err != nil { 56 return nil, err 57 } 58 followingProp := streams.NewActivityStreamsFollowingProperty() 59 followingProp.SetIRI(followingURI) 60 person.SetActivityStreamsFollowing(followingProp) 61 62 // followers 63 // The URI for retrieving a list of this user's followers 64 followersURI, err := url.Parse(a.FollowersURI) 65 if err != nil { 66 return nil, err 67 } 68 followersProp := streams.NewActivityStreamsFollowersProperty() 69 followersProp.SetIRI(followersURI) 70 person.SetActivityStreamsFollowers(followersProp) 71 72 // inbox 73 // the activitypub inbox of this user for accepting messages 74 inboxURI, err := url.Parse(a.InboxURI) 75 if err != nil { 76 return nil, err 77 } 78 inboxProp := streams.NewActivityStreamsInboxProperty() 79 inboxProp.SetIRI(inboxURI) 80 person.SetActivityStreamsInbox(inboxProp) 81 82 // shared inbox -- only add this if we know for sure it has one 83 if a.SharedInboxURI != nil && *a.SharedInboxURI != "" { 84 sharedInboxURI, err := url.Parse(*a.SharedInboxURI) 85 if err != nil { 86 return nil, err 87 } 88 endpointsProp := streams.NewActivityStreamsEndpointsProperty() 89 endpoints := streams.NewActivityStreamsEndpoints() 90 sharedInboxProp := streams.NewActivityStreamsSharedInboxProperty() 91 sharedInboxProp.SetIRI(sharedInboxURI) 92 endpoints.SetActivityStreamsSharedInbox(sharedInboxProp) 93 endpointsProp.AppendActivityStreamsEndpoints(endpoints) 94 person.SetActivityStreamsEndpoints(endpointsProp) 95 } 96 97 // outbox 98 // the activitypub outbox of this user for serving messages 99 outboxURI, err := url.Parse(a.OutboxURI) 100 if err != nil { 101 return nil, err 102 } 103 outboxProp := streams.NewActivityStreamsOutboxProperty() 104 outboxProp.SetIRI(outboxURI) 105 person.SetActivityStreamsOutbox(outboxProp) 106 107 // featured posts 108 // Pinned posts. 109 featuredURI, err := url.Parse(a.FeaturedCollectionURI) 110 if err != nil { 111 return nil, err 112 } 113 featuredProp := streams.NewTootFeaturedProperty() 114 featuredProp.SetIRI(featuredURI) 115 person.SetTootFeatured(featuredProp) 116 117 // featuredTags 118 // NOT IMPLEMENTED 119 120 // preferredUsername 121 // Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI. 122 preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty() 123 preferredUsernameProp.SetXMLSchemaString(a.Username) 124 person.SetActivityStreamsPreferredUsername(preferredUsernameProp) 125 126 // name 127 // Used as profile display name. 128 nameProp := streams.NewActivityStreamsNameProperty() 129 if a.Username != "" { 130 nameProp.AppendXMLSchemaString(a.DisplayName) 131 } else { 132 nameProp.AppendXMLSchemaString(a.Username) 133 } 134 person.SetActivityStreamsName(nameProp) 135 136 // summary 137 // Used as profile bio. 138 if a.Note != "" { 139 summaryProp := streams.NewActivityStreamsSummaryProperty() 140 summaryProp.AppendXMLSchemaString(a.Note) 141 person.SetActivityStreamsSummary(summaryProp) 142 } 143 144 // url 145 // Used as profile link. 146 profileURL, err := url.Parse(a.URL) 147 if err != nil { 148 return nil, err 149 } 150 urlProp := streams.NewActivityStreamsUrlProperty() 151 urlProp.AppendIRI(profileURL) 152 person.SetActivityStreamsUrl(urlProp) 153 154 // manuallyApprovesFollowers 155 // Will be shown as a locked account. 156 manuallyApprovesFollowersProp := streams.NewActivityStreamsManuallyApprovesFollowersProperty() 157 manuallyApprovesFollowersProp.Set(*a.Locked) 158 person.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp) 159 160 // discoverable 161 // Will be shown in the profile directory. 162 discoverableProp := streams.NewTootDiscoverableProperty() 163 discoverableProp.Set(*a.Discoverable) 164 person.SetTootDiscoverable(discoverableProp) 165 166 // devices 167 // NOT IMPLEMENTED, probably won't implement 168 169 // alsoKnownAs 170 // Required for Move activity. 171 // TODO: NOT IMPLEMENTED **YET** -- this needs to be added as an activitypub extension to https://github.com/go-fed/activity, see https://github.com/go-fed/activity/tree/master/astool 172 173 // publicKey 174 // Required for signatures. 175 publicKeyProp := streams.NewW3IDSecurityV1PublicKeyProperty() 176 177 // create the public key 178 publicKey := streams.NewW3IDSecurityV1PublicKey() 179 180 // set ID for the public key 181 publicKeyIDProp := streams.NewJSONLDIdProperty() 182 publicKeyURI, err := url.Parse(a.PublicKeyURI) 183 if err != nil { 184 return nil, err 185 } 186 publicKeyIDProp.SetIRI(publicKeyURI) 187 publicKey.SetJSONLDId(publicKeyIDProp) 188 189 // set owner for the public key 190 publicKeyOwnerProp := streams.NewW3IDSecurityV1OwnerProperty() 191 publicKeyOwnerProp.SetIRI(profileIDURI) 192 publicKey.SetW3IDSecurityV1Owner(publicKeyOwnerProp) 193 194 // set the pem key itself 195 encodedPublicKey, err := x509.MarshalPKIXPublicKey(a.PublicKey) 196 if err != nil { 197 return nil, err 198 } 199 publicKeyBytes := pem.EncodeToMemory(&pem.Block{ 200 Type: "PUBLIC KEY", 201 Bytes: encodedPublicKey, 202 }) 203 publicKeyPEMProp := streams.NewW3IDSecurityV1PublicKeyPemProperty() 204 publicKeyPEMProp.Set(string(publicKeyBytes)) 205 publicKey.SetW3IDSecurityV1PublicKeyPem(publicKeyPEMProp) 206 207 // append the public key to the public key property 208 publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey) 209 210 // set the public key property on the Person 211 person.SetW3IDSecurityV1PublicKey(publicKeyProp) 212 213 // tags 214 tagProp := streams.NewActivityStreamsTagProperty() 215 216 // tag -- emojis 217 emojis := a.Emojis 218 if len(a.EmojiIDs) > len(emojis) { 219 emojis = []*gtsmodel.Emoji{} 220 for _, emojiID := range a.EmojiIDs { 221 emoji, err := c.db.GetEmojiByID(ctx, emojiID) 222 if err != nil { 223 return nil, fmt.Errorf("AccountToAS: error getting emoji %s from database: %s", emojiID, err) 224 } 225 emojis = append(emojis, emoji) 226 } 227 } 228 for _, emoji := range emojis { 229 asEmoji, err := c.EmojiToAS(ctx, emoji) 230 if err != nil { 231 return nil, fmt.Errorf("AccountToAS: error converting emoji to AS emoji: %s", err) 232 } 233 tagProp.AppendTootEmoji(asEmoji) 234 } 235 236 // tag -- hashtags 237 // TODO 238 239 person.SetActivityStreamsTag(tagProp) 240 241 // attachment 242 // Used for profile fields. 243 if len(a.Fields) != 0 { 244 attachmentProp := streams.NewActivityStreamsAttachmentProperty() 245 246 for _, field := range a.Fields { 247 propertyValue := streams.NewSchemaPropertyValue() 248 249 nameProp := streams.NewActivityStreamsNameProperty() 250 nameProp.AppendXMLSchemaString(field.Name) 251 propertyValue.SetActivityStreamsName(nameProp) 252 253 valueProp := streams.NewSchemaValueProperty() 254 valueProp.Set(field.Value) 255 propertyValue.SetSchemaValue(valueProp) 256 257 attachmentProp.AppendSchemaPropertyValue(propertyValue) 258 } 259 260 person.SetActivityStreamsAttachment(attachmentProp) 261 } 262 263 // endpoints 264 // NOT IMPLEMENTED -- this is for shared inbox which we don't use 265 266 // icon 267 // Used as profile avatar. 268 if a.AvatarMediaAttachmentID != "" { 269 if a.AvatarMediaAttachment == nil { 270 avatar, err := c.db.GetAttachmentByID(ctx, a.AvatarMediaAttachmentID) 271 if err == nil { 272 a.AvatarMediaAttachment = avatar 273 } else { 274 log.Errorf(ctx, "error getting Avatar with id %s: %s", a.AvatarMediaAttachmentID, err) 275 } 276 } 277 278 if a.AvatarMediaAttachment != nil { 279 iconProperty := streams.NewActivityStreamsIconProperty() 280 281 iconImage := streams.NewActivityStreamsImage() 282 283 mediaType := streams.NewActivityStreamsMediaTypeProperty() 284 mediaType.Set(a.AvatarMediaAttachment.File.ContentType) 285 iconImage.SetActivityStreamsMediaType(mediaType) 286 287 avatarURLProperty := streams.NewActivityStreamsUrlProperty() 288 avatarURL, err := url.Parse(a.AvatarMediaAttachment.URL) 289 if err != nil { 290 return nil, err 291 } 292 avatarURLProperty.AppendIRI(avatarURL) 293 iconImage.SetActivityStreamsUrl(avatarURLProperty) 294 295 iconProperty.AppendActivityStreamsImage(iconImage) 296 person.SetActivityStreamsIcon(iconProperty) 297 } 298 } 299 300 // image 301 // Used as profile header. 302 if a.HeaderMediaAttachmentID != "" { 303 if a.HeaderMediaAttachment == nil { 304 header, err := c.db.GetAttachmentByID(ctx, a.HeaderMediaAttachmentID) 305 if err == nil { 306 a.HeaderMediaAttachment = header 307 } else { 308 log.Errorf(ctx, "error getting Header with id %s: %s", a.HeaderMediaAttachmentID, err) 309 } 310 } 311 312 if a.HeaderMediaAttachment != nil { 313 headerProperty := streams.NewActivityStreamsImageProperty() 314 315 headerImage := streams.NewActivityStreamsImage() 316 317 mediaType := streams.NewActivityStreamsMediaTypeProperty() 318 mediaType.Set(a.HeaderMediaAttachment.File.ContentType) 319 headerImage.SetActivityStreamsMediaType(mediaType) 320 321 headerURLProperty := streams.NewActivityStreamsUrlProperty() 322 headerURL, err := url.Parse(a.HeaderMediaAttachment.URL) 323 if err != nil { 324 return nil, err 325 } 326 headerURLProperty.AppendIRI(headerURL) 327 headerImage.SetActivityStreamsUrl(headerURLProperty) 328 329 headerProperty.AppendActivityStreamsImage(headerImage) 330 person.SetActivityStreamsImage(headerProperty) 331 } 332 } 333 334 return person, nil 335 } 336 337 // Converts a gts model account into a VERY MINIMAL Activity Streams person type. 338 // 339 // The returned account will just have the Type, Username, PublicKey, and ID properties set. 340 func (c *converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) { 341 person := streams.NewActivityStreamsPerson() 342 343 // id should be the activitypub URI of this user 344 // something like https://example.org/users/example_user 345 profileIDURI, err := url.Parse(a.URI) 346 if err != nil { 347 return nil, err 348 } 349 idProp := streams.NewJSONLDIdProperty() 350 idProp.SetIRI(profileIDURI) 351 person.SetJSONLDId(idProp) 352 353 // preferredUsername 354 // Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI. 355 preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty() 356 preferredUsernameProp.SetXMLSchemaString(a.Username) 357 person.SetActivityStreamsPreferredUsername(preferredUsernameProp) 358 359 // publicKey 360 // Required for signatures. 361 publicKeyProp := streams.NewW3IDSecurityV1PublicKeyProperty() 362 363 // create the public key 364 publicKey := streams.NewW3IDSecurityV1PublicKey() 365 366 // set ID for the public key 367 publicKeyIDProp := streams.NewJSONLDIdProperty() 368 publicKeyURI, err := url.Parse(a.PublicKeyURI) 369 if err != nil { 370 return nil, err 371 } 372 publicKeyIDProp.SetIRI(publicKeyURI) 373 publicKey.SetJSONLDId(publicKeyIDProp) 374 375 // set owner for the public key 376 publicKeyOwnerProp := streams.NewW3IDSecurityV1OwnerProperty() 377 publicKeyOwnerProp.SetIRI(profileIDURI) 378 publicKey.SetW3IDSecurityV1Owner(publicKeyOwnerProp) 379 380 // set the pem key itself 381 encodedPublicKey, err := x509.MarshalPKIXPublicKey(a.PublicKey) 382 if err != nil { 383 return nil, err 384 } 385 publicKeyBytes := pem.EncodeToMemory(&pem.Block{ 386 Type: "PUBLIC KEY", 387 Bytes: encodedPublicKey, 388 }) 389 publicKeyPEMProp := streams.NewW3IDSecurityV1PublicKeyPemProperty() 390 publicKeyPEMProp.Set(string(publicKeyBytes)) 391 publicKey.SetW3IDSecurityV1PublicKeyPem(publicKeyPEMProp) 392 393 // append the public key to the public key property 394 publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey) 395 396 // set the public key property on the Person 397 person.SetW3IDSecurityV1PublicKey(publicKeyProp) 398 399 return person, nil 400 } 401 402 func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) { 403 // ensure prerequisites here before we get stuck in 404 405 // check if author account is already attached to status and attach it if not 406 // if we can't retrieve this, bail here already because we can't attribute the status to anyone 407 if s.Account == nil { 408 a, err := c.db.GetAccountByID(ctx, s.AccountID) 409 if err != nil { 410 return nil, fmt.Errorf("StatusToAS: error retrieving author account from db: %s", err) 411 } 412 s.Account = a 413 } 414 415 // create the Note! 416 status := streams.NewActivityStreamsNote() 417 418 // id 419 statusURI, err := url.Parse(s.URI) 420 if err != nil { 421 return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.URI, err) 422 } 423 statusIDProp := streams.NewJSONLDIdProperty() 424 statusIDProp.SetIRI(statusURI) 425 status.SetJSONLDId(statusIDProp) 426 427 // type 428 // will be set automatically by go-fed 429 430 // summary aka cw 431 statusSummaryProp := streams.NewActivityStreamsSummaryProperty() 432 statusSummaryProp.AppendXMLSchemaString(s.ContentWarning) 433 status.SetActivityStreamsSummary(statusSummaryProp) 434 435 // inReplyTo 436 if s.InReplyToURI != "" { 437 rURI, err := url.Parse(s.InReplyToURI) 438 if err != nil { 439 return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.InReplyToURI, err) 440 } 441 442 inReplyToProp := streams.NewActivityStreamsInReplyToProperty() 443 inReplyToProp.AppendIRI(rURI) 444 status.SetActivityStreamsInReplyTo(inReplyToProp) 445 } 446 447 // published 448 publishedProp := streams.NewActivityStreamsPublishedProperty() 449 publishedProp.Set(s.CreatedAt) 450 status.SetActivityStreamsPublished(publishedProp) 451 452 // url 453 if s.URL != "" { 454 sURL, err := url.Parse(s.URL) 455 if err != nil { 456 return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.URL, err) 457 } 458 459 urlProp := streams.NewActivityStreamsUrlProperty() 460 urlProp.AppendIRI(sURL) 461 status.SetActivityStreamsUrl(urlProp) 462 } 463 464 // attributedTo 465 authorAccountURI, err := url.Parse(s.Account.URI) 466 if err != nil { 467 return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.Account.URI, err) 468 } 469 attributedToProp := streams.NewActivityStreamsAttributedToProperty() 470 attributedToProp.AppendIRI(authorAccountURI) 471 status.SetActivityStreamsAttributedTo(attributedToProp) 472 473 // tags 474 tagProp := streams.NewActivityStreamsTagProperty() 475 476 // tag -- mentions 477 mentions := s.Mentions 478 if len(s.MentionIDs) > len(mentions) { 479 mentions, err = c.db.GetMentions(ctx, s.MentionIDs) 480 if err != nil { 481 return nil, fmt.Errorf("StatusToAS: error getting mentions: %w", err) 482 } 483 } 484 for _, m := range mentions { 485 asMention, err := c.MentionToAS(ctx, m) 486 if err != nil { 487 return nil, fmt.Errorf("StatusToAS: error converting mention to AS mention: %s", err) 488 } 489 tagProp.AppendActivityStreamsMention(asMention) 490 } 491 492 // tag -- emojis 493 emojis := s.Emojis 494 if len(s.EmojiIDs) > len(emojis) { 495 emojis = []*gtsmodel.Emoji{} 496 for _, emojiID := range s.EmojiIDs { 497 emoji, err := c.db.GetEmojiByID(ctx, emojiID) 498 if err != nil { 499 return nil, fmt.Errorf("StatusToAS: error getting emoji %s from database: %s", emojiID, err) 500 } 501 emojis = append(emojis, emoji) 502 } 503 } 504 for _, emoji := range emojis { 505 asEmoji, err := c.EmojiToAS(ctx, emoji) 506 if err != nil { 507 return nil, fmt.Errorf("StatusToAS: error converting emoji to AS emoji: %s", err) 508 } 509 tagProp.AppendTootEmoji(asEmoji) 510 } 511 512 // tag -- hashtags 513 // TODO 514 515 status.SetActivityStreamsTag(tagProp) 516 517 // parse out some URIs we need here 518 authorFollowersURI, err := url.Parse(s.Account.FollowersURI) 519 if err != nil { 520 return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.Account.FollowersURI, err) 521 } 522 523 publicURI, err := url.Parse(pub.PublicActivityPubIRI) 524 if err != nil { 525 return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", pub.PublicActivityPubIRI, err) 526 } 527 528 // to and cc 529 toProp := streams.NewActivityStreamsToProperty() 530 ccProp := streams.NewActivityStreamsCcProperty() 531 switch s.Visibility { 532 case gtsmodel.VisibilityDirect: 533 // if DIRECT, then only mentioned users should be added to TO, and nothing to CC 534 for _, m := range mentions { 535 iri, err := url.Parse(m.TargetAccount.URI) 536 if err != nil { 537 return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.TargetAccount.URI, err) 538 } 539 toProp.AppendIRI(iri) 540 } 541 case gtsmodel.VisibilityMutualsOnly: 542 // TODO 543 case gtsmodel.VisibilityFollowersOnly: 544 // if FOLLOWERS ONLY then we want to add followers to TO, and mentions to CC 545 toProp.AppendIRI(authorFollowersURI) 546 for _, m := range mentions { 547 iri, err := url.Parse(m.TargetAccount.URI) 548 if err != nil { 549 return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.TargetAccount.URI, err) 550 } 551 ccProp.AppendIRI(iri) 552 } 553 case gtsmodel.VisibilityUnlocked: 554 // if UNLOCKED, we want to add followers to TO, and public and mentions to CC 555 toProp.AppendIRI(authorFollowersURI) 556 ccProp.AppendIRI(publicURI) 557 for _, m := range mentions { 558 iri, err := url.Parse(m.TargetAccount.URI) 559 if err != nil { 560 return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.TargetAccount.URI, err) 561 } 562 ccProp.AppendIRI(iri) 563 } 564 case gtsmodel.VisibilityPublic: 565 // if PUBLIC, we want to add public to TO, and followers and mentions to CC 566 toProp.AppendIRI(publicURI) 567 ccProp.AppendIRI(authorFollowersURI) 568 for _, m := range mentions { 569 iri, err := url.Parse(m.TargetAccount.URI) 570 if err != nil { 571 return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.TargetAccount.URI, err) 572 } 573 ccProp.AppendIRI(iri) 574 } 575 } 576 status.SetActivityStreamsTo(toProp) 577 status.SetActivityStreamsCc(ccProp) 578 579 // conversation 580 // TODO 581 582 // content -- the actual post itself 583 contentProp := streams.NewActivityStreamsContentProperty() 584 contentProp.AppendXMLSchemaString(s.Content) 585 status.SetActivityStreamsContent(contentProp) 586 587 // attachments 588 attachmentProp := streams.NewActivityStreamsAttachmentProperty() 589 attachments := s.Attachments 590 if len(s.AttachmentIDs) > len(attachments) { 591 attachments = []*gtsmodel.MediaAttachment{} 592 for _, attachmentID := range s.AttachmentIDs { 593 attachment, err := c.db.GetAttachmentByID(ctx, attachmentID) 594 if err != nil { 595 return nil, fmt.Errorf("StatusToAS: error getting attachment %s from database: %s", attachmentID, err) 596 } 597 attachments = append(attachments, attachment) 598 } 599 } 600 for _, a := range attachments { 601 doc, err := c.AttachmentToAS(ctx, a) 602 if err != nil { 603 return nil, fmt.Errorf("StatusToAS: error converting attachment: %s", err) 604 } 605 attachmentProp.AppendActivityStreamsDocument(doc) 606 } 607 status.SetActivityStreamsAttachment(attachmentProp) 608 609 // replies 610 repliesCollection, err := c.StatusToASRepliesCollection(ctx, s, false) 611 if err != nil { 612 return nil, fmt.Errorf("error creating repliesCollection: %s", err) 613 } 614 615 repliesProp := streams.NewActivityStreamsRepliesProperty() 616 repliesProp.SetActivityStreamsCollection(repliesCollection) 617 status.SetActivityStreamsReplies(repliesProp) 618 619 // sensitive 620 sensitiveProp := streams.NewActivityStreamsSensitiveProperty() 621 sensitiveProp.AppendXMLSchemaBoolean(*s.Sensitive) 622 status.SetActivityStreamsSensitive(sensitiveProp) 623 624 return status, nil 625 } 626 627 func (c *converter) StatusToASDelete(ctx context.Context, s *gtsmodel.Status) (vocab.ActivityStreamsDelete, error) { 628 // Parse / fetch some information 629 // we need to create the Delete. 630 631 if s.Account == nil { 632 var err error 633 s.Account, err = c.db.GetAccountByID(ctx, s.AccountID) 634 if err != nil { 635 return nil, fmt.Errorf("StatusToASDelete: error retrieving author account from db: %w", err) 636 } 637 } 638 639 actorIRI, err := url.Parse(s.AccountURI) 640 if err != nil { 641 return nil, fmt.Errorf("StatusToASDelete: error parsing actorIRI %s: %w", s.AccountURI, err) 642 } 643 644 statusIRI, err := url.Parse(s.URI) 645 if err != nil { 646 return nil, fmt.Errorf("StatusToASDelete: error parsing statusIRI %s: %w", s.URI, err) 647 } 648 649 // Create a Delete. 650 delete := streams.NewActivityStreamsDelete() 651 652 // Set appropriate actor for the Delete. 653 deleteActor := streams.NewActivityStreamsActorProperty() 654 deleteActor.AppendIRI(actorIRI) 655 delete.SetActivityStreamsActor(deleteActor) 656 657 // Set the status IRI as the 'object' property. 658 // We should avoid serializing the whole status 659 // when doing a delete because it's wasteful and 660 // could accidentally leak the now-deleted status. 661 deleteObject := streams.NewActivityStreamsObjectProperty() 662 deleteObject.AppendIRI(statusIRI) 663 delete.SetActivityStreamsObject(deleteObject) 664 665 // Address the Delete appropriately. 666 toProp := streams.NewActivityStreamsToProperty() 667 ccProp := streams.NewActivityStreamsCcProperty() 668 669 // Unless the status was a direct message, we can 670 // address the Delete To the ActivityPub Public URI. 671 // This ensures that the Delete will have as wide an 672 // audience as possible. 673 // 674 // Because we're using just the status URI, not the 675 // whole status, it won't leak any sensitive info. 676 // At worst, a remote instance becomes aware of the 677 // URI for a status which is now deleted anyway. 678 if s.Visibility != gtsmodel.VisibilityDirect { 679 publicURI, err := url.Parse(pub.PublicActivityPubIRI) 680 if err != nil { 681 return nil, fmt.Errorf("StatusToASDelete: error parsing url %s: %w", pub.PublicActivityPubIRI, err) 682 } 683 toProp.AppendIRI(publicURI) 684 685 actorFollowersURI, err := url.Parse(s.Account.FollowersURI) 686 if err != nil { 687 return nil, fmt.Errorf("StatusToASDelete: error parsing url %s: %w", s.Account.FollowersURI, err) 688 } 689 ccProp.AppendIRI(actorFollowersURI) 690 } 691 692 // Always include the replied-to account and any 693 // mentioned accounts as addressees as well. 694 // 695 // Worst case scenario here is that a replied account 696 // who wasn't mentioned (and perhaps didn't see the 697 // message), sees that someone has now deleted a status 698 // in which they were replied to but not mentioned. In 699 // other words, they *might* see that someone subtooted 700 // about them, but they won't know what was said. 701 702 // Ensure mentions are populated. 703 mentions := s.Mentions 704 if len(s.MentionIDs) > len(mentions) { 705 mentions, err = c.db.GetMentions(ctx, s.MentionIDs) 706 if err != nil { 707 return nil, fmt.Errorf("StatusToASDelete: error getting mentions: %w", err) 708 } 709 } 710 711 // Remember which accounts were mentioned 712 // here to avoid duplicating them later. 713 mentionedAccountIDs := make(map[string]interface{}, len(mentions)) 714 715 // For direct messages, add URI 716 // to To, else just add to CC. 717 var f func(v *url.URL) 718 if s.Visibility == gtsmodel.VisibilityDirect { 719 f = toProp.AppendIRI 720 } else { 721 f = ccProp.AppendIRI 722 } 723 724 for _, m := range mentions { 725 mentionedAccountIDs[m.TargetAccountID] = nil // Remember this ID. 726 727 iri, err := url.Parse(m.TargetAccount.URI) 728 if err != nil { 729 return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.TargetAccount.URI, err) 730 } 731 732 f(iri) 733 } 734 735 if s.InReplyToAccountID != "" { 736 if _, ok := mentionedAccountIDs[s.InReplyToAccountID]; !ok { 737 // Only address to this account if it 738 // wasn't already included as a mention. 739 if s.InReplyToAccount == nil { 740 s.InReplyToAccount, err = c.db.GetAccountByID(ctx, s.InReplyToAccountID) 741 if err != nil && !errors.Is(err, db.ErrNoEntries) { 742 return nil, fmt.Errorf("StatusToASDelete: db error getting account %s: %w", s.InReplyToAccountID, err) 743 } 744 } 745 746 if s.InReplyToAccount != nil { 747 inReplyToAccountURI, err := url.Parse(s.InReplyToAccount.URI) 748 if err != nil { 749 return nil, fmt.Errorf("StatusToASDelete: error parsing url %s: %w", s.InReplyToAccount.URI, err) 750 } 751 ccProp.AppendIRI(inReplyToAccountURI) 752 } 753 } 754 } 755 756 delete.SetActivityStreamsTo(toProp) 757 delete.SetActivityStreamsCc(ccProp) 758 759 return delete, nil 760 } 761 762 func (c *converter) FollowToAS(ctx context.Context, f *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (vocab.ActivityStreamsFollow, error) { 763 // parse out the various URIs we need for this 764 // origin account (who's doing the follow) 765 originAccountURI, err := url.Parse(originAccount.URI) 766 if err != nil { 767 return nil, fmt.Errorf("followtoasfollow: error parsing origin account uri: %s", err) 768 } 769 originActor := streams.NewActivityStreamsActorProperty() 770 originActor.AppendIRI(originAccountURI) 771 772 // target account (who's being followed) 773 targetAccountURI, err := url.Parse(targetAccount.URI) 774 if err != nil { 775 return nil, fmt.Errorf("followtoasfollow: error parsing target account uri: %s", err) 776 } 777 778 // uri of the follow activity itself 779 followURI, err := url.Parse(f.URI) 780 if err != nil { 781 return nil, fmt.Errorf("followtoasfollow: error parsing follow uri: %s", err) 782 } 783 784 // start preparing the follow activity 785 follow := streams.NewActivityStreamsFollow() 786 787 // set the actor 788 follow.SetActivityStreamsActor(originActor) 789 790 // set the id 791 followIDProp := streams.NewJSONLDIdProperty() 792 followIDProp.SetIRI(followURI) 793 follow.SetJSONLDId(followIDProp) 794 795 // set the object 796 followObjectProp := streams.NewActivityStreamsObjectProperty() 797 followObjectProp.AppendIRI(targetAccountURI) 798 follow.SetActivityStreamsObject(followObjectProp) 799 800 // set the To property 801 followToProp := streams.NewActivityStreamsToProperty() 802 followToProp.AppendIRI(targetAccountURI) 803 follow.SetActivityStreamsTo(followToProp) 804 805 return follow, nil 806 } 807 808 func (c *converter) MentionToAS(ctx context.Context, m *gtsmodel.Mention) (vocab.ActivityStreamsMention, error) { 809 if m.TargetAccount == nil { 810 a, err := c.db.GetAccountByID(ctx, m.TargetAccountID) 811 if err != nil { 812 return nil, fmt.Errorf("MentionToAS: error getting target account from db: %s", err) 813 } 814 m.TargetAccount = a 815 } 816 817 // create the mention 818 mention := streams.NewActivityStreamsMention() 819 820 // href -- this should be the URI of the mentioned user 821 hrefProp := streams.NewActivityStreamsHrefProperty() 822 hrefURI, err := url.Parse(m.TargetAccount.URI) 823 if err != nil { 824 return nil, fmt.Errorf("MentionToAS: error parsing uri %s: %s", m.TargetAccount.URI, err) 825 } 826 hrefProp.SetIRI(hrefURI) 827 mention.SetActivityStreamsHref(hrefProp) 828 829 // name -- this should be the namestring of the mentioned user, something like @whatever@example.org 830 var domain string 831 if m.TargetAccount.Domain == "" { 832 accountDomain := config.GetAccountDomain() 833 if accountDomain == "" { 834 accountDomain = config.GetHost() 835 } 836 domain = accountDomain 837 } else { 838 domain = m.TargetAccount.Domain 839 } 840 username := m.TargetAccount.Username 841 nameString := fmt.Sprintf("@%s@%s", username, domain) 842 nameProp := streams.NewActivityStreamsNameProperty() 843 nameProp.AppendXMLSchemaString(nameString) 844 mention.SetActivityStreamsName(nameProp) 845 846 return mention, nil 847 } 848 849 /* 850 we're making something like this: 851 { 852 "id": "https://example.com/emoji/123", 853 "type": "Emoji", 854 "name": ":kappa:", 855 "icon": { 856 "type": "Image", 857 "mediaType": "image/png", 858 "url": "https://example.com/files/kappa.png" 859 } 860 } 861 */ 862 func (c *converter) EmojiToAS(ctx context.Context, e *gtsmodel.Emoji) (vocab.TootEmoji, error) { 863 // create the emoji 864 emoji := streams.NewTootEmoji() 865 866 // set the ID property to the blocks's URI 867 idProp := streams.NewJSONLDIdProperty() 868 idIRI, err := url.Parse(e.URI) 869 if err != nil { 870 return nil, fmt.Errorf("EmojiToAS: error parsing uri %s: %s", e.URI, err) 871 } 872 idProp.Set(idIRI) 873 emoji.SetJSONLDId(idProp) 874 875 nameProp := streams.NewActivityStreamsNameProperty() 876 nameString := fmt.Sprintf(":%s:", e.Shortcode) 877 nameProp.AppendXMLSchemaString(nameString) 878 emoji.SetActivityStreamsName(nameProp) 879 880 iconProperty := streams.NewActivityStreamsIconProperty() 881 iconImage := streams.NewActivityStreamsImage() 882 883 mediaType := streams.NewActivityStreamsMediaTypeProperty() 884 mediaType.Set(e.ImageContentType) 885 iconImage.SetActivityStreamsMediaType(mediaType) 886 887 emojiURLProperty := streams.NewActivityStreamsUrlProperty() 888 emojiURL, err := url.Parse(e.ImageURL) 889 if err != nil { 890 return nil, fmt.Errorf("EmojiToAS: error parsing url %s: %s", e.ImageURL, err) 891 } 892 emojiURLProperty.AppendIRI(emojiURL) 893 iconImage.SetActivityStreamsUrl(emojiURLProperty) 894 895 iconProperty.AppendActivityStreamsImage(iconImage) 896 emoji.SetActivityStreamsIcon(iconProperty) 897 898 updatedProp := streams.NewActivityStreamsUpdatedProperty() 899 updatedProp.Set(e.ImageUpdatedAt) 900 emoji.SetActivityStreamsUpdated(updatedProp) 901 902 return emoji, nil 903 } 904 905 func (c *converter) AttachmentToAS(ctx context.Context, a *gtsmodel.MediaAttachment) (vocab.ActivityStreamsDocument, error) { 906 // type -- Document 907 doc := streams.NewActivityStreamsDocument() 908 909 // mediaType aka mime content type 910 mediaTypeProp := streams.NewActivityStreamsMediaTypeProperty() 911 mediaTypeProp.Set(a.File.ContentType) 912 doc.SetActivityStreamsMediaType(mediaTypeProp) 913 914 // url -- for the original image not the thumbnail 915 urlProp := streams.NewActivityStreamsUrlProperty() 916 imageURL, err := url.Parse(a.URL) 917 if err != nil { 918 return nil, fmt.Errorf("AttachmentToAS: error parsing uri %s: %s", a.URL, err) 919 } 920 urlProp.AppendIRI(imageURL) 921 doc.SetActivityStreamsUrl(urlProp) 922 923 // name -- aka image description 924 nameProp := streams.NewActivityStreamsNameProperty() 925 nameProp.AppendXMLSchemaString(a.Description) 926 doc.SetActivityStreamsName(nameProp) 927 928 // blurhash 929 blurProp := streams.NewTootBlurhashProperty() 930 blurProp.Set(a.Blurhash) 931 doc.SetTootBlurhash(blurProp) 932 933 // focalpoint 934 // TODO 935 936 return doc, nil 937 } 938 939 /* 940 We want to end up with something like this: 941 942 { 943 "@context": "https://www.w3.org/ns/activitystreams", 944 "actor": "https://ondergrond.org/users/dumpsterqueer", 945 "id": "https://ondergrond.org/users/dumpsterqueer#likes/44584", 946 "object": "https://testingtesting123.xyz/users/gotosocial_test_account/statuses/771aea80-a33d-4d6d-8dfd-57d4d2bfcbd4", 947 "type": "Like" 948 } 949 */ 950 func (c *converter) FaveToAS(ctx context.Context, f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, error) { 951 // check if targetStatus is already pinned to this fave, and fetch it if not 952 if f.Status == nil { 953 s, err := c.db.GetStatusByID(ctx, f.StatusID) 954 if err != nil { 955 return nil, fmt.Errorf("FaveToAS: error fetching target status from database: %s", err) 956 } 957 f.Status = s 958 } 959 960 // check if the targetAccount is already pinned to this fave, and fetch it if not 961 if f.TargetAccount == nil { 962 a, err := c.db.GetAccountByID(ctx, f.TargetAccountID) 963 if err != nil { 964 return nil, fmt.Errorf("FaveToAS: error fetching target account from database: %s", err) 965 } 966 f.TargetAccount = a 967 } 968 969 // check if the faving account is already pinned to this fave, and fetch it if not 970 if f.Account == nil { 971 a, err := c.db.GetAccountByID(ctx, f.AccountID) 972 if err != nil { 973 return nil, fmt.Errorf("FaveToAS: error fetching faving account from database: %s", err) 974 } 975 f.Account = a 976 } 977 978 // create the like 979 like := streams.NewActivityStreamsLike() 980 981 // set the actor property to the fave-ing account's URI 982 actorProp := streams.NewActivityStreamsActorProperty() 983 actorIRI, err := url.Parse(f.Account.URI) 984 if err != nil { 985 return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.Account.URI, err) 986 } 987 actorProp.AppendIRI(actorIRI) 988 like.SetActivityStreamsActor(actorProp) 989 990 // set the ID property to the fave's URI 991 idProp := streams.NewJSONLDIdProperty() 992 idIRI, err := url.Parse(f.URI) 993 if err != nil { 994 return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.URI, err) 995 } 996 idProp.Set(idIRI) 997 like.SetJSONLDId(idProp) 998 999 // set the object property to the target status's URI 1000 objectProp := streams.NewActivityStreamsObjectProperty() 1001 statusIRI, err := url.Parse(f.Status.URI) 1002 if err != nil { 1003 return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.Status.URI, err) 1004 } 1005 objectProp.AppendIRI(statusIRI) 1006 like.SetActivityStreamsObject(objectProp) 1007 1008 // set the TO property to the target account's IRI 1009 toProp := streams.NewActivityStreamsToProperty() 1010 toIRI, err := url.Parse(f.TargetAccount.URI) 1011 if err != nil { 1012 return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.TargetAccount.URI, err) 1013 } 1014 toProp.AppendIRI(toIRI) 1015 like.SetActivityStreamsTo(toProp) 1016 1017 return like, nil 1018 } 1019 1020 func (c *converter) BoostToAS(ctx context.Context, boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) (vocab.ActivityStreamsAnnounce, error) { 1021 // the boosted status is probably pinned to the boostWrapperStatus but double check to make sure 1022 if boostWrapperStatus.BoostOf == nil { 1023 b, err := c.db.GetStatusByID(ctx, boostWrapperStatus.BoostOfID) 1024 if err != nil { 1025 return nil, fmt.Errorf("BoostToAS: error getting status with ID %s from the db: %s", boostWrapperStatus.BoostOfID, err) 1026 } 1027 boostWrapperStatus.BoostOf = b 1028 } 1029 1030 // create the announce 1031 announce := streams.NewActivityStreamsAnnounce() 1032 1033 // set the actor 1034 boosterURI, err := url.Parse(boostingAccount.URI) 1035 if err != nil { 1036 return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostingAccount.URI, err) 1037 } 1038 actorProp := streams.NewActivityStreamsActorProperty() 1039 actorProp.AppendIRI(boosterURI) 1040 announce.SetActivityStreamsActor(actorProp) 1041 1042 // set the ID 1043 boostIDURI, err := url.Parse(boostWrapperStatus.URI) 1044 if err != nil { 1045 return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostWrapperStatus.URI, err) 1046 } 1047 idProp := streams.NewJSONLDIdProperty() 1048 idProp.SetIRI(boostIDURI) 1049 announce.SetJSONLDId(idProp) 1050 1051 // set the object 1052 boostedStatusURI, err := url.Parse(boostWrapperStatus.BoostOf.URI) 1053 if err != nil { 1054 return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostWrapperStatus.BoostOf.URI, err) 1055 } 1056 objectProp := streams.NewActivityStreamsObjectProperty() 1057 objectProp.AppendIRI(boostedStatusURI) 1058 announce.SetActivityStreamsObject(objectProp) 1059 1060 // set the published time 1061 publishedProp := streams.NewActivityStreamsPublishedProperty() 1062 publishedProp.Set(boostWrapperStatus.CreatedAt) 1063 announce.SetActivityStreamsPublished(publishedProp) 1064 1065 // set the to 1066 followersURI, err := url.Parse(boostingAccount.FollowersURI) 1067 if err != nil { 1068 return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostingAccount.FollowersURI, err) 1069 } 1070 toProp := streams.NewActivityStreamsToProperty() 1071 toProp.AppendIRI(followersURI) 1072 announce.SetActivityStreamsTo(toProp) 1073 1074 // set the cc 1075 ccProp := streams.NewActivityStreamsCcProperty() 1076 boostedAccountURI, err := url.Parse(boostedAccount.URI) 1077 if err != nil { 1078 return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostedAccount.URI, err) 1079 } 1080 ccProp.AppendIRI(boostedAccountURI) 1081 1082 // maybe CC it to public depending on the boosted status visibility 1083 switch boostWrapperStatus.BoostOf.Visibility { 1084 case gtsmodel.VisibilityPublic, gtsmodel.VisibilityUnlocked: 1085 publicURI, err := url.Parse(pub.PublicActivityPubIRI) 1086 if err != nil { 1087 return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", pub.PublicActivityPubIRI, err) 1088 } 1089 ccProp.AppendIRI(publicURI) 1090 } 1091 1092 announce.SetActivityStreamsCc(ccProp) 1093 1094 return announce, nil 1095 } 1096 1097 /* 1098 we want to end up with something like this: 1099 1100 { 1101 "@context": "https://www.w3.org/ns/activitystreams", 1102 "actor": "https://example.org/users/some_user", 1103 "id":"https://example.org/users/some_user/blocks/SOME_ULID_OF_A_BLOCK", 1104 "object":"https://some_other.instance/users/some_other_user", 1105 "type":"Block" 1106 } 1107 */ 1108 func (c *converter) BlockToAS(ctx context.Context, b *gtsmodel.Block) (vocab.ActivityStreamsBlock, error) { 1109 if b.Account == nil { 1110 a, err := c.db.GetAccountByID(ctx, b.AccountID) 1111 if err != nil { 1112 return nil, fmt.Errorf("BlockToAS: error getting block owner account from database: %s", err) 1113 } 1114 b.Account = a 1115 } 1116 1117 if b.TargetAccount == nil { 1118 a, err := c.db.GetAccountByID(ctx, b.TargetAccountID) 1119 if err != nil { 1120 return nil, fmt.Errorf("BlockToAS: error getting block target account from database: %s", err) 1121 } 1122 b.TargetAccount = a 1123 } 1124 1125 // create the block 1126 block := streams.NewActivityStreamsBlock() 1127 1128 // set the actor property to the block-ing account's URI 1129 actorProp := streams.NewActivityStreamsActorProperty() 1130 actorIRI, err := url.Parse(b.Account.URI) 1131 if err != nil { 1132 return nil, fmt.Errorf("BlockToAS: error parsing uri %s: %s", b.Account.URI, err) 1133 } 1134 actorProp.AppendIRI(actorIRI) 1135 block.SetActivityStreamsActor(actorProp) 1136 1137 // set the ID property to the blocks's URI 1138 idProp := streams.NewJSONLDIdProperty() 1139 idIRI, err := url.Parse(b.URI) 1140 if err != nil { 1141 return nil, fmt.Errorf("BlockToAS: error parsing uri %s: %s", b.URI, err) 1142 } 1143 idProp.Set(idIRI) 1144 block.SetJSONLDId(idProp) 1145 1146 // set the object property to the target account's URI 1147 objectProp := streams.NewActivityStreamsObjectProperty() 1148 targetIRI, err := url.Parse(b.TargetAccount.URI) 1149 if err != nil { 1150 return nil, fmt.Errorf("BlockToAS: error parsing uri %s: %s", b.TargetAccount.URI, err) 1151 } 1152 objectProp.AppendIRI(targetIRI) 1153 block.SetActivityStreamsObject(objectProp) 1154 1155 // set the TO property to the target account's IRI 1156 toProp := streams.NewActivityStreamsToProperty() 1157 toIRI, err := url.Parse(b.TargetAccount.URI) 1158 if err != nil { 1159 return nil, fmt.Errorf("BlockToAS: error parsing uri %s: %s", b.TargetAccount.URI, err) 1160 } 1161 toProp.AppendIRI(toIRI) 1162 block.SetActivityStreamsTo(toProp) 1163 1164 return block, nil 1165 } 1166 1167 /* 1168 the goal is to end up with something like this: 1169 1170 { 1171 "@context": "https://www.w3.org/ns/activitystreams", 1172 "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", 1173 "type": "Collection", 1174 "first": { 1175 "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?page=true", 1176 "type": "CollectionPage", 1177 "next": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true", 1178 "partOf": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", 1179 "items": [] 1180 } 1181 } 1182 */ 1183 func (c *converter) StatusToASRepliesCollection(ctx context.Context, status *gtsmodel.Status, onlyOtherAccounts bool) (vocab.ActivityStreamsCollection, error) { 1184 collectionID := fmt.Sprintf("%s/replies", status.URI) 1185 collectionIDURI, err := url.Parse(collectionID) 1186 if err != nil { 1187 return nil, err 1188 } 1189 1190 collection := streams.NewActivityStreamsCollection() 1191 1192 // collection.id 1193 collectionIDProp := streams.NewJSONLDIdProperty() 1194 collectionIDProp.SetIRI(collectionIDURI) 1195 collection.SetJSONLDId(collectionIDProp) 1196 1197 // first 1198 first := streams.NewActivityStreamsFirstProperty() 1199 firstPage := streams.NewActivityStreamsCollectionPage() 1200 1201 // first.id 1202 firstPageIDProp := streams.NewJSONLDIdProperty() 1203 firstPageID, err := url.Parse(fmt.Sprintf("%s?page=true", collectionID)) 1204 if err != nil { 1205 return nil, gtserror.NewErrorInternalError(err) 1206 } 1207 firstPageIDProp.SetIRI(firstPageID) 1208 firstPage.SetJSONLDId(firstPageIDProp) 1209 1210 // first.next 1211 nextProp := streams.NewActivityStreamsNextProperty() 1212 nextPropID, err := url.Parse(fmt.Sprintf("%s?only_other_accounts=%t&page=true", collectionID, onlyOtherAccounts)) 1213 if err != nil { 1214 return nil, gtserror.NewErrorInternalError(err) 1215 } 1216 nextProp.SetIRI(nextPropID) 1217 firstPage.SetActivityStreamsNext(nextProp) 1218 1219 // first.partOf 1220 partOfProp := streams.NewActivityStreamsPartOfProperty() 1221 partOfProp.SetIRI(collectionIDURI) 1222 firstPage.SetActivityStreamsPartOf(partOfProp) 1223 1224 first.SetActivityStreamsCollectionPage(firstPage) 1225 1226 // collection.first 1227 collection.SetActivityStreamsFirst(first) 1228 1229 return collection, nil 1230 } 1231 1232 /* 1233 the goal is to end up with something like this: 1234 1235 { 1236 "@context": "https://www.w3.org/ns/activitystreams", 1237 "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true", 1238 "type": "CollectionPage", 1239 "next": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?min_id=106720870266901180&only_other_accounts=true&page=true", 1240 "partOf": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", 1241 "items": [ 1242 "https://example.com/users/someone/statuses/106720752853216226", 1243 "https://somewhere.online/users/eeeeeeeeeep/statuses/106720870163727231" 1244 ] 1245 } 1246 */ 1247 func (c *converter) StatusURIsToASRepliesPage(ctx context.Context, status *gtsmodel.Status, onlyOtherAccounts bool, minID string, replies map[string]*url.URL) (vocab.ActivityStreamsCollectionPage, error) { 1248 collectionID := fmt.Sprintf("%s/replies", status.URI) 1249 1250 page := streams.NewActivityStreamsCollectionPage() 1251 1252 // .id 1253 pageIDProp := streams.NewJSONLDIdProperty() 1254 pageIDString := fmt.Sprintf("%s?page=true&only_other_accounts=%t", collectionID, onlyOtherAccounts) 1255 if minID != "" { 1256 pageIDString = fmt.Sprintf("%s&min_id=%s", pageIDString, minID) 1257 } 1258 1259 pageID, err := url.Parse(pageIDString) 1260 if err != nil { 1261 return nil, gtserror.NewErrorInternalError(err) 1262 } 1263 pageIDProp.SetIRI(pageID) 1264 page.SetJSONLDId(pageIDProp) 1265 1266 // .partOf 1267 collectionIDURI, err := url.Parse(collectionID) 1268 if err != nil { 1269 return nil, err 1270 } 1271 partOfProp := streams.NewActivityStreamsPartOfProperty() 1272 partOfProp.SetIRI(collectionIDURI) 1273 page.SetActivityStreamsPartOf(partOfProp) 1274 1275 // .items 1276 items := streams.NewActivityStreamsItemsProperty() 1277 var highestID string 1278 for k, v := range replies { 1279 items.AppendIRI(v) 1280 if k > highestID { 1281 highestID = k 1282 } 1283 } 1284 page.SetActivityStreamsItems(items) 1285 1286 // .next 1287 nextProp := streams.NewActivityStreamsNextProperty() 1288 nextPropIDString := fmt.Sprintf("%s?only_other_accounts=%t&page=true", collectionID, onlyOtherAccounts) 1289 if highestID != "" { 1290 nextPropIDString = fmt.Sprintf("%s&min_id=%s", nextPropIDString, highestID) 1291 } 1292 1293 nextPropID, err := url.Parse(nextPropIDString) 1294 if err != nil { 1295 return nil, gtserror.NewErrorInternalError(err) 1296 } 1297 nextProp.SetIRI(nextPropID) 1298 page.SetActivityStreamsNext(nextProp) 1299 1300 return page, nil 1301 } 1302 1303 /* 1304 the goal is to end up with something like this: 1305 1306 { 1307 "id": "https://example.org/users/whatever/outbox?page=true", 1308 "type": "OrderedCollectionPage", 1309 "next": "https://example.org/users/whatever/outbox?max_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true", 1310 "prev": "https://example.org/users/whatever/outbox?min_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true", 1311 "partOf": "https://example.org/users/whatever/outbox", 1312 "orderedItems": [ 1313 "id": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7/activity", 1314 "type": "Create", 1315 "actor": "https://example.org/users/whatever", 1316 "published": "2021-10-18T20:06:18Z", 1317 "to": [ 1318 "https://www.w3.org/ns/activitystreams#Public" 1319 ], 1320 "cc": [ 1321 "https://example.org/users/whatever/followers" 1322 ], 1323 "object": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7" 1324 ] 1325 } 1326 */ 1327 func (c *converter) StatusesToASOutboxPage(ctx context.Context, outboxID string, maxID string, minID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollectionPage, error) { 1328 page := streams.NewActivityStreamsOrderedCollectionPage() 1329 1330 // .id 1331 pageIDProp := streams.NewJSONLDIdProperty() 1332 pageID := fmt.Sprintf("%s?page=true", outboxID) 1333 if minID != "" { 1334 pageID = fmt.Sprintf("%s&minID=%s", pageID, minID) 1335 } 1336 if maxID != "" { 1337 pageID = fmt.Sprintf("%s&maxID=%s", pageID, maxID) 1338 } 1339 pageIDURI, err := url.Parse(pageID) 1340 if err != nil { 1341 return nil, err 1342 } 1343 pageIDProp.SetIRI(pageIDURI) 1344 page.SetJSONLDId(pageIDProp) 1345 1346 // .partOf 1347 collectionIDURI, err := url.Parse(outboxID) 1348 if err != nil { 1349 return nil, err 1350 } 1351 partOfProp := streams.NewActivityStreamsPartOfProperty() 1352 partOfProp.SetIRI(collectionIDURI) 1353 page.SetActivityStreamsPartOf(partOfProp) 1354 1355 // .orderedItems 1356 itemsProp := streams.NewActivityStreamsOrderedItemsProperty() 1357 var highest string 1358 var lowest string 1359 for _, s := range statuses { 1360 note, err := c.StatusToAS(ctx, s) 1361 if err != nil { 1362 return nil, err 1363 } 1364 1365 create, err := c.WrapNoteInCreate(note, true) 1366 if err != nil { 1367 return nil, err 1368 } 1369 1370 itemsProp.AppendActivityStreamsCreate(create) 1371 1372 if highest == "" || s.ID > highest { 1373 highest = s.ID 1374 } 1375 if lowest == "" || s.ID < lowest { 1376 lowest = s.ID 1377 } 1378 } 1379 page.SetActivityStreamsOrderedItems(itemsProp) 1380 1381 // .next 1382 if lowest != "" { 1383 nextProp := streams.NewActivityStreamsNextProperty() 1384 nextPropIDString := fmt.Sprintf("%s?page=true&max_id=%s", outboxID, lowest) 1385 nextPropIDURI, err := url.Parse(nextPropIDString) 1386 if err != nil { 1387 return nil, err 1388 } 1389 nextProp.SetIRI(nextPropIDURI) 1390 page.SetActivityStreamsNext(nextProp) 1391 } 1392 1393 // .prev 1394 if highest != "" { 1395 prevProp := streams.NewActivityStreamsPrevProperty() 1396 prevPropIDString := fmt.Sprintf("%s?page=true&min_id=%s", outboxID, highest) 1397 prevPropIDURI, err := url.Parse(prevPropIDString) 1398 if err != nil { 1399 return nil, err 1400 } 1401 prevProp.SetIRI(prevPropIDURI) 1402 page.SetActivityStreamsPrev(prevProp) 1403 } 1404 1405 return page, nil 1406 } 1407 1408 /* 1409 we want something that looks like this: 1410 1411 { 1412 "@context": "https://www.w3.org/ns/activitystreams", 1413 "id": "https://example.org/users/whatever/outbox", 1414 "type": "OrderedCollection", 1415 "first": "https://example.org/users/whatever/outbox?page=true" 1416 } 1417 */ 1418 func (c *converter) OutboxToASCollection(ctx context.Context, outboxID string) (vocab.ActivityStreamsOrderedCollection, error) { 1419 collection := streams.NewActivityStreamsOrderedCollection() 1420 1421 collectionIDProp := streams.NewJSONLDIdProperty() 1422 outboxIDURI, err := url.Parse(outboxID) 1423 if err != nil { 1424 return nil, fmt.Errorf("error parsing url %s", outboxID) 1425 } 1426 collectionIDProp.SetIRI(outboxIDURI) 1427 collection.SetJSONLDId(collectionIDProp) 1428 1429 collectionFirstProp := streams.NewActivityStreamsFirstProperty() 1430 collectionFirstPropID := fmt.Sprintf("%s?page=true", outboxID) 1431 collectionFirstPropIDURI, err := url.Parse(collectionFirstPropID) 1432 if err != nil { 1433 return nil, fmt.Errorf("error parsing url %s", collectionFirstPropID) 1434 } 1435 collectionFirstProp.SetIRI(collectionFirstPropIDURI) 1436 collection.SetActivityStreamsFirst(collectionFirstProp) 1437 1438 return collection, nil 1439 } 1440 1441 func (c *converter) StatusesToASFeaturedCollection(ctx context.Context, featuredCollectionID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollection, error) { 1442 collection := streams.NewActivityStreamsOrderedCollection() 1443 1444 collectionIDProp := streams.NewJSONLDIdProperty() 1445 featuredCollectionIDURI, err := url.Parse(featuredCollectionID) 1446 if err != nil { 1447 return nil, fmt.Errorf("error parsing url %s", featuredCollectionID) 1448 } 1449 collectionIDProp.SetIRI(featuredCollectionIDURI) 1450 collection.SetJSONLDId(collectionIDProp) 1451 1452 itemsProp := streams.NewActivityStreamsOrderedItemsProperty() 1453 for _, s := range statuses { 1454 uri, err := url.Parse(s.URI) 1455 if err != nil { 1456 return nil, fmt.Errorf("error parsing url %s", s.URI) 1457 } 1458 itemsProp.AppendIRI(uri) 1459 } 1460 collection.SetActivityStreamsOrderedItems(itemsProp) 1461 1462 totalItemsProp := streams.NewActivityStreamsTotalItemsProperty() 1463 totalItemsProp.Set(len(statuses)) 1464 collection.SetActivityStreamsTotalItems(totalItemsProp) 1465 1466 return collection, nil 1467 } 1468 1469 func (c *converter) ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (vocab.ActivityStreamsFlag, error) { 1470 flag := streams.NewActivityStreamsFlag() 1471 1472 flagIDProp := streams.NewJSONLDIdProperty() 1473 idURI, err := url.Parse(r.URI) 1474 if err != nil { 1475 return nil, fmt.Errorf("error parsing url %s: %w", r.URI, err) 1476 } 1477 flagIDProp.SetIRI(idURI) 1478 flag.SetJSONLDId(flagIDProp) 1479 1480 // for privacy, set the actor as the INSTANCE ACTOR, 1481 // not as the actor who created the report 1482 instanceAccount, err := c.db.GetInstanceAccount(ctx, "") 1483 if err != nil { 1484 return nil, fmt.Errorf("error getting instance account: %w", err) 1485 } 1486 instanceAccountIRI, err := url.Parse(instanceAccount.URI) 1487 if err != nil { 1488 return nil, fmt.Errorf("error parsing url %s: %w", instanceAccount.URI, err) 1489 } 1490 flagActorProp := streams.NewActivityStreamsActorProperty() 1491 flagActorProp.AppendIRI(instanceAccountIRI) 1492 flag.SetActivityStreamsActor(flagActorProp) 1493 1494 // content should be the comment submitted when the report was created 1495 contentProp := streams.NewActivityStreamsContentProperty() 1496 contentProp.AppendXMLSchemaString(r.Comment) 1497 flag.SetActivityStreamsContent(contentProp) 1498 1499 // set at least the target account uri as the object of the flag 1500 objectProp := streams.NewActivityStreamsObjectProperty() 1501 targetAccountURI, err := url.Parse(r.TargetAccount.URI) 1502 if err != nil { 1503 return nil, fmt.Errorf("error parsing url %s: %w", r.TargetAccount.URI, err) 1504 } 1505 objectProp.AppendIRI(targetAccountURI) 1506 // also set status URIs if they were provided with the report 1507 for _, s := range r.Statuses { 1508 statusURI, err := url.Parse(s.URI) 1509 if err != nil { 1510 return nil, fmt.Errorf("error parsing url %s: %w", s.URI, err) 1511 } 1512 objectProp.AppendIRI(statusURI) 1513 } 1514 flag.SetActivityStreamsObject(objectProp) 1515 1516 return flag, nil 1517 }