mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-23 02:32:12 +00:00
7cc40302a5
* add miekg/dns dependency * set/validate accountDomain * move finger to dereferencer * totally break GetRemoteAccount * start reworking finger func a bit * start reworking getRemoteAccount a bit * move mention parts to namestring * rework webfingerget * use util function to extract webfinger parts * use accountDomain * rework finger again, final form * just a real nasty commit, the worst * remove refresh from account * use new ASRepToAccount signature * fix incorrect debug call * fix for new getRemoteAccount * rework GetRemoteAccount * start updating tests to remove repetition * break a lot of tests Move shared test logic into the testrig, rather than having it scattered all over the place. This allows us to just mock the transport controller once, and have all tests use it (unless they need not to for some other reason). * fix up tests to use main mock httpclient * webfinger only if necessary * cheeky linting with the lads * update mentionName regex recognize instance accounts * don't finger instance accounts * test webfinger part extraction * increase default worker count to 4 per cpu * don't repeat regex parsing * final search for discovered accountDomain * be more permissive in namestring lookup * add more extraction tests * simplify GetParseMentionFunc * skip long search if local account * fix broken test * consolidate to all use same caching libraries Signed-off-by: kim <grufwub@gmail.com> * perform more caching in the database layer Signed-off-by: kim <grufwub@gmail.com> * remove ASNote cache Signed-off-by: kim <grufwub@gmail.com> * update cache library, improve db tracing hooks Signed-off-by: kim <grufwub@gmail.com> * return ErrNoEntries if no account status IDs found, small formatting changes Signed-off-by: kim <grufwub@gmail.com> * fix tests, thanks tobi! Signed-off-by: kim <grufwub@gmail.com> Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
1168 lines
37 KiB
Go
1168 lines
37 KiB
Go
/*
|
|
GoToSocial
|
|
Copyright (C) 2021-2022 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 (
|
|
"context"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/superseriousbusiness/activity/pub"
|
|
"github.com/superseriousbusiness/activity/streams"
|
|
"github.com/superseriousbusiness/activity/streams/vocab"
|
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
|
)
|
|
|
|
// const (
|
|
// // highestID is the highest possible ULID
|
|
// highestID = "ZZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
|
// // lowestID is the lowest possible ULID
|
|
// lowestID = "00000000000000000000000000"
|
|
// )
|
|
|
|
// Converts a gts model account into an Activity Streams person type.
|
|
func (c *converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) {
|
|
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.
|
|
manuallyApprovesFollowersProp := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
|
|
manuallyApprovesFollowersProp.Set(a.Locked)
|
|
person.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp)
|
|
|
|
// 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 != "" {
|
|
if a.AvatarMediaAttachment == nil {
|
|
avatar, err := c.db.GetAttachmentByID(ctx, a.AvatarMediaAttachmentID)
|
|
if err == nil {
|
|
a.AvatarMediaAttachment = avatar
|
|
} else {
|
|
logrus.Errorf("AccountToAS: error getting Avatar with id %s: %s", a.AvatarMediaAttachmentID, err)
|
|
}
|
|
}
|
|
|
|
if a.AvatarMediaAttachment != nil {
|
|
iconProperty := streams.NewActivityStreamsIconProperty()
|
|
|
|
iconImage := streams.NewActivityStreamsImage()
|
|
|
|
mediaType := streams.NewActivityStreamsMediaTypeProperty()
|
|
mediaType.Set(a.AvatarMediaAttachment.File.ContentType)
|
|
iconImage.SetActivityStreamsMediaType(mediaType)
|
|
|
|
avatarURLProperty := streams.NewActivityStreamsUrlProperty()
|
|
avatarURL, err := url.Parse(a.AvatarMediaAttachment.URL)
|
|
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 != "" {
|
|
if a.HeaderMediaAttachment == nil {
|
|
header, err := c.db.GetAttachmentByID(ctx, a.HeaderMediaAttachmentID)
|
|
if err == nil {
|
|
a.HeaderMediaAttachment = header
|
|
} else {
|
|
logrus.Errorf("AccountToAS: error getting Header with id %s: %s", a.HeaderMediaAttachmentID, err)
|
|
}
|
|
}
|
|
|
|
if a.HeaderMediaAttachment != nil {
|
|
headerProperty := streams.NewActivityStreamsImageProperty()
|
|
|
|
headerImage := streams.NewActivityStreamsImage()
|
|
|
|
mediaType := streams.NewActivityStreamsMediaTypeProperty()
|
|
mediaType.Set(a.HeaderMediaAttachment.File.ContentType)
|
|
headerImage.SetActivityStreamsMediaType(mediaType)
|
|
|
|
headerURLProperty := streams.NewActivityStreamsUrlProperty()
|
|
headerURL, err := url.Parse(a.HeaderMediaAttachment.URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
headerURLProperty.AppendIRI(headerURL)
|
|
headerImage.SetActivityStreamsUrl(headerURLProperty)
|
|
|
|
headerProperty.AppendActivityStreamsImage(headerImage)
|
|
person.SetActivityStreamsImage(headerProperty)
|
|
}
|
|
}
|
|
|
|
return person, nil
|
|
}
|
|
|
|
// Converts a gts model account into a VERY MINIMAL Activity Streams person type.
|
|
//
|
|
// The returned account will just have the Type, Username, PublicKey, and ID properties set.
|
|
func (c *converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) {
|
|
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
|
|
}
|
|
|
|
func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) {
|
|
// 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
|
|
if s.Account == nil {
|
|
a, err := c.db.GetAccountByID(ctx, s.AccountID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error retrieving author account from db: %s", err)
|
|
}
|
|
s.Account = a
|
|
}
|
|
|
|
// 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
|
|
if s.InReplyTo == nil {
|
|
rs, err := c.db.GetStatusByID(ctx, s.InReplyToID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error getting replied to status %s: %s", s.InReplyToID, err)
|
|
}
|
|
s.InReplyTo = rs
|
|
}
|
|
rURI, err := url.Parse(s.InReplyTo.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.InReplyTo.URI, err)
|
|
}
|
|
|
|
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
|
|
authorAccountURI, err := url.Parse(s.Account.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.Account.URI, err)
|
|
}
|
|
attributedToProp := streams.NewActivityStreamsAttributedToProperty()
|
|
attributedToProp.AppendIRI(authorAccountURI)
|
|
status.SetActivityStreamsAttributedTo(attributedToProp)
|
|
|
|
// tags
|
|
tagProp := streams.NewActivityStreamsTagProperty()
|
|
|
|
// tag -- mentions
|
|
for _, m := range s.Mentions {
|
|
asMention, err := c.MentionToAS(ctx, m)
|
|
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
|
|
authorFollowersURI, err := url.Parse(s.Account.FollowersURI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.Account.FollowersURI, err)
|
|
}
|
|
|
|
publicURI, err := url.Parse(pub.PublicActivityPubIRI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", pub.PublicActivityPubIRI, 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
|
|
for _, m := range s.Mentions {
|
|
iri, err := url.Parse(m.TargetAccount.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.TargetAccount.URI, err)
|
|
}
|
|
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)
|
|
for _, m := range s.Mentions {
|
|
iri, err := url.Parse(m.TargetAccount.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.TargetAccount.URI, err)
|
|
}
|
|
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)
|
|
for _, m := range s.Mentions {
|
|
iri, err := url.Parse(m.TargetAccount.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.TargetAccount.URI, err)
|
|
}
|
|
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)
|
|
for _, m := range s.Mentions {
|
|
iri, err := url.Parse(m.TargetAccount.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.TargetAccount.URI, err)
|
|
}
|
|
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()
|
|
for _, a := range s.Attachments {
|
|
doc, err := c.AttachmentToAS(ctx, a)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("StatusToAS: error converting attachment: %s", err)
|
|
}
|
|
attachmentProp.AppendActivityStreamsDocument(doc)
|
|
}
|
|
status.SetActivityStreamsAttachment(attachmentProp)
|
|
|
|
// replies
|
|
repliesCollection, err := c.StatusToASRepliesCollection(ctx, s, false)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating repliesCollection: %s", err)
|
|
}
|
|
|
|
repliesProp := streams.NewActivityStreamsRepliesProperty()
|
|
repliesProp.SetActivityStreamsCollection(repliesCollection)
|
|
status.SetActivityStreamsReplies(repliesProp)
|
|
|
|
// sensitive
|
|
sensitiveProp := streams.NewActivityStreamsSensitiveProperty()
|
|
sensitiveProp.AppendXMLSchemaBoolean(s.Sensitive)
|
|
status.SetActivityStreamsSensitive(sensitiveProp)
|
|
|
|
return status, nil
|
|
}
|
|
|
|
func (c *converter) FollowToAS(ctx context.Context, f *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (vocab.ActivityStreamsFollow, error) {
|
|
// 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
|
|
}
|
|
|
|
func (c *converter) MentionToAS(ctx context.Context, m *gtsmodel.Mention) (vocab.ActivityStreamsMention, error) {
|
|
if m.TargetAccount == nil {
|
|
a, err := c.db.GetAccountByID(ctx, m.TargetAccountID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("MentionToAS: error getting target account from db: %s", err)
|
|
}
|
|
m.TargetAccount = a
|
|
}
|
|
|
|
// create the mention
|
|
mention := streams.NewActivityStreamsMention()
|
|
|
|
// href -- this should be the URI of the mentioned user
|
|
hrefProp := streams.NewActivityStreamsHrefProperty()
|
|
hrefURI, err := url.Parse(m.TargetAccount.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("MentionToAS: error parsing uri %s: %s", m.TargetAccount.URI, err)
|
|
}
|
|
hrefProp.SetIRI(hrefURI)
|
|
mention.SetActivityStreamsHref(hrefProp)
|
|
|
|
// name -- this should be the namestring of the mentioned user, something like @whatever@example.org
|
|
var domain string
|
|
if m.TargetAccount.Domain == "" {
|
|
accountDomain := config.GetAccountDomain()
|
|
if accountDomain == "" {
|
|
accountDomain = config.GetHost()
|
|
}
|
|
domain = accountDomain
|
|
} else {
|
|
domain = m.TargetAccount.Domain
|
|
}
|
|
username := m.TargetAccount.Username
|
|
nameString := fmt.Sprintf("@%s@%s", username, domain)
|
|
nameProp := streams.NewActivityStreamsNameProperty()
|
|
nameProp.AppendXMLSchemaString(nameString)
|
|
mention.SetActivityStreamsName(nameProp)
|
|
|
|
return mention, nil
|
|
}
|
|
|
|
func (c *converter) AttachmentToAS(ctx context.Context, a *gtsmodel.MediaAttachment) (vocab.ActivityStreamsDocument, error) {
|
|
// 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
|
|
}
|
|
|
|
/*
|
|
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"
|
|
}
|
|
*/
|
|
func (c *converter) FaveToAS(ctx context.Context, f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, error) {
|
|
// check if targetStatus is already pinned to this fave, and fetch it if not
|
|
if f.Status == nil {
|
|
s, err := c.db.GetStatusByID(ctx, f.StatusID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("FaveToAS: error fetching target status from database: %s", err)
|
|
}
|
|
f.Status = s
|
|
}
|
|
|
|
// check if the targetAccount is already pinned to this fave, and fetch it if not
|
|
if f.TargetAccount == nil {
|
|
a, err := c.db.GetAccountByID(ctx, f.TargetAccountID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("FaveToAS: error fetching target account from database: %s", err)
|
|
}
|
|
f.TargetAccount = a
|
|
}
|
|
|
|
// check if the faving account is already pinned to this fave, and fetch it if not
|
|
if f.Account == nil {
|
|
a, err := c.db.GetAccountByID(ctx, f.AccountID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("FaveToAS: error fetching faving account from database: %s", err)
|
|
}
|
|
f.Account = a
|
|
}
|
|
|
|
// create the like
|
|
like := streams.NewActivityStreamsLike()
|
|
|
|
// set the actor property to the fave-ing account's URI
|
|
actorProp := streams.NewActivityStreamsActorProperty()
|
|
actorIRI, err := url.Parse(f.Account.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.Account.URI, err)
|
|
}
|
|
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()
|
|
statusIRI, err := url.Parse(f.Status.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.Status.URI, err)
|
|
}
|
|
objectProp.AppendIRI(statusIRI)
|
|
like.SetActivityStreamsObject(objectProp)
|
|
|
|
// set the TO property to the target account's IRI
|
|
toProp := streams.NewActivityStreamsToProperty()
|
|
toIRI, err := url.Parse(f.TargetAccount.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.TargetAccount.URI, err)
|
|
}
|
|
toProp.AppendIRI(toIRI)
|
|
like.SetActivityStreamsTo(toProp)
|
|
|
|
return like, nil
|
|
}
|
|
|
|
func (c *converter) BoostToAS(ctx context.Context, boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) (vocab.ActivityStreamsAnnounce, error) {
|
|
// the boosted status is probably pinned to the boostWrapperStatus but double check to make sure
|
|
if boostWrapperStatus.BoostOf == nil {
|
|
b, err := c.db.GetStatusByID(ctx, boostWrapperStatus.BoostOfID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("BoostToAS: error getting status with ID %s from the db: %s", boostWrapperStatus.BoostOfID, err)
|
|
}
|
|
boostWrapperStatus.BoostOf = b
|
|
}
|
|
|
|
// 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
|
|
boostedStatusURI, err := url.Parse(boostWrapperStatus.BoostOf.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostWrapperStatus.BoostOf.URI, err)
|
|
}
|
|
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
|
|
ccProp := streams.NewActivityStreamsCcProperty()
|
|
boostedAccountURI, err := url.Parse(boostedAccount.URI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostedAccount.URI, err)
|
|
}
|
|
ccProp.AppendIRI(boostedAccountURI)
|
|
|
|
// maybe CC it to public depending on the boosted status visibility
|
|
switch boostWrapperStatus.BoostOf.Visibility {
|
|
case gtsmodel.VisibilityPublic, gtsmodel.VisibilityUnlocked:
|
|
publicURI, err := url.Parse(pub.PublicActivityPubIRI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", pub.PublicActivityPubIRI, err)
|
|
}
|
|
ccProp.AppendIRI(publicURI)
|
|
}
|
|
|
|
announce.SetActivityStreamsCc(ccProp)
|
|
|
|
return announce, nil
|
|
}
|
|
|
|
/*
|
|
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"
|
|
}
|
|
*/
|
|
func (c *converter) BlockToAS(ctx context.Context, b *gtsmodel.Block) (vocab.ActivityStreamsBlock, error) {
|
|
if b.Account == nil {
|
|
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)
|
|
}
|
|
b.Account = a
|
|
}
|
|
|
|
if b.TargetAccount == nil {
|
|
a, err := c.db.GetAccountByID(ctx, b.TargetAccountID)
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
/*
|
|
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": []
|
|
}
|
|
}
|
|
*/
|
|
func (c *converter) StatusToASRepliesCollection(ctx context.Context, status *gtsmodel.Status, onlyOtherAccounts bool) (vocab.ActivityStreamsCollection, error) {
|
|
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"
|
|
]
|
|
}
|
|
*/
|
|
func (c *converter) StatusURIsToASRepliesPage(ctx context.Context, status *gtsmodel.Status, onlyOtherAccounts bool, minID string, replies map[string]*url.URL) (vocab.ActivityStreamsCollectionPage, error) {
|
|
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
|
|
}
|
|
|
|
/*
|
|
the goal is to end up with something like this:
|
|
{
|
|
"id": "https://example.org/users/whatever/outbox?page=true",
|
|
"type": "OrderedCollectionPage",
|
|
"next": "https://example.org/users/whatever/outbox?max_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true",
|
|
"prev": "https://example.org/users/whatever/outbox?min_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true",
|
|
"partOf": "https://example.org/users/whatever/outbox",
|
|
"orderedItems": [
|
|
"id": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7/activity",
|
|
"type": "Create",
|
|
"actor": "https://example.org/users/whatever",
|
|
"published": "2021-10-18T20:06:18Z",
|
|
"to": [
|
|
"https://www.w3.org/ns/activitystreams#Public"
|
|
],
|
|
"cc": [
|
|
"https://example.org/users/whatever/followers"
|
|
],
|
|
"object": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7"
|
|
]
|
|
}
|
|
*/
|
|
func (c *converter) StatusesToASOutboxPage(ctx context.Context, outboxID string, maxID string, minID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollectionPage, error) {
|
|
page := streams.NewActivityStreamsOrderedCollectionPage()
|
|
|
|
// .id
|
|
pageIDProp := streams.NewJSONLDIdProperty()
|
|
pageID := fmt.Sprintf("%s?page=true", outboxID)
|
|
if minID != "" {
|
|
pageID = fmt.Sprintf("%s&minID=%s", pageID, minID)
|
|
}
|
|
if maxID != "" {
|
|
pageID = fmt.Sprintf("%s&maxID=%s", pageID, maxID)
|
|
}
|
|
pageIDURI, err := url.Parse(pageID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pageIDProp.SetIRI(pageIDURI)
|
|
page.SetJSONLDId(pageIDProp)
|
|
|
|
// .partOf
|
|
collectionIDURI, err := url.Parse(outboxID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
partOfProp := streams.NewActivityStreamsPartOfProperty()
|
|
partOfProp.SetIRI(collectionIDURI)
|
|
page.SetActivityStreamsPartOf(partOfProp)
|
|
|
|
// .orderedItems
|
|
itemsProp := streams.NewActivityStreamsOrderedItemsProperty()
|
|
var highest string
|
|
var lowest string
|
|
for _, s := range statuses {
|
|
note, err := c.StatusToAS(ctx, s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
create, err := c.WrapNoteInCreate(note, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
itemsProp.AppendActivityStreamsCreate(create)
|
|
|
|
if highest == "" || s.ID > highest {
|
|
highest = s.ID
|
|
}
|
|
if lowest == "" || s.ID < lowest {
|
|
lowest = s.ID
|
|
}
|
|
}
|
|
page.SetActivityStreamsOrderedItems(itemsProp)
|
|
|
|
// .next
|
|
if lowest != "" {
|
|
nextProp := streams.NewActivityStreamsNextProperty()
|
|
nextPropIDString := fmt.Sprintf("%s?page=true&max_id=%s", outboxID, lowest)
|
|
nextPropIDURI, err := url.Parse(nextPropIDString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nextProp.SetIRI(nextPropIDURI)
|
|
page.SetActivityStreamsNext(nextProp)
|
|
}
|
|
|
|
// .prev
|
|
if highest != "" {
|
|
prevProp := streams.NewActivityStreamsPrevProperty()
|
|
prevPropIDString := fmt.Sprintf("%s?page=true&min_id=%s", outboxID, highest)
|
|
prevPropIDURI, err := url.Parse(prevPropIDString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
prevProp.SetIRI(prevPropIDURI)
|
|
page.SetActivityStreamsPrev(prevProp)
|
|
}
|
|
|
|
return page, nil
|
|
}
|
|
|
|
/*
|
|
we want something that looks like this:
|
|
|
|
{
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
"id": "https://example.org/users/whatever/outbox",
|
|
"type": "OrderedCollection",
|
|
"first": "https://example.org/users/whatever/outbox?page=true"
|
|
}
|
|
*/
|
|
func (c *converter) OutboxToASCollection(ctx context.Context, outboxID string) (vocab.ActivityStreamsOrderedCollection, error) {
|
|
collection := streams.NewActivityStreamsOrderedCollection()
|
|
|
|
collectionIDProp := streams.NewJSONLDIdProperty()
|
|
outboxIDURI, err := url.Parse(outboxID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing url %s", outboxID)
|
|
}
|
|
collectionIDProp.SetIRI(outboxIDURI)
|
|
collection.SetJSONLDId(collectionIDProp)
|
|
|
|
collectionFirstProp := streams.NewActivityStreamsFirstProperty()
|
|
collectionFirstPropID := fmt.Sprintf("%s?page=true", outboxID)
|
|
collectionFirstPropIDURI, err := url.Parse(collectionFirstPropID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing url %s", collectionFirstPropID)
|
|
}
|
|
collectionFirstProp.SetIRI(collectionFirstPropIDURI)
|
|
collection.SetActivityStreamsFirst(collectionFirstProp)
|
|
|
|
return collection, nil
|
|
}
|