2021-05-08 12:25:55 +00:00
/ *
GoToSocial
Copyright ( C ) 2021 GoToSocial Authors admin @ gotosocial . org
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
package typeutils
import (
2021-08-25 13:34:33 +00:00
"context"
2021-05-08 12:25:55 +00:00
"crypto/x509"
"encoding/pem"
2021-05-21 13:48:26 +00:00
"fmt"
2021-05-08 12:25:55 +00:00
"net/url"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
2021-08-10 11:32:39 +00:00
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
2021-05-08 12:25:55 +00:00
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// Converts a gts model account into an Activity Streams person type, following
// the spec laid out for mastodon here: https://docs.joinmastodon.org/spec/activitypub/
2021-08-25 13:34:33 +00:00
func ( c * converter ) AccountToAS ( ctx context . Context , a * gtsmodel . Account ) ( vocab . ActivityStreamsPerson , error ) {
2021-08-20 10:26:56 +00:00
// first check if we have this person in our asCache already
if personI , err := c . asCache . Fetch ( a . ID ) ; err == nil {
if person , ok := personI . ( vocab . ActivityStreamsPerson ) ; ok {
// we have it, so just return it as-is
return person , nil
}
}
2021-05-08 12:25:55 +00:00
person := streams . NewActivityStreamsPerson ( )
// id should be the activitypub URI of this user
// something like https://example.org/users/example_user
profileIDURI , err := url . Parse ( a . URI )
if err != nil {
return nil , err
}
idProp := streams . NewJSONLDIdProperty ( )
idProp . SetIRI ( profileIDURI )
person . SetJSONLDId ( idProp )
// following
// The URI for retrieving a list of accounts this user is following
followingURI , err := url . Parse ( a . FollowingURI )
if err != nil {
return nil , err
}
followingProp := streams . NewActivityStreamsFollowingProperty ( )
followingProp . SetIRI ( followingURI )
person . SetActivityStreamsFollowing ( followingProp )
// followers
// The URI for retrieving a list of this user's followers
followersURI , err := url . Parse ( a . FollowersURI )
if err != nil {
return nil , err
}
followersProp := streams . NewActivityStreamsFollowersProperty ( )
followersProp . SetIRI ( followersURI )
person . SetActivityStreamsFollowers ( followersProp )
// inbox
// the activitypub inbox of this user for accepting messages
inboxURI , err := url . Parse ( a . InboxURI )
if err != nil {
return nil , err
}
inboxProp := streams . NewActivityStreamsInboxProperty ( )
inboxProp . SetIRI ( inboxURI )
person . SetActivityStreamsInbox ( inboxProp )
// outbox
// the activitypub outbox of this user for serving messages
outboxURI , err := url . Parse ( a . OutboxURI )
if err != nil {
return nil , err
}
outboxProp := streams . NewActivityStreamsOutboxProperty ( )
outboxProp . SetIRI ( outboxURI )
person . SetActivityStreamsOutbox ( outboxProp )
// featured posts
// Pinned posts.
featuredURI , err := url . Parse ( a . FeaturedCollectionURI )
if err != nil {
return nil , err
}
featuredProp := streams . NewTootFeaturedProperty ( )
featuredProp . SetIRI ( featuredURI )
person . SetTootFeatured ( featuredProp )
// featuredTags
// NOT IMPLEMENTED
// preferredUsername
// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
preferredUsernameProp := streams . NewActivityStreamsPreferredUsernameProperty ( )
preferredUsernameProp . SetXMLSchemaString ( a . Username )
person . SetActivityStreamsPreferredUsername ( preferredUsernameProp )
// name
// Used as profile display name.
nameProp := streams . NewActivityStreamsNameProperty ( )
if a . Username != "" {
nameProp . AppendXMLSchemaString ( a . DisplayName )
} else {
nameProp . AppendXMLSchemaString ( a . Username )
}
person . SetActivityStreamsName ( nameProp )
// summary
// Used as profile bio.
if a . Note != "" {
summaryProp := streams . NewActivityStreamsSummaryProperty ( )
summaryProp . AppendXMLSchemaString ( a . Note )
person . SetActivityStreamsSummary ( summaryProp )
}
// url
// Used as profile link.
profileURL , err := url . Parse ( a . URL )
if err != nil {
return nil , err
}
urlProp := streams . NewActivityStreamsUrlProperty ( )
urlProp . AppendIRI ( profileURL )
person . SetActivityStreamsUrl ( urlProp )
// manuallyApprovesFollowers
// Will be shown as a locked account.
2021-08-23 10:46:05 +00:00
manuallyApprovesFollowersProp := streams . NewActivityStreamsManuallyApprovesFollowersProperty ( )
manuallyApprovesFollowersProp . Set ( a . Locked )
person . SetActivityStreamsManuallyApprovesFollowers ( manuallyApprovesFollowersProp )
2021-05-08 12:25:55 +00:00
// discoverable
// Will be shown in the profile directory.
discoverableProp := streams . NewTootDiscoverableProperty ( )
discoverableProp . Set ( a . Discoverable )
person . SetTootDiscoverable ( discoverableProp )
// devices
// NOT IMPLEMENTED, probably won't implement
// alsoKnownAs
// Required for Move activity.
// 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
// publicKey
// Required for signatures.
publicKeyProp := streams . NewW3IDSecurityV1PublicKeyProperty ( )
// create the public key
publicKey := streams . NewW3IDSecurityV1PublicKey ( )
// set ID for the public key
publicKeyIDProp := streams . NewJSONLDIdProperty ( )
publicKeyURI , err := url . Parse ( a . PublicKeyURI )
if err != nil {
return nil , err
}
publicKeyIDProp . SetIRI ( publicKeyURI )
publicKey . SetJSONLDId ( publicKeyIDProp )
// set owner for the public key
publicKeyOwnerProp := streams . NewW3IDSecurityV1OwnerProperty ( )
publicKeyOwnerProp . SetIRI ( profileIDURI )
publicKey . SetW3IDSecurityV1Owner ( publicKeyOwnerProp )
// set the pem key itself
encodedPublicKey , err := x509 . MarshalPKIXPublicKey ( a . PublicKey )
if err != nil {
return nil , err
}
publicKeyBytes := pem . EncodeToMemory ( & pem . Block {
Type : "PUBLIC KEY" ,
Bytes : encodedPublicKey ,
} )
publicKeyPEMProp := streams . NewW3IDSecurityV1PublicKeyPemProperty ( )
publicKeyPEMProp . Set ( string ( publicKeyBytes ) )
publicKey . SetW3IDSecurityV1PublicKeyPem ( publicKeyPEMProp )
// append the public key to the public key property
publicKeyProp . AppendW3IDSecurityV1PublicKey ( publicKey )
// set the public key property on the Person
person . SetW3IDSecurityV1PublicKey ( publicKeyProp )
// tag
// TODO: Any tags used in the summary of this profile
// attachment
// Used for profile fields.
// TODO: The PropertyValue type has to be added: https://schema.org/PropertyValue
// endpoints
// NOT IMPLEMENTED -- this is for shared inbox which we don't use
// icon
// Used as profile avatar.
if a . AvatarMediaAttachmentID != "" {
2021-08-25 13:34:33 +00:00
if a . AvatarMediaAttachment == nil {
avatar := & gtsmodel . MediaAttachment { }
if err := c . db . GetByID ( ctx , a . AvatarMediaAttachmentID , avatar ) ; err != nil {
return nil , err
}
a . AvatarMediaAttachment = avatar
2021-05-08 12:25:55 +00:00
}
2021-05-15 09:58:11 +00:00
iconProperty := streams . NewActivityStreamsIconProperty ( )
iconImage := streams . NewActivityStreamsImage ( )
2021-05-08 12:25:55 +00:00
mediaType := streams . NewActivityStreamsMediaTypeProperty ( )
2021-08-25 13:34:33 +00:00
mediaType . Set ( a . AvatarMediaAttachment . File . ContentType )
2021-05-08 12:25:55 +00:00
iconImage . SetActivityStreamsMediaType ( mediaType )
avatarURLProperty := streams . NewActivityStreamsUrlProperty ( )
2021-08-25 13:34:33 +00:00
avatarURL , err := url . Parse ( a . AvatarMediaAttachment . URL )
2021-05-08 12:25:55 +00:00
if err != nil {
return nil , err
}
avatarURLProperty . AppendIRI ( avatarURL )
iconImage . SetActivityStreamsUrl ( avatarURLProperty )
iconProperty . AppendActivityStreamsImage ( iconImage )
person . SetActivityStreamsIcon ( iconProperty )
}
// image
// Used as profile header.
if a . HeaderMediaAttachmentID != "" {
2021-08-25 13:34:33 +00:00
if a . HeaderMediaAttachment == nil {
header := & gtsmodel . MediaAttachment { }
if err := c . db . GetByID ( ctx , a . HeaderMediaAttachmentID , header ) ; err != nil {
return nil , err
}
a . HeaderMediaAttachment = header
2021-05-08 12:25:55 +00:00
}
2021-05-15 09:58:11 +00:00
headerProperty := streams . NewActivityStreamsImageProperty ( )
headerImage := streams . NewActivityStreamsImage ( )
2021-05-08 12:25:55 +00:00
mediaType := streams . NewActivityStreamsMediaTypeProperty ( )
2021-08-25 13:34:33 +00:00
mediaType . Set ( a . HeaderMediaAttachment . File . ContentType )
2021-05-08 12:25:55 +00:00
headerImage . SetActivityStreamsMediaType ( mediaType )
headerURLProperty := streams . NewActivityStreamsUrlProperty ( )
2021-08-25 13:34:33 +00:00
headerURL , err := url . Parse ( a . HeaderMediaAttachment . URL )
2021-05-08 12:25:55 +00:00
if err != nil {
return nil , err
}
headerURLProperty . AppendIRI ( headerURL )
headerImage . SetActivityStreamsUrl ( headerURLProperty )
headerProperty . AppendActivityStreamsImage ( headerImage )
2021-05-29 17:36:54 +00:00
person . SetActivityStreamsImage ( headerProperty )
2021-05-08 12:25:55 +00:00
}
2021-08-20 10:26:56 +00:00
// put the person in our cache in case we need it again soon
if err := c . asCache . Store ( a . ID , person ) ; err != nil {
return nil , err
}
2021-05-08 12:25:55 +00:00
return person , nil
}
2021-06-26 14:21:40 +00:00
// Converts a gts model account into a VERY MINIMAL Activity Streams person type, following
// the spec laid out for mastodon here: https://docs.joinmastodon.org/spec/activitypub/
//
// The returned account will just have the Type, Username, PublicKey, and ID properties set.
2021-08-25 13:34:33 +00:00
func ( c * converter ) AccountToASMinimal ( ctx context . Context , a * gtsmodel . Account ) ( vocab . ActivityStreamsPerson , error ) {
2021-06-26 14:21:40 +00:00
person := streams . NewActivityStreamsPerson ( )
// id should be the activitypub URI of this user
// something like https://example.org/users/example_user
profileIDURI , err := url . Parse ( a . URI )
if err != nil {
return nil , err
}
idProp := streams . NewJSONLDIdProperty ( )
idProp . SetIRI ( profileIDURI )
person . SetJSONLDId ( idProp )
// preferredUsername
// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
preferredUsernameProp := streams . NewActivityStreamsPreferredUsernameProperty ( )
preferredUsernameProp . SetXMLSchemaString ( a . Username )
person . SetActivityStreamsPreferredUsername ( preferredUsernameProp )
// publicKey
// Required for signatures.
publicKeyProp := streams . NewW3IDSecurityV1PublicKeyProperty ( )
// create the public key
publicKey := streams . NewW3IDSecurityV1PublicKey ( )
// set ID for the public key
publicKeyIDProp := streams . NewJSONLDIdProperty ( )
publicKeyURI , err := url . Parse ( a . PublicKeyURI )
if err != nil {
return nil , err
}
publicKeyIDProp . SetIRI ( publicKeyURI )
publicKey . SetJSONLDId ( publicKeyIDProp )
// set owner for the public key
publicKeyOwnerProp := streams . NewW3IDSecurityV1OwnerProperty ( )
publicKeyOwnerProp . SetIRI ( profileIDURI )
publicKey . SetW3IDSecurityV1Owner ( publicKeyOwnerProp )
// set the pem key itself
encodedPublicKey , err := x509 . MarshalPKIXPublicKey ( a . PublicKey )
if err != nil {
return nil , err
}
publicKeyBytes := pem . EncodeToMemory ( & pem . Block {
Type : "PUBLIC KEY" ,
Bytes : encodedPublicKey ,
} )
publicKeyPEMProp := streams . NewW3IDSecurityV1PublicKeyPemProperty ( )
publicKeyPEMProp . Set ( string ( publicKeyBytes ) )
publicKey . SetW3IDSecurityV1PublicKeyPem ( publicKeyPEMProp )
// append the public key to the public key property
publicKeyProp . AppendW3IDSecurityV1PublicKey ( publicKey )
// set the public key property on the Person
person . SetW3IDSecurityV1PublicKey ( publicKeyProp )
return person , nil
}
2021-08-25 13:34:33 +00:00
func ( c * converter ) StatusToAS ( ctx context . Context , s * gtsmodel . Status ) ( vocab . ActivityStreamsNote , error ) {
2021-08-20 10:26:56 +00:00
// first check if we have this note in our asCache already
if noteI , err := c . asCache . Fetch ( s . ID ) ; err == nil {
if note , ok := noteI . ( vocab . ActivityStreamsNote ) ; ok {
// we have it, so just return it as-is
return note , nil
}
}
2021-05-21 13:48:26 +00:00
// ensure prerequisites here before we get stuck in
// check if author account is already attached to status and attach it if not
// if we can't retrieve this, bail here already because we can't attribute the status to anyone
2021-08-20 10:26:56 +00:00
if s . Account == nil {
2021-08-25 13:34:33 +00:00
a , err := c . db . GetAccountByID ( ctx , s . AccountID )
2021-08-20 10:26:56 +00:00
if err != nil {
2021-05-21 13:48:26 +00:00
return nil , fmt . Errorf ( "StatusToAS: error retrieving author account from db: %s" , err )
}
2021-08-20 10:26:56 +00:00
s . Account = a
2021-05-21 13:48:26 +00:00
}
// create the Note!
status := streams . NewActivityStreamsNote ( )
// id
statusURI , err := url . Parse ( s . URI )
if err != nil {
return nil , fmt . Errorf ( "StatusToAS: error parsing url %s: %s" , s . URI , err )
}
statusIDProp := streams . NewJSONLDIdProperty ( )
statusIDProp . SetIRI ( statusURI )
status . SetJSONLDId ( statusIDProp )
// type
// will be set automatically by go-fed
// summary aka cw
statusSummaryProp := streams . NewActivityStreamsSummaryProperty ( )
statusSummaryProp . AppendXMLSchemaString ( s . ContentWarning )
status . SetActivityStreamsSummary ( statusSummaryProp )
// inReplyTo
if s . InReplyToID != "" {
// fetch the replied status if we don't have it on hand already
2021-08-20 10:26:56 +00:00
if s . InReplyTo == nil {
2021-05-21 13:48:26 +00:00
rs := & gtsmodel . Status { }
2021-08-25 13:34:33 +00:00
if err := c . db . GetByID ( ctx , s . InReplyToID , rs ) ; err != nil {
2021-05-21 13:48:26 +00:00
return nil , fmt . Errorf ( "StatusToAS: error retrieving replied-to status from db: %s" , err )
}
2021-08-20 10:26:56 +00:00
s . InReplyTo = rs
2021-05-21 13:48:26 +00:00
}
2021-08-20 10:26:56 +00:00
rURI , err := url . Parse ( s . InReplyTo . URI )
2021-05-21 13:48:26 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "StatusToAS: error parsing url %s: %s" , s . InReplyTo . URI , err )
2021-05-21 13:48:26 +00:00
}
inReplyToProp := streams . NewActivityStreamsInReplyToProperty ( )
inReplyToProp . AppendIRI ( rURI )
status . SetActivityStreamsInReplyTo ( inReplyToProp )
}
// published
publishedProp := streams . NewActivityStreamsPublishedProperty ( )
publishedProp . Set ( s . CreatedAt )
status . SetActivityStreamsPublished ( publishedProp )
// url
if s . URL != "" {
sURL , err := url . Parse ( s . URL )
if err != nil {
return nil , fmt . Errorf ( "StatusToAS: error parsing url %s: %s" , s . URL , err )
}
urlProp := streams . NewActivityStreamsUrlProperty ( )
urlProp . AppendIRI ( sURL )
status . SetActivityStreamsUrl ( urlProp )
}
// attributedTo
2021-08-20 10:26:56 +00:00
authorAccountURI , err := url . Parse ( s . Account . URI )
2021-05-21 13:48:26 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "StatusToAS: error parsing url %s: %s" , s . Account . URI , err )
2021-05-21 13:48:26 +00:00
}
attributedToProp := streams . NewActivityStreamsAttributedToProperty ( )
attributedToProp . AppendIRI ( authorAccountURI )
status . SetActivityStreamsAttributedTo ( attributedToProp )
// tags
tagProp := streams . NewActivityStreamsTagProperty ( )
// tag -- mentions
2021-08-20 10:26:56 +00:00
for _ , m := range s . Mentions {
2021-08-25 13:34:33 +00:00
asMention , err := c . MentionToAS ( ctx , m )
2021-05-21 13:48:26 +00:00
if err != nil {
return nil , fmt . Errorf ( "StatusToAS: error converting mention to AS mention: %s" , err )
}
tagProp . AppendActivityStreamsMention ( asMention )
}
// tag -- emojis
// TODO
// tag -- hashtags
// TODO
status . SetActivityStreamsTag ( tagProp )
// parse out some URIs we need here
2021-08-20 10:26:56 +00:00
authorFollowersURI , err := url . Parse ( s . Account . FollowersURI )
2021-05-21 13:48:26 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "StatusToAS: error parsing url %s: %s" , s . Account . FollowersURI , err )
2021-05-21 13:48:26 +00:00
}
publicURI , err := url . Parse ( asPublicURI )
if err != nil {
return nil , fmt . Errorf ( "StatusToAS: error parsing url %s: %s" , asPublicURI , err )
}
// to and cc
toProp := streams . NewActivityStreamsToProperty ( )
ccProp := streams . NewActivityStreamsCcProperty ( )
switch s . Visibility {
case gtsmodel . VisibilityDirect :
// if DIRECT, then only mentioned users should be added to TO, and nothing to CC
2021-08-20 10:26:56 +00:00
for _ , m := range s . Mentions {
iri , err := url . Parse ( m . OriginAccount . URI )
2021-05-21 13:48:26 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "StatusToAS: error parsing uri %s: %s" , m . OriginAccount . URI , err )
2021-05-21 13:48:26 +00:00
}
toProp . AppendIRI ( iri )
}
case gtsmodel . VisibilityMutualsOnly :
// TODO
case gtsmodel . VisibilityFollowersOnly :
// if FOLLOWERS ONLY then we want to add followers to TO, and mentions to CC
toProp . AppendIRI ( authorFollowersURI )
2021-08-20 10:26:56 +00:00
for _ , m := range s . Mentions {
iri , err := url . Parse ( m . OriginAccount . URI )
2021-05-21 13:48:26 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "StatusToAS: error parsing uri %s: %s" , m . OriginAccount . URI , err )
2021-05-21 13:48:26 +00:00
}
ccProp . AppendIRI ( iri )
}
case gtsmodel . VisibilityUnlocked :
// if UNLOCKED, we want to add followers to TO, and public and mentions to CC
toProp . AppendIRI ( authorFollowersURI )
ccProp . AppendIRI ( publicURI )
2021-08-20 10:26:56 +00:00
for _ , m := range s . Mentions {
iri , err := url . Parse ( m . OriginAccount . URI )
2021-05-21 13:48:26 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "StatusToAS: error parsing uri %s: %s" , m . OriginAccount . URI , err )
2021-05-21 13:48:26 +00:00
}
ccProp . AppendIRI ( iri )
}
case gtsmodel . VisibilityPublic :
// if PUBLIC, we want to add public to TO, and followers and mentions to CC
toProp . AppendIRI ( publicURI )
ccProp . AppendIRI ( authorFollowersURI )
2021-08-20 10:26:56 +00:00
for _ , m := range s . Mentions {
iri , err := url . Parse ( m . OriginAccount . URI )
2021-05-21 13:48:26 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "StatusToAS: error parsing uri %s: %s" , m . OriginAccount . URI , err )
2021-05-21 13:48:26 +00:00
}
ccProp . AppendIRI ( iri )
}
}
status . SetActivityStreamsTo ( toProp )
status . SetActivityStreamsCc ( ccProp )
// conversation
// TODO
// content -- the actual post itself
contentProp := streams . NewActivityStreamsContentProperty ( )
contentProp . AppendXMLSchemaString ( s . Content )
status . SetActivityStreamsContent ( contentProp )
// attachment
attachmentProp := streams . NewActivityStreamsAttachmentProperty ( )
2021-08-20 10:26:56 +00:00
for _ , a := range s . Attachments {
2021-08-25 13:34:33 +00:00
doc , err := c . AttachmentToAS ( ctx , a )
2021-05-21 13:48:26 +00:00
if err != nil {
return nil , fmt . Errorf ( "StatusToAS: error converting attachment: %s" , err )
}
attachmentProp . AppendActivityStreamsDocument ( doc )
}
status . SetActivityStreamsAttachment ( attachmentProp )
// replies
2021-08-25 13:34:33 +00:00
repliesCollection , err := c . StatusToASRepliesCollection ( ctx , s , false )
2021-08-10 11:32:39 +00:00
if err != nil {
return nil , fmt . Errorf ( "error creating repliesCollection: %s" , err )
}
repliesProp := streams . NewActivityStreamsRepliesProperty ( )
repliesProp . SetActivityStreamsCollection ( repliesCollection )
status . SetActivityStreamsReplies ( repliesProp )
2021-05-21 21:04:59 +00:00
2021-08-20 10:26:56 +00:00
// put the note in our cache in case we need it again soon
if err := c . asCache . Store ( s . ID , status ) ; err != nil {
return nil , err
}
2021-05-21 13:48:26 +00:00
return status , nil
}
2021-08-25 13:34:33 +00:00
func ( c * converter ) FollowToAS ( ctx context . Context , f * gtsmodel . Follow , originAccount * gtsmodel . Account , targetAccount * gtsmodel . Account ) ( vocab . ActivityStreamsFollow , error ) {
2021-05-21 13:48:26 +00:00
// parse out the various URIs we need for this
// origin account (who's doing the follow)
originAccountURI , err := url . Parse ( originAccount . URI )
if err != nil {
return nil , fmt . Errorf ( "followtoasfollow: error parsing origin account uri: %s" , err )
}
originActor := streams . NewActivityStreamsActorProperty ( )
originActor . AppendIRI ( originAccountURI )
// target account (who's being followed)
targetAccountURI , err := url . Parse ( targetAccount . URI )
if err != nil {
return nil , fmt . Errorf ( "followtoasfollow: error parsing target account uri: %s" , err )
}
// uri of the follow activity itself
followURI , err := url . Parse ( f . URI )
if err != nil {
return nil , fmt . Errorf ( "followtoasfollow: error parsing follow uri: %s" , err )
}
// start preparing the follow activity
follow := streams . NewActivityStreamsFollow ( )
// set the actor
follow . SetActivityStreamsActor ( originActor )
// set the id
followIDProp := streams . NewJSONLDIdProperty ( )
followIDProp . SetIRI ( followURI )
follow . SetJSONLDId ( followIDProp )
// set the object
followObjectProp := streams . NewActivityStreamsObjectProperty ( )
followObjectProp . AppendIRI ( targetAccountURI )
follow . SetActivityStreamsObject ( followObjectProp )
// set the To property
followToProp := streams . NewActivityStreamsToProperty ( )
followToProp . AppendIRI ( targetAccountURI )
follow . SetActivityStreamsTo ( followToProp )
return follow , nil
}
2021-08-25 13:34:33 +00:00
func ( c * converter ) MentionToAS ( ctx context . Context , m * gtsmodel . Mention ) ( vocab . ActivityStreamsMention , error ) {
2021-08-29 10:03:08 +00:00
if m . TargetAccount == nil {
a , err := c . db . GetAccountByID ( ctx , m . TargetAccountID )
if err != nil {
2021-05-21 13:48:26 +00:00
return nil , fmt . Errorf ( "MentionToAS: error getting target account from db: %s" , err )
}
2021-08-29 10:03:08 +00:00
m . TargetAccount = a
2021-05-21 13:48:26 +00:00
}
// create the mention
mention := streams . NewActivityStreamsMention ( )
// href -- this should be the URI of the mentioned user
hrefProp := streams . NewActivityStreamsHrefProperty ( )
2021-08-29 10:03:08 +00:00
hrefURI , err := url . Parse ( m . TargetAccount . URI )
2021-05-21 13:48:26 +00:00
if err != nil {
2021-08-29 10:03:08 +00:00
return nil , fmt . Errorf ( "MentionToAS: error parsing uri %s: %s" , m . TargetAccount . URI , err )
2021-05-21 13:48:26 +00:00
}
hrefProp . SetIRI ( hrefURI )
mention . SetActivityStreamsHref ( hrefProp )
// name -- this should be the namestring of the mentioned user, something like @whatever@example.org
var domain string
2021-08-29 10:03:08 +00:00
if m . TargetAccount . Domain == "" {
2021-07-19 16:42:08 +00:00
domain = c . config . AccountDomain
2021-05-21 13:48:26 +00:00
} else {
2021-08-29 10:03:08 +00:00
domain = m . TargetAccount . Domain
2021-05-21 13:48:26 +00:00
}
2021-08-29 10:03:08 +00:00
username := m . TargetAccount . Username
2021-05-21 13:48:26 +00:00
nameString := fmt . Sprintf ( "@%s@%s" , username , domain )
nameProp := streams . NewActivityStreamsNameProperty ( )
nameProp . AppendXMLSchemaString ( nameString )
mention . SetActivityStreamsName ( nameProp )
return mention , nil
}
2021-08-25 13:34:33 +00:00
func ( c * converter ) AttachmentToAS ( ctx context . Context , a * gtsmodel . MediaAttachment ) ( vocab . ActivityStreamsDocument , error ) {
2021-05-21 13:48:26 +00:00
// type -- Document
doc := streams . NewActivityStreamsDocument ( )
// mediaType aka mime content type
mediaTypeProp := streams . NewActivityStreamsMediaTypeProperty ( )
mediaTypeProp . Set ( a . File . ContentType )
doc . SetActivityStreamsMediaType ( mediaTypeProp )
// url -- for the original image not the thumbnail
urlProp := streams . NewActivityStreamsUrlProperty ( )
imageURL , err := url . Parse ( a . URL )
if err != nil {
return nil , fmt . Errorf ( "AttachmentToAS: error parsing uri %s: %s" , a . URL , err )
}
urlProp . AppendIRI ( imageURL )
doc . SetActivityStreamsUrl ( urlProp )
// name -- aka image description
nameProp := streams . NewActivityStreamsNameProperty ( )
nameProp . AppendXMLSchemaString ( a . Description )
doc . SetActivityStreamsName ( nameProp )
// blurhash
blurProp := streams . NewTootBlurhashProperty ( )
blurProp . Set ( a . Blurhash )
doc . SetTootBlurhash ( blurProp )
// focalpoint
// TODO
return doc , nil
2021-05-08 12:25:55 +00:00
}
2021-05-24 16:49:48 +00:00
/ *
We want to end up with something like this :
{
"@context" : "https://www.w3.org/ns/activitystreams" ,
"actor" : "https://ondergrond.org/users/dumpsterqueer" ,
"id" : "https://ondergrond.org/users/dumpsterqueer#likes/44584" ,
"object" : "https://testingtesting123.xyz/users/gotosocial_test_account/statuses/771aea80-a33d-4d6d-8dfd-57d4d2bfcbd4" ,
"type" : "Like"
}
* /
2021-08-25 13:34:33 +00:00
func ( c * converter ) FaveToAS ( ctx context . Context , f * gtsmodel . StatusFave ) ( vocab . ActivityStreamsLike , error ) {
2021-05-24 16:49:48 +00:00
// check if targetStatus is already pinned to this fave, and fetch it if not
2021-08-20 10:26:56 +00:00
if f . Status == nil {
2021-08-29 10:03:08 +00:00
s , err := c . db . GetStatusByID ( ctx , f . StatusID )
if err != nil {
2021-05-24 16:49:48 +00:00
return nil , fmt . Errorf ( "FaveToAS: error fetching target status from database: %s" , err )
}
2021-08-20 10:26:56 +00:00
f . Status = s
2021-05-24 16:49:48 +00:00
}
// check if the targetAccount is already pinned to this fave, and fetch it if not
2021-08-20 10:26:56 +00:00
if f . TargetAccount == nil {
2021-08-29 10:03:08 +00:00
a , err := c . db . GetAccountByID ( ctx , f . TargetAccountID )
if err != nil {
2021-05-24 16:49:48 +00:00
return nil , fmt . Errorf ( "FaveToAS: error fetching target account from database: %s" , err )
}
2021-08-20 10:26:56 +00:00
f . TargetAccount = a
2021-05-24 16:49:48 +00:00
}
// check if the faving account is already pinned to this fave, and fetch it if not
2021-08-20 10:26:56 +00:00
if f . Account == nil {
2021-08-29 10:03:08 +00:00
a , err := c . db . GetAccountByID ( ctx , f . AccountID )
if err != nil {
2021-05-24 16:49:48 +00:00
return nil , fmt . Errorf ( "FaveToAS: error fetching faving account from database: %s" , err )
}
2021-08-20 10:26:56 +00:00
f . Account = a
2021-05-24 16:49:48 +00:00
}
// create the like
like := streams . NewActivityStreamsLike ( )
// set the actor property to the fave-ing account's URI
actorProp := streams . NewActivityStreamsActorProperty ( )
2021-08-20 10:26:56 +00:00
actorIRI , err := url . Parse ( f . Account . URI )
2021-05-24 16:49:48 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "FaveToAS: error parsing uri %s: %s" , f . Account . URI , err )
2021-05-24 16:49:48 +00:00
}
actorProp . AppendIRI ( actorIRI )
like . SetActivityStreamsActor ( actorProp )
// set the ID property to the fave's URI
idProp := streams . NewJSONLDIdProperty ( )
idIRI , err := url . Parse ( f . URI )
if err != nil {
return nil , fmt . Errorf ( "FaveToAS: error parsing uri %s: %s" , f . URI , err )
}
idProp . Set ( idIRI )
like . SetJSONLDId ( idProp )
// set the object property to the target status's URI
objectProp := streams . NewActivityStreamsObjectProperty ( )
2021-08-20 10:26:56 +00:00
statusIRI , err := url . Parse ( f . Status . URI )
2021-05-24 16:49:48 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "FaveToAS: error parsing uri %s: %s" , f . Status . URI , err )
2021-05-24 16:49:48 +00:00
}
objectProp . AppendIRI ( statusIRI )
like . SetActivityStreamsObject ( objectProp )
// set the TO property to the target account's IRI
toProp := streams . NewActivityStreamsToProperty ( )
2021-08-20 10:26:56 +00:00
toIRI , err := url . Parse ( f . TargetAccount . URI )
2021-05-24 16:49:48 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "FaveToAS: error parsing uri %s: %s" , f . TargetAccount . URI , err )
2021-05-24 16:49:48 +00:00
}
toProp . AppendIRI ( toIRI )
like . SetActivityStreamsTo ( toProp )
return like , nil
}
2021-05-28 17:57:04 +00:00
2021-08-25 13:34:33 +00:00
func ( c * converter ) BoostToAS ( ctx context . Context , boostWrapperStatus * gtsmodel . Status , boostingAccount * gtsmodel . Account , boostedAccount * gtsmodel . Account ) ( vocab . ActivityStreamsAnnounce , error ) {
2021-05-28 17:57:04 +00:00
// the boosted status is probably pinned to the boostWrapperStatus but double check to make sure
2021-08-20 10:26:56 +00:00
if boostWrapperStatus . BoostOf == nil {
2021-08-29 10:03:08 +00:00
b , err := c . db . GetStatusByID ( ctx , boostWrapperStatus . BoostOfID )
if err != nil {
2021-05-28 17:57:04 +00:00
return nil , fmt . Errorf ( "BoostToAS: error getting status with ID %s from the db: %s" , boostWrapperStatus . BoostOfID , err )
}
2021-08-20 10:26:56 +00:00
boostWrapperStatus . BoostOf = b
2021-05-28 17:57:04 +00:00
}
// create the announce
announce := streams . NewActivityStreamsAnnounce ( )
// set the actor
boosterURI , err := url . Parse ( boostingAccount . URI )
if err != nil {
return nil , fmt . Errorf ( "BoostToAS: error parsing uri %s: %s" , boostingAccount . URI , err )
}
actorProp := streams . NewActivityStreamsActorProperty ( )
actorProp . AppendIRI ( boosterURI )
announce . SetActivityStreamsActor ( actorProp )
// set the ID
boostIDURI , err := url . Parse ( boostWrapperStatus . URI )
if err != nil {
return nil , fmt . Errorf ( "BoostToAS: error parsing uri %s: %s" , boostWrapperStatus . URI , err )
}
idProp := streams . NewJSONLDIdProperty ( )
idProp . SetIRI ( boostIDURI )
announce . SetJSONLDId ( idProp )
// set the object
2021-08-20 10:26:56 +00:00
boostedStatusURI , err := url . Parse ( boostWrapperStatus . BoostOf . URI )
2021-05-28 17:57:04 +00:00
if err != nil {
2021-08-20 10:26:56 +00:00
return nil , fmt . Errorf ( "BoostToAS: error parsing uri %s: %s" , boostWrapperStatus . BoostOf . URI , err )
2021-05-28 17:57:04 +00:00
}
objectProp := streams . NewActivityStreamsObjectProperty ( )
objectProp . AppendIRI ( boostedStatusURI )
announce . SetActivityStreamsObject ( objectProp )
// set the published time
publishedProp := streams . NewActivityStreamsPublishedProperty ( )
publishedProp . Set ( boostWrapperStatus . CreatedAt )
announce . SetActivityStreamsPublished ( publishedProp )
// set the to
followersURI , err := url . Parse ( boostingAccount . FollowersURI )
if err != nil {
return nil , fmt . Errorf ( "BoostToAS: error parsing uri %s: %s" , boostingAccount . FollowersURI , err )
}
toProp := streams . NewActivityStreamsToProperty ( )
toProp . AppendIRI ( followersURI )
announce . SetActivityStreamsTo ( toProp )
// set the cc
boostedURI , err := url . Parse ( boostedAccount . URI )
if err != nil {
return nil , fmt . Errorf ( "BoostToAS: error parsing uri %s: %s" , boostedAccount . URI , err )
}
publicURI , err := url . Parse ( asPublicURI )
if err != nil {
return nil , fmt . Errorf ( "BoostToAS: error parsing uri %s: %s" , asPublicURI , err )
}
ccProp := streams . NewActivityStreamsCcProperty ( )
ccProp . AppendIRI ( boostedURI )
ccProp . AppendIRI ( publicURI )
announce . SetActivityStreamsCc ( ccProp )
return announce , nil
}
2021-07-11 14:22:21 +00:00
/ *
we want to end up with something like this :
{
"@context" : "https://www.w3.org/ns/activitystreams" ,
"actor" : "https://example.org/users/some_user" ,
"id" : "https://example.org/users/some_user/blocks/SOME_ULID_OF_A_BLOCK" ,
"object" : "https://some_other.instance/users/some_other_user" ,
"type" : "Block"
}
* /
2021-08-25 13:34:33 +00:00
func ( c * converter ) BlockToAS ( ctx context . Context , b * gtsmodel . Block ) ( vocab . ActivityStreamsBlock , error ) {
2021-07-11 14:22:21 +00:00
if b . Account == nil {
2021-08-29 10:03:08 +00:00
a , err := c . db . GetAccountByID ( ctx , b . AccountID )
if err != nil {
return nil , fmt . Errorf ( "BlockToAS: error getting block owner account from database: %s" , err )
2021-07-11 14:22:21 +00:00
}
b . Account = a
}
if b . TargetAccount == nil {
2021-08-29 10:03:08 +00:00
a , err := c . db . GetAccountByID ( ctx , b . TargetAccountID )
if err != nil {
2021-07-11 14:22:21 +00:00
return nil , fmt . Errorf ( "BlockToAS: error getting block target account from database: %s" , err )
}
b . TargetAccount = a
}
// create the block
block := streams . NewActivityStreamsBlock ( )
// set the actor property to the block-ing account's URI
actorProp := streams . NewActivityStreamsActorProperty ( )
actorIRI , err := url . Parse ( b . Account . URI )
if err != nil {
return nil , fmt . Errorf ( "BlockToAS: error parsing uri %s: %s" , b . Account . URI , err )
}
actorProp . AppendIRI ( actorIRI )
block . SetActivityStreamsActor ( actorProp )
// set the ID property to the blocks's URI
idProp := streams . NewJSONLDIdProperty ( )
idIRI , err := url . Parse ( b . URI )
if err != nil {
return nil , fmt . Errorf ( "BlockToAS: error parsing uri %s: %s" , b . URI , err )
}
idProp . Set ( idIRI )
block . SetJSONLDId ( idProp )
// set the object property to the target account's URI
objectProp := streams . NewActivityStreamsObjectProperty ( )
targetIRI , err := url . Parse ( b . TargetAccount . URI )
if err != nil {
return nil , fmt . Errorf ( "BlockToAS: error parsing uri %s: %s" , b . TargetAccount . URI , err )
}
objectProp . AppendIRI ( targetIRI )
block . SetActivityStreamsObject ( objectProp )
// set the TO property to the target account's IRI
toProp := streams . NewActivityStreamsToProperty ( )
toIRI , err := url . Parse ( b . TargetAccount . URI )
if err != nil {
return nil , fmt . Errorf ( "BlockToAS: error parsing uri %s: %s" , b . TargetAccount . URI , err )
}
toProp . AppendIRI ( toIRI )
block . SetActivityStreamsTo ( toProp )
return block , nil
}
2021-08-10 11:32:39 +00:00
/ *
the goal is to end up with something like this :
{
"@context" : "https://www.w3.org/ns/activitystreams" ,
"id" : "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies" ,
"type" : "Collection" ,
"first" : {
"id" : "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?page=true" ,
"type" : "CollectionPage" ,
"next" : "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true" ,
"partOf" : "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies" ,
"items" : [ ]
}
}
* /
2021-08-25 13:34:33 +00:00
func ( c * converter ) StatusToASRepliesCollection ( ctx context . Context , status * gtsmodel . Status , onlyOtherAccounts bool ) ( vocab . ActivityStreamsCollection , error ) {
2021-08-10 11:32:39 +00:00
collectionID := fmt . Sprintf ( "%s/replies" , status . URI )
collectionIDURI , err := url . Parse ( collectionID )
if err != nil {
return nil , err
}
collection := streams . NewActivityStreamsCollection ( )
// collection.id
collectionIDProp := streams . NewJSONLDIdProperty ( )
collectionIDProp . SetIRI ( collectionIDURI )
collection . SetJSONLDId ( collectionIDProp )
// first
first := streams . NewActivityStreamsFirstProperty ( )
firstPage := streams . NewActivityStreamsCollectionPage ( )
// first.id
firstPageIDProp := streams . NewJSONLDIdProperty ( )
firstPageID , err := url . Parse ( fmt . Sprintf ( "%s?page=true" , collectionID ) )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
firstPageIDProp . SetIRI ( firstPageID )
firstPage . SetJSONLDId ( firstPageIDProp )
// first.next
nextProp := streams . NewActivityStreamsNextProperty ( )
nextPropID , err := url . Parse ( fmt . Sprintf ( "%s?only_other_accounts=%t&page=true" , collectionID , onlyOtherAccounts ) )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
nextProp . SetIRI ( nextPropID )
firstPage . SetActivityStreamsNext ( nextProp )
// first.partOf
partOfProp := streams . NewActivityStreamsPartOfProperty ( )
partOfProp . SetIRI ( collectionIDURI )
firstPage . SetActivityStreamsPartOf ( partOfProp )
first . SetActivityStreamsCollectionPage ( firstPage )
// collection.first
collection . SetActivityStreamsFirst ( first )
return collection , nil
}
/ *
the goal is to end up with something like this :
{
"@context" : "https://www.w3.org/ns/activitystreams" ,
"id" : "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true" ,
"type" : "CollectionPage" ,
"next" : "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?min_id=106720870266901180&only_other_accounts=true&page=true" ,
"partOf" : "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies" ,
"items" : [
"https://example.com/users/someone/statuses/106720752853216226" ,
"https://somewhere.online/users/eeeeeeeeeep/statuses/106720870163727231"
]
}
* /
2021-08-25 13:34:33 +00:00
func ( c * converter ) StatusURIsToASRepliesPage ( ctx context . Context , status * gtsmodel . Status , onlyOtherAccounts bool , minID string , replies map [ string ] * url . URL ) ( vocab . ActivityStreamsCollectionPage , error ) {
2021-08-10 11:32:39 +00:00
collectionID := fmt . Sprintf ( "%s/replies" , status . URI )
page := streams . NewActivityStreamsCollectionPage ( )
// .id
pageIDProp := streams . NewJSONLDIdProperty ( )
pageIDString := fmt . Sprintf ( "%s?page=true&only_other_accounts=%t" , collectionID , onlyOtherAccounts )
if minID != "" {
pageIDString = fmt . Sprintf ( "%s&min_id=%s" , pageIDString , minID )
}
pageID , err := url . Parse ( pageIDString )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
pageIDProp . SetIRI ( pageID )
page . SetJSONLDId ( pageIDProp )
// .partOf
collectionIDURI , err := url . Parse ( collectionID )
if err != nil {
return nil , err
}
partOfProp := streams . NewActivityStreamsPartOfProperty ( )
partOfProp . SetIRI ( collectionIDURI )
page . SetActivityStreamsPartOf ( partOfProp )
// .items
items := streams . NewActivityStreamsItemsProperty ( )
var highestID string
for k , v := range replies {
items . AppendIRI ( v )
if k > highestID {
highestID = k
}
}
page . SetActivityStreamsItems ( items )
// .next
nextProp := streams . NewActivityStreamsNextProperty ( )
nextPropIDString := fmt . Sprintf ( "%s?only_other_accounts=%t&page=true" , collectionID , onlyOtherAccounts )
if highestID != "" {
nextPropIDString = fmt . Sprintf ( "%s&min_id=%s" , nextPropIDString , highestID )
}
nextPropID , err := url . Parse ( nextPropIDString )
if err != nil {
return nil , gtserror . NewErrorInternalError ( err )
}
nextProp . SetIRI ( nextPropID )
page . SetActivityStreamsNext ( nextProp )
return page , nil
}