diff --git a/internal/db/gtsmodel/account.go b/internal/db/gtsmodel/account.go index ed06eba1e..8ee05f45b 100644 --- a/internal/db/gtsmodel/account.go +++ b/internal/db/gtsmodel/account.go @@ -93,15 +93,15 @@ type Account struct { // Last time this account was located using the webfinger API. LastWebfingeredAt time.Time `pg:"type:timestamp"` // Address of this account's activitypub inbox, for sending activity to - InboxURL string `pg:",unique"` + InboxURI string `pg:",unique"` // Address of this account's activitypub outbox - OutboxURL string `pg:",unique"` - // Don't support shared inbox right now so this is just a stub for a future implementation - SharedInboxURL string `pg:",unique"` - // URL for getting the followers list of this account - FollowersURL string `pg:",unique"` + OutboxURI string `pg:",unique"` + // URI for getting the following list of this account + FollowingURI string `pg:",unique"` + // URI for getting the followers list of this account + FollowersURI string `pg:",unique"` // URL for getting the featured collection list of this account - FeaturedCollectionURL string `pg:",unique"` + FeaturedCollectionURI string `pg:",unique"` // What type of activitypub actor is this account? ActorType ActivityStreamsActor // This account is associated with x account id diff --git a/internal/db/pg.go b/internal/db/pg.go index a6ae8ce19..33ce26ba7 100644 --- a/internal/db/pg.go +++ b/internal/db/pg.go @@ -468,10 +468,11 @@ func (ps *postgresService) NewSignup(username string, reason string, requireAppr PublicKeyURI: newAccountURIs.PublicKeyURI, ActorType: gtsmodel.ActivityStreamsPerson, URI: newAccountURIs.UserURI, - InboxURL: newAccountURIs.InboxURI, - OutboxURL: newAccountURIs.OutboxURI, - FollowersURL: newAccountURIs.FollowersURI, - FeaturedCollectionURL: newAccountURIs.CollectionURI, + InboxURI: newAccountURIs.InboxURI, + OutboxURI: newAccountURIs.OutboxURI, + FollowersURI: newAccountURIs.FollowersURI, + FollowingURI: newAccountURIs.FollowingURI, + FeaturedCollectionURI: newAccountURIs.CollectionURI, } if _, err = ps.conn.Model(a).Insert(); err != nil { return nil, err diff --git a/internal/federation/protocol_test.go b/internal/federation/protocol_test.go index b30c4c7c1..eeb849acf 100644 --- a/internal/federation/protocol_test.go +++ b/internal/federation/protocol_test.go @@ -144,7 +144,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() { "owner": "%s", "publicKeyPem": "%s" } - }`, sendingAccount.URI, sendingAccount.Username, sendingAccount.InboxURL, sendingAccount.PublicKeyURI, sendingAccount.URI, publicKeyString) + }`, sendingAccount.URI, sendingAccount.Username, sendingAccount.InboxURI, sendingAccount.PublicKeyURI, sendingAccount.URI, publicKeyString) // create a transport controller whose client will just return the response body string we specified above tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) { diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index 6db2cf270..4f19e07b2 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -19,9 +19,7 @@ package typeutils import ( - "fmt" - "time" - + "github.com/go-fed/activity/streams/vocab" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" @@ -84,12 +82,18 @@ type TypeConverter interface { MastoVisToVis(m mastotypes.Visibility) gtsmodel.Visibility /* - ACTIVITYPUB MODEL TO INTERNAL (gts) MODEL + ACTIVITYSTREAMS MODEL TO INTERNAL (gts) MODEL */ /* - INTERNAL (gts) MODEL TO ACTIVITYPUB MODEL + INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL */ + + // AccountToAS converts a gts model account into an activity streams person, suitable for federation + AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) + + // StatusToAS converts a gts model status into an activity streams note, suitable for federation + StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) } type converter struct { @@ -104,465 +108,3 @@ func NewConverter(config *config.Config, db db.DB) TypeConverter { db: db, } } - -func (c *converter) AccountToMastoSensitive(a *gtsmodel.Account) (*mastotypes.Account, error) { - // we can build this sensitive account easily by first getting the public account.... - mastoAccount, err := c.AccountToMastoPublic(a) - if err != nil { - return nil, err - } - - // then adding the Source object to it... - - // check pending follow requests aimed at this account - fr := []gtsmodel.FollowRequest{} - if err := c.db.GetFollowRequestsForAccountID(a.ID, &fr); err != nil { - if _, ok := err.(db.ErrNoEntries); !ok { - return nil, fmt.Errorf("error getting follow requests: %s", err) - } - } - var frc int - if fr != nil { - frc = len(fr) - } - - mastoAccount.Source = &mastotypes.Source{ - Privacy: c.VisToMasto(a.Privacy), - Sensitive: a.Sensitive, - Language: a.Language, - Note: a.Note, - Fields: mastoAccount.Fields, - FollowRequestsCount: frc, - } - - return mastoAccount, nil -} - -func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*mastotypes.Account, error) { - // count followers - followers := []gtsmodel.Follow{} - if err := c.db.GetFollowersByAccountID(a.ID, &followers); err != nil { - if _, ok := err.(db.ErrNoEntries); !ok { - return nil, fmt.Errorf("error getting followers: %s", err) - } - } - var followersCount int - if followers != nil { - followersCount = len(followers) - } - - // count following - following := []gtsmodel.Follow{} - if err := c.db.GetFollowingByAccountID(a.ID, &following); err != nil { - if _, ok := err.(db.ErrNoEntries); !ok { - return nil, fmt.Errorf("error getting following: %s", err) - } - } - var followingCount int - if following != nil { - followingCount = len(following) - } - - // count statuses - statuses := []gtsmodel.Status{} - if err := c.db.GetStatusesByAccountID(a.ID, &statuses); err != nil { - if _, ok := err.(db.ErrNoEntries); !ok { - return nil, fmt.Errorf("error getting last statuses: %s", err) - } - } - var statusesCount int - if statuses != nil { - statusesCount = len(statuses) - } - - // check when the last status was - lastStatus := >smodel.Status{} - if err := c.db.GetLastStatusForAccountID(a.ID, lastStatus); err != nil { - if _, ok := err.(db.ErrNoEntries); !ok { - return nil, fmt.Errorf("error getting last status: %s", err) - } - } - var lastStatusAt string - if lastStatus != nil { - lastStatusAt = lastStatus.CreatedAt.Format(time.RFC3339) - } - - // build the avatar and header URLs - avi := >smodel.MediaAttachment{} - if err := c.db.GetAvatarForAccountID(avi, a.ID); err != nil { - if _, ok := err.(db.ErrNoEntries); !ok { - return nil, fmt.Errorf("error getting avatar: %s", err) - } - } - aviURL := avi.URL - aviURLStatic := avi.Thumbnail.URL - - header := >smodel.MediaAttachment{} - if err := c.db.GetHeaderForAccountID(avi, a.ID); err != nil { - if _, ok := err.(db.ErrNoEntries); !ok { - return nil, fmt.Errorf("error getting header: %s", err) - } - } - headerURL := header.URL - headerURLStatic := header.Thumbnail.URL - - // get the fields set on this account - fields := []mastotypes.Field{} - for _, f := range a.Fields { - mField := mastotypes.Field{ - Name: f.Name, - Value: f.Value, - } - if !f.VerifiedAt.IsZero() { - mField.VerifiedAt = f.VerifiedAt.Format(time.RFC3339) - } - fields = append(fields, mField) - } - - var acct string - if a.Domain != "" { - // this is a remote user - acct = fmt.Sprintf("%s@%s", a.Username, a.Domain) - } else { - // this is a local user - acct = a.Username - } - - return &mastotypes.Account{ - ID: a.ID, - Username: a.Username, - Acct: acct, - DisplayName: a.DisplayName, - Locked: a.Locked, - Bot: a.Bot, - CreatedAt: a.CreatedAt.Format(time.RFC3339), - Note: a.Note, - URL: a.URL, - Avatar: aviURL, - AvatarStatic: aviURLStatic, - Header: headerURL, - HeaderStatic: headerURLStatic, - FollowersCount: followersCount, - FollowingCount: followingCount, - StatusesCount: statusesCount, - LastStatusAt: lastStatusAt, - Emojis: nil, // TODO: implement this - Fields: fields, - }, nil -} - -func (c *converter) AppToMastoSensitive(a *gtsmodel.Application) (*mastotypes.Application, error) { - return &mastotypes.Application{ - ID: a.ID, - Name: a.Name, - Website: a.Website, - RedirectURI: a.RedirectURI, - ClientID: a.ClientID, - ClientSecret: a.ClientSecret, - VapidKey: a.VapidKey, - }, nil -} - -func (c *converter) AppToMastoPublic(a *gtsmodel.Application) (*mastotypes.Application, error) { - return &mastotypes.Application{ - Name: a.Name, - Website: a.Website, - }, nil -} - -func (c *converter) AttachmentToMasto(a *gtsmodel.MediaAttachment) (mastotypes.Attachment, error) { - return mastotypes.Attachment{ - ID: a.ID, - Type: string(a.Type), - URL: a.URL, - PreviewURL: a.Thumbnail.URL, - RemoteURL: a.RemoteURL, - PreviewRemoteURL: a.Thumbnail.RemoteURL, - Meta: mastotypes.MediaMeta{ - Original: mastotypes.MediaDimensions{ - Width: a.FileMeta.Original.Width, - Height: a.FileMeta.Original.Height, - Size: fmt.Sprintf("%dx%d", a.FileMeta.Original.Width, a.FileMeta.Original.Height), - Aspect: float32(a.FileMeta.Original.Aspect), - }, - Small: mastotypes.MediaDimensions{ - Width: a.FileMeta.Small.Width, - Height: a.FileMeta.Small.Height, - Size: fmt.Sprintf("%dx%d", a.FileMeta.Small.Width, a.FileMeta.Small.Height), - Aspect: float32(a.FileMeta.Small.Aspect), - }, - Focus: mastotypes.MediaFocus{ - X: a.FileMeta.Focus.X, - Y: a.FileMeta.Focus.Y, - }, - }, - Description: a.Description, - Blurhash: a.Blurhash, - }, nil -} - -func (c *converter) MentionToMasto(m *gtsmodel.Mention) (mastotypes.Mention, error) { - target := >smodel.Account{} - if err := c.db.GetByID(m.TargetAccountID, target); err != nil { - return mastotypes.Mention{}, err - } - - var local bool - if target.Domain == "" { - local = true - } - - var acct string - if local { - acct = fmt.Sprintf("@%s", target.Username) - } else { - acct = fmt.Sprintf("@%s@%s", target.Username, target.Domain) - } - - return mastotypes.Mention{ - ID: target.ID, - Username: target.Username, - URL: target.URL, - Acct: acct, - }, nil -} - -func (c *converter) EmojiToMasto(e *gtsmodel.Emoji) (mastotypes.Emoji, error) { - return mastotypes.Emoji{ - Shortcode: e.Shortcode, - URL: e.ImageURL, - StaticURL: e.ImageStaticURL, - VisibleInPicker: e.VisibleInPicker, - Category: e.CategoryID, - }, nil -} - -func (c *converter) TagToMasto(t *gtsmodel.Tag) (mastotypes.Tag, error) { - tagURL := fmt.Sprintf("%s://%s/tags/%s", c.config.Protocol, c.config.Host, t.Name) - - return mastotypes.Tag{ - Name: t.Name, - URL: tagURL, // we don't serve URLs with collections of tagged statuses (FOR NOW) so this is purely for mastodon compatibility ¯\_(ツ)_/¯ - }, nil -} - -func (c *converter) StatusToMasto( - s *gtsmodel.Status, - targetAccount *gtsmodel.Account, - requestingAccount *gtsmodel.Account, - boostOfAccount *gtsmodel.Account, - replyToAccount *gtsmodel.Account, - reblogOfStatus *gtsmodel.Status) (*mastotypes.Status, error) { - - repliesCount, err := c.db.GetReplyCountForStatus(s) - if err != nil { - return nil, fmt.Errorf("error counting replies: %s", err) - } - - reblogsCount, err := c.db.GetReblogCountForStatus(s) - if err != nil { - return nil, fmt.Errorf("error counting reblogs: %s", err) - } - - favesCount, err := c.db.GetFaveCountForStatus(s) - if err != nil { - return nil, fmt.Errorf("error counting faves: %s", err) - } - - var faved bool - var reblogged bool - var bookmarked bool - var pinned bool - var muted bool - - // requestingAccount will be nil for public requests without auth - // But if it's not nil, we can also get information about the requestingAccount's interaction with this status - if requestingAccount != nil { - faved, err = c.db.StatusFavedBy(s, requestingAccount.ID) - if err != nil { - return nil, fmt.Errorf("error checking if requesting account has faved status: %s", err) - } - - reblogged, err = c.db.StatusRebloggedBy(s, requestingAccount.ID) - if err != nil { - return nil, fmt.Errorf("error checking if requesting account has reblogged status: %s", err) - } - - muted, err = c.db.StatusMutedBy(s, requestingAccount.ID) - if err != nil { - return nil, fmt.Errorf("error checking if requesting account has muted status: %s", err) - } - - bookmarked, err = c.db.StatusBookmarkedBy(s, requestingAccount.ID) - if err != nil { - return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err) - } - - pinned, err = c.db.StatusPinnedBy(s, requestingAccount.ID) - if err != nil { - return nil, fmt.Errorf("error checking if requesting account has pinned status: %s", err) - } - } - - var mastoRebloggedStatus *mastotypes.Status // TODO - - var mastoApplication *mastotypes.Application - if s.CreatedWithApplicationID != "" { - gtsApplication := >smodel.Application{} - if err := c.db.GetByID(s.CreatedWithApplicationID, gtsApplication); err != nil { - return nil, fmt.Errorf("error fetching application used to create status: %s", err) - } - mastoApplication, err = c.AppToMastoPublic(gtsApplication) - if err != nil { - return nil, fmt.Errorf("error parsing application used to create status: %s", err) - } - } - - mastoTargetAccount, err := c.AccountToMastoPublic(targetAccount) - if err != nil { - return nil, fmt.Errorf("error parsing account of status author: %s", err) - } - - mastoAttachments := []mastotypes.Attachment{} - // the status might already have some gts attachments on it if it's not been pulled directly from the database - // if so, we can directly convert the gts attachments into masto ones - if s.GTSMediaAttachments != nil { - for _, gtsAttachment := range s.GTSMediaAttachments { - mastoAttachment, err := c.AttachmentToMasto(gtsAttachment) - if err != nil { - return nil, fmt.Errorf("error converting attachment with id %s: %s", gtsAttachment.ID, err) - } - mastoAttachments = append(mastoAttachments, mastoAttachment) - } - // the status doesn't have gts attachments on it, but it does have attachment IDs - // in this case, we need to pull the gts attachments from the db to convert them into masto ones - } else { - for _, a := range s.Attachments { - gtsAttachment := >smodel.MediaAttachment{} - if err := c.db.GetByID(a, gtsAttachment); err != nil { - return nil, fmt.Errorf("error getting attachment with id %s: %s", a, err) - } - mastoAttachment, err := c.AttachmentToMasto(gtsAttachment) - if err != nil { - return nil, fmt.Errorf("error converting attachment with id %s: %s", a, err) - } - mastoAttachments = append(mastoAttachments, mastoAttachment) - } - } - - mastoMentions := []mastotypes.Mention{} - // the status might already have some gts mentions on it if it's not been pulled directly from the database - // if so, we can directly convert the gts mentions into masto ones - if s.GTSMentions != nil { - for _, gtsMention := range s.GTSMentions { - mastoMention, err := c.MentionToMasto(gtsMention) - if err != nil { - return nil, fmt.Errorf("error converting mention with id %s: %s", gtsMention.ID, err) - } - mastoMentions = append(mastoMentions, mastoMention) - } - // the status doesn't have gts mentions on it, but it does have mention IDs - // in this case, we need to pull the gts mentions from the db to convert them into masto ones - } else { - for _, m := range s.Mentions { - gtsMention := >smodel.Mention{} - if err := c.db.GetByID(m, gtsMention); err != nil { - return nil, fmt.Errorf("error getting mention with id %s: %s", m, err) - } - mastoMention, err := c.MentionToMasto(gtsMention) - if err != nil { - return nil, fmt.Errorf("error converting mention with id %s: %s", gtsMention.ID, err) - } - mastoMentions = append(mastoMentions, mastoMention) - } - } - - mastoTags := []mastotypes.Tag{} - // the status might already have some gts tags on it if it's not been pulled directly from the database - // if so, we can directly convert the gts tags into masto ones - if s.GTSTags != nil { - for _, gtsTag := range s.GTSTags { - mastoTag, err := c.TagToMasto(gtsTag) - if err != nil { - return nil, fmt.Errorf("error converting tag with id %s: %s", gtsTag.ID, err) - } - mastoTags = append(mastoTags, mastoTag) - } - // the status doesn't have gts tags on it, but it does have tag IDs - // in this case, we need to pull the gts tags from the db to convert them into masto ones - } else { - for _, t := range s.Tags { - gtsTag := >smodel.Tag{} - if err := c.db.GetByID(t, gtsTag); err != nil { - return nil, fmt.Errorf("error getting tag with id %s: %s", t, err) - } - mastoTag, err := c.TagToMasto(gtsTag) - if err != nil { - return nil, fmt.Errorf("error converting tag with id %s: %s", gtsTag.ID, err) - } - mastoTags = append(mastoTags, mastoTag) - } - } - - mastoEmojis := []mastotypes.Emoji{} - // the status might already have some gts emojis on it if it's not been pulled directly from the database - // if so, we can directly convert the gts emojis into masto ones - if s.GTSEmojis != nil { - for _, gtsEmoji := range s.GTSEmojis { - mastoEmoji, err := c.EmojiToMasto(gtsEmoji) - if err != nil { - return nil, fmt.Errorf("error converting emoji with id %s: %s", gtsEmoji.ID, err) - } - mastoEmojis = append(mastoEmojis, mastoEmoji) - } - // the status doesn't have gts emojis on it, but it does have emoji IDs - // in this case, we need to pull the gts emojis from the db to convert them into masto ones - } else { - for _, e := range s.Emojis { - gtsEmoji := >smodel.Emoji{} - if err := c.db.GetByID(e, gtsEmoji); err != nil { - return nil, fmt.Errorf("error getting emoji with id %s: %s", e, err) - } - mastoEmoji, err := c.EmojiToMasto(gtsEmoji) - if err != nil { - return nil, fmt.Errorf("error converting emoji with id %s: %s", gtsEmoji.ID, err) - } - mastoEmojis = append(mastoEmojis, mastoEmoji) - } - } - - var mastoCard *mastotypes.Card - var mastoPoll *mastotypes.Poll - - return &mastotypes.Status{ - ID: s.ID, - CreatedAt: s.CreatedAt.Format(time.RFC3339), - InReplyToID: s.InReplyToID, - InReplyToAccountID: s.InReplyToAccountID, - Sensitive: s.Sensitive, - SpoilerText: s.ContentWarning, - Visibility: c.VisToMasto(s.Visibility), - Language: s.Language, - URI: s.URI, - URL: s.URL, - RepliesCount: repliesCount, - ReblogsCount: reblogsCount, - FavouritesCount: favesCount, - Favourited: faved, - Reblogged: reblogged, - Muted: muted, - Bookmarked: bookmarked, - Pinned: pinned, - Content: s.Content, - Reblog: mastoRebloggedStatus, - Application: mastoApplication, - Account: mastoTargetAccount, - MediaAttachments: mastoAttachments, - Mentions: mastoMentions, - Tags: mastoTags, - Emojis: mastoEmojis, - Card: mastoCard, // TODO: implement cards - Poll: mastoPoll, // TODO: implement polls - Text: s.Text, - }, nil -} diff --git a/internal/typeutils/visibility.go b/internal/typeutils/frontendtointernal.go similarity index 73% rename from internal/typeutils/visibility.go rename to internal/typeutils/frontendtointernal.go index 71be92f5e..4ac6a74cf 100644 --- a/internal/typeutils/visibility.go +++ b/internal/typeutils/frontendtointernal.go @@ -37,18 +37,3 @@ func (c *converter) MastoVisToVis(m mastotypes.Visibility) gtsmodel.Visibility { } return "" } - -// VisToMasto converts a gts visibility into its mastodon equivalent -func (c *converter) VisToMasto(m gtsmodel.Visibility) mastotypes.Visibility { - switch m { - case gtsmodel.VisibilityPublic: - return mastotypes.VisibilityPublic - case gtsmodel.VisibilityUnlocked: - return mastotypes.VisibilityUnlisted - case gtsmodel.VisibilityFollowersOnly, gtsmodel.VisibilityMutualsOnly: - return mastotypes.VisibilityPrivate - case gtsmodel.VisibilityDirect: - return mastotypes.VisibilityDirect - } - return "" -} diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go new file mode 100644 index 000000000..7db337c4d --- /dev/null +++ b/internal/typeutils/internaltoas.go @@ -0,0 +1,210 @@ +/* + 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 . +*/ + +package typeutils + +import ( + "crypto/x509" + "encoding/pem" + "net/url" + "strings" + + "github.com/go-fed/activity/streams" + "github.com/go-fed/activity/streams/vocab" + "github.com/superseriousbusiness/gotosocial/internal/db/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/ +func (c *converter) AccountToAS(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) + + // manuallyApproveFollowers + // Will be shown as a locked account. + // 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 + + // discoverable + // Will be shown in the profile directory. + discoverableProp := streams.NewTootDiscoverableProperty() + discoverableProp.Set(a.Discoverable) + person.SetTootDiscoverable(discoverableProp) + + // devices + // NOT IMPLEMENTED + + // 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, + }) + publicKeyString := strings.ReplaceAll(string(publicKeyBytes), "\n", "\\n") // replace all the newlines with backslash n + publicKeyPEMProp := streams.NewW3IDSecurityV1PublicKeyPemProperty() + publicKeyPEMProp.Set(publicKeyString) + + // append the public key to the public key property + publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey) + + // set the public key property on the Person + person.SetW3IDSecurityV1PublicKey(publicKeyProp) + + // tag + // Any tags used in the summary of this profile + + // attachment + // Used for profile fields. + + // endpoints + // NOT IMPLEMENTED -- this is for shared inbox which we don't use + + // icon + // Used as profile avatar. + + // image + // Used as profile header. + + return person, nil +} + +func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) { + return nil, nil +} diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go new file mode 100644 index 000000000..e20e6936c --- /dev/null +++ b/internal/typeutils/internaltoas_test.go @@ -0,0 +1,19 @@ +/* + 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 . +*/ + +package typeutils_test diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go new file mode 100644 index 000000000..60e7fa3fa --- /dev/null +++ b/internal/typeutils/internaltofrontend.go @@ -0,0 +1,505 @@ +/* + 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 . +*/ + +package typeutils + +import ( + "fmt" + "time" + + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/mastotypes" +) + +func (c *converter) AccountToMastoSensitive(a *gtsmodel.Account) (*mastotypes.Account, error) { + // we can build this sensitive account easily by first getting the public account.... + mastoAccount, err := c.AccountToMastoPublic(a) + if err != nil { + return nil, err + } + + // then adding the Source object to it... + + // check pending follow requests aimed at this account + fr := []gtsmodel.FollowRequest{} + if err := c.db.GetFollowRequestsForAccountID(a.ID, &fr); err != nil { + if _, ok := err.(db.ErrNoEntries); !ok { + return nil, fmt.Errorf("error getting follow requests: %s", err) + } + } + var frc int + if fr != nil { + frc = len(fr) + } + + mastoAccount.Source = &mastotypes.Source{ + Privacy: c.VisToMasto(a.Privacy), + Sensitive: a.Sensitive, + Language: a.Language, + Note: a.Note, + Fields: mastoAccount.Fields, + FollowRequestsCount: frc, + } + + return mastoAccount, nil +} + +func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*mastotypes.Account, error) { + // count followers + followers := []gtsmodel.Follow{} + if err := c.db.GetFollowersByAccountID(a.ID, &followers); err != nil { + if _, ok := err.(db.ErrNoEntries); !ok { + return nil, fmt.Errorf("error getting followers: %s", err) + } + } + var followersCount int + if followers != nil { + followersCount = len(followers) + } + + // count following + following := []gtsmodel.Follow{} + if err := c.db.GetFollowingByAccountID(a.ID, &following); err != nil { + if _, ok := err.(db.ErrNoEntries); !ok { + return nil, fmt.Errorf("error getting following: %s", err) + } + } + var followingCount int + if following != nil { + followingCount = len(following) + } + + // count statuses + statuses := []gtsmodel.Status{} + if err := c.db.GetStatusesByAccountID(a.ID, &statuses); err != nil { + if _, ok := err.(db.ErrNoEntries); !ok { + return nil, fmt.Errorf("error getting last statuses: %s", err) + } + } + var statusesCount int + if statuses != nil { + statusesCount = len(statuses) + } + + // check when the last status was + lastStatus := >smodel.Status{} + if err := c.db.GetLastStatusForAccountID(a.ID, lastStatus); err != nil { + if _, ok := err.(db.ErrNoEntries); !ok { + return nil, fmt.Errorf("error getting last status: %s", err) + } + } + var lastStatusAt string + if lastStatus != nil { + lastStatusAt = lastStatus.CreatedAt.Format(time.RFC3339) + } + + // build the avatar and header URLs + avi := >smodel.MediaAttachment{} + if err := c.db.GetAvatarForAccountID(avi, a.ID); err != nil { + if _, ok := err.(db.ErrNoEntries); !ok { + return nil, fmt.Errorf("error getting avatar: %s", err) + } + } + aviURL := avi.URL + aviURLStatic := avi.Thumbnail.URL + + header := >smodel.MediaAttachment{} + if err := c.db.GetHeaderForAccountID(avi, a.ID); err != nil { + if _, ok := err.(db.ErrNoEntries); !ok { + return nil, fmt.Errorf("error getting header: %s", err) + } + } + headerURL := header.URL + headerURLStatic := header.Thumbnail.URL + + // get the fields set on this account + fields := []mastotypes.Field{} + for _, f := range a.Fields { + mField := mastotypes.Field{ + Name: f.Name, + Value: f.Value, + } + if !f.VerifiedAt.IsZero() { + mField.VerifiedAt = f.VerifiedAt.Format(time.RFC3339) + } + fields = append(fields, mField) + } + + var acct string + if a.Domain != "" { + // this is a remote user + acct = fmt.Sprintf("%s@%s", a.Username, a.Domain) + } else { + // this is a local user + acct = a.Username + } + + return &mastotypes.Account{ + ID: a.ID, + Username: a.Username, + Acct: acct, + DisplayName: a.DisplayName, + Locked: a.Locked, + Bot: a.Bot, + CreatedAt: a.CreatedAt.Format(time.RFC3339), + Note: a.Note, + URL: a.URL, + Avatar: aviURL, + AvatarStatic: aviURLStatic, + Header: headerURL, + HeaderStatic: headerURLStatic, + FollowersCount: followersCount, + FollowingCount: followingCount, + StatusesCount: statusesCount, + LastStatusAt: lastStatusAt, + Emojis: nil, // TODO: implement this + Fields: fields, + }, nil +} + +func (c *converter) AppToMastoSensitive(a *gtsmodel.Application) (*mastotypes.Application, error) { + return &mastotypes.Application{ + ID: a.ID, + Name: a.Name, + Website: a.Website, + RedirectURI: a.RedirectURI, + ClientID: a.ClientID, + ClientSecret: a.ClientSecret, + VapidKey: a.VapidKey, + }, nil +} + +func (c *converter) AppToMastoPublic(a *gtsmodel.Application) (*mastotypes.Application, error) { + return &mastotypes.Application{ + Name: a.Name, + Website: a.Website, + }, nil +} + +func (c *converter) AttachmentToMasto(a *gtsmodel.MediaAttachment) (mastotypes.Attachment, error) { + return mastotypes.Attachment{ + ID: a.ID, + Type: string(a.Type), + URL: a.URL, + PreviewURL: a.Thumbnail.URL, + RemoteURL: a.RemoteURL, + PreviewRemoteURL: a.Thumbnail.RemoteURL, + Meta: mastotypes.MediaMeta{ + Original: mastotypes.MediaDimensions{ + Width: a.FileMeta.Original.Width, + Height: a.FileMeta.Original.Height, + Size: fmt.Sprintf("%dx%d", a.FileMeta.Original.Width, a.FileMeta.Original.Height), + Aspect: float32(a.FileMeta.Original.Aspect), + }, + Small: mastotypes.MediaDimensions{ + Width: a.FileMeta.Small.Width, + Height: a.FileMeta.Small.Height, + Size: fmt.Sprintf("%dx%d", a.FileMeta.Small.Width, a.FileMeta.Small.Height), + Aspect: float32(a.FileMeta.Small.Aspect), + }, + Focus: mastotypes.MediaFocus{ + X: a.FileMeta.Focus.X, + Y: a.FileMeta.Focus.Y, + }, + }, + Description: a.Description, + Blurhash: a.Blurhash, + }, nil +} + +func (c *converter) MentionToMasto(m *gtsmodel.Mention) (mastotypes.Mention, error) { + target := >smodel.Account{} + if err := c.db.GetByID(m.TargetAccountID, target); err != nil { + return mastotypes.Mention{}, err + } + + var local bool + if target.Domain == "" { + local = true + } + + var acct string + if local { + acct = fmt.Sprintf("@%s", target.Username) + } else { + acct = fmt.Sprintf("@%s@%s", target.Username, target.Domain) + } + + return mastotypes.Mention{ + ID: target.ID, + Username: target.Username, + URL: target.URL, + Acct: acct, + }, nil +} + +func (c *converter) EmojiToMasto(e *gtsmodel.Emoji) (mastotypes.Emoji, error) { + return mastotypes.Emoji{ + Shortcode: e.Shortcode, + URL: e.ImageURL, + StaticURL: e.ImageStaticURL, + VisibleInPicker: e.VisibleInPicker, + Category: e.CategoryID, + }, nil +} + +func (c *converter) TagToMasto(t *gtsmodel.Tag) (mastotypes.Tag, error) { + tagURL := fmt.Sprintf("%s://%s/tags/%s", c.config.Protocol, c.config.Host, t.Name) + + return mastotypes.Tag{ + Name: t.Name, + URL: tagURL, // we don't serve URLs with collections of tagged statuses (FOR NOW) so this is purely for mastodon compatibility ¯\_(ツ)_/¯ + }, nil +} + +func (c *converter) StatusToMasto( + s *gtsmodel.Status, + targetAccount *gtsmodel.Account, + requestingAccount *gtsmodel.Account, + boostOfAccount *gtsmodel.Account, + replyToAccount *gtsmodel.Account, + reblogOfStatus *gtsmodel.Status) (*mastotypes.Status, error) { + + repliesCount, err := c.db.GetReplyCountForStatus(s) + if err != nil { + return nil, fmt.Errorf("error counting replies: %s", err) + } + + reblogsCount, err := c.db.GetReblogCountForStatus(s) + if err != nil { + return nil, fmt.Errorf("error counting reblogs: %s", err) + } + + favesCount, err := c.db.GetFaveCountForStatus(s) + if err != nil { + return nil, fmt.Errorf("error counting faves: %s", err) + } + + var faved bool + var reblogged bool + var bookmarked bool + var pinned bool + var muted bool + + // requestingAccount will be nil for public requests without auth + // But if it's not nil, we can also get information about the requestingAccount's interaction with this status + if requestingAccount != nil { + faved, err = c.db.StatusFavedBy(s, requestingAccount.ID) + if err != nil { + return nil, fmt.Errorf("error checking if requesting account has faved status: %s", err) + } + + reblogged, err = c.db.StatusRebloggedBy(s, requestingAccount.ID) + if err != nil { + return nil, fmt.Errorf("error checking if requesting account has reblogged status: %s", err) + } + + muted, err = c.db.StatusMutedBy(s, requestingAccount.ID) + if err != nil { + return nil, fmt.Errorf("error checking if requesting account has muted status: %s", err) + } + + bookmarked, err = c.db.StatusBookmarkedBy(s, requestingAccount.ID) + if err != nil { + return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err) + } + + pinned, err = c.db.StatusPinnedBy(s, requestingAccount.ID) + if err != nil { + return nil, fmt.Errorf("error checking if requesting account has pinned status: %s", err) + } + } + + var mastoRebloggedStatus *mastotypes.Status // TODO + + var mastoApplication *mastotypes.Application + if s.CreatedWithApplicationID != "" { + gtsApplication := >smodel.Application{} + if err := c.db.GetByID(s.CreatedWithApplicationID, gtsApplication); err != nil { + return nil, fmt.Errorf("error fetching application used to create status: %s", err) + } + mastoApplication, err = c.AppToMastoPublic(gtsApplication) + if err != nil { + return nil, fmt.Errorf("error parsing application used to create status: %s", err) + } + } + + mastoTargetAccount, err := c.AccountToMastoPublic(targetAccount) + if err != nil { + return nil, fmt.Errorf("error parsing account of status author: %s", err) + } + + mastoAttachments := []mastotypes.Attachment{} + // the status might already have some gts attachments on it if it's not been pulled directly from the database + // if so, we can directly convert the gts attachments into masto ones + if s.GTSMediaAttachments != nil { + for _, gtsAttachment := range s.GTSMediaAttachments { + mastoAttachment, err := c.AttachmentToMasto(gtsAttachment) + if err != nil { + return nil, fmt.Errorf("error converting attachment with id %s: %s", gtsAttachment.ID, err) + } + mastoAttachments = append(mastoAttachments, mastoAttachment) + } + // the status doesn't have gts attachments on it, but it does have attachment IDs + // in this case, we need to pull the gts attachments from the db to convert them into masto ones + } else { + for _, a := range s.Attachments { + gtsAttachment := >smodel.MediaAttachment{} + if err := c.db.GetByID(a, gtsAttachment); err != nil { + return nil, fmt.Errorf("error getting attachment with id %s: %s", a, err) + } + mastoAttachment, err := c.AttachmentToMasto(gtsAttachment) + if err != nil { + return nil, fmt.Errorf("error converting attachment with id %s: %s", a, err) + } + mastoAttachments = append(mastoAttachments, mastoAttachment) + } + } + + mastoMentions := []mastotypes.Mention{} + // the status might already have some gts mentions on it if it's not been pulled directly from the database + // if so, we can directly convert the gts mentions into masto ones + if s.GTSMentions != nil { + for _, gtsMention := range s.GTSMentions { + mastoMention, err := c.MentionToMasto(gtsMention) + if err != nil { + return nil, fmt.Errorf("error converting mention with id %s: %s", gtsMention.ID, err) + } + mastoMentions = append(mastoMentions, mastoMention) + } + // the status doesn't have gts mentions on it, but it does have mention IDs + // in this case, we need to pull the gts mentions from the db to convert them into masto ones + } else { + for _, m := range s.Mentions { + gtsMention := >smodel.Mention{} + if err := c.db.GetByID(m, gtsMention); err != nil { + return nil, fmt.Errorf("error getting mention with id %s: %s", m, err) + } + mastoMention, err := c.MentionToMasto(gtsMention) + if err != nil { + return nil, fmt.Errorf("error converting mention with id %s: %s", gtsMention.ID, err) + } + mastoMentions = append(mastoMentions, mastoMention) + } + } + + mastoTags := []mastotypes.Tag{} + // the status might already have some gts tags on it if it's not been pulled directly from the database + // if so, we can directly convert the gts tags into masto ones + if s.GTSTags != nil { + for _, gtsTag := range s.GTSTags { + mastoTag, err := c.TagToMasto(gtsTag) + if err != nil { + return nil, fmt.Errorf("error converting tag with id %s: %s", gtsTag.ID, err) + } + mastoTags = append(mastoTags, mastoTag) + } + // the status doesn't have gts tags on it, but it does have tag IDs + // in this case, we need to pull the gts tags from the db to convert them into masto ones + } else { + for _, t := range s.Tags { + gtsTag := >smodel.Tag{} + if err := c.db.GetByID(t, gtsTag); err != nil { + return nil, fmt.Errorf("error getting tag with id %s: %s", t, err) + } + mastoTag, err := c.TagToMasto(gtsTag) + if err != nil { + return nil, fmt.Errorf("error converting tag with id %s: %s", gtsTag.ID, err) + } + mastoTags = append(mastoTags, mastoTag) + } + } + + mastoEmojis := []mastotypes.Emoji{} + // the status might already have some gts emojis on it if it's not been pulled directly from the database + // if so, we can directly convert the gts emojis into masto ones + if s.GTSEmojis != nil { + for _, gtsEmoji := range s.GTSEmojis { + mastoEmoji, err := c.EmojiToMasto(gtsEmoji) + if err != nil { + return nil, fmt.Errorf("error converting emoji with id %s: %s", gtsEmoji.ID, err) + } + mastoEmojis = append(mastoEmojis, mastoEmoji) + } + // the status doesn't have gts emojis on it, but it does have emoji IDs + // in this case, we need to pull the gts emojis from the db to convert them into masto ones + } else { + for _, e := range s.Emojis { + gtsEmoji := >smodel.Emoji{} + if err := c.db.GetByID(e, gtsEmoji); err != nil { + return nil, fmt.Errorf("error getting emoji with id %s: %s", e, err) + } + mastoEmoji, err := c.EmojiToMasto(gtsEmoji) + if err != nil { + return nil, fmt.Errorf("error converting emoji with id %s: %s", gtsEmoji.ID, err) + } + mastoEmojis = append(mastoEmojis, mastoEmoji) + } + } + + var mastoCard *mastotypes.Card + var mastoPoll *mastotypes.Poll + + return &mastotypes.Status{ + ID: s.ID, + CreatedAt: s.CreatedAt.Format(time.RFC3339), + InReplyToID: s.InReplyToID, + InReplyToAccountID: s.InReplyToAccountID, + Sensitive: s.Sensitive, + SpoilerText: s.ContentWarning, + Visibility: c.VisToMasto(s.Visibility), + Language: s.Language, + URI: s.URI, + URL: s.URL, + RepliesCount: repliesCount, + ReblogsCount: reblogsCount, + FavouritesCount: favesCount, + Favourited: faved, + Reblogged: reblogged, + Muted: muted, + Bookmarked: bookmarked, + Pinned: pinned, + Content: s.Content, + Reblog: mastoRebloggedStatus, + Application: mastoApplication, + Account: mastoTargetAccount, + MediaAttachments: mastoAttachments, + Mentions: mastoMentions, + Tags: mastoTags, + Emojis: mastoEmojis, + Card: mastoCard, // TODO: implement cards + Poll: mastoPoll, // TODO: implement polls + Text: s.Text, + }, nil +} + +// VisToMasto converts a gts visibility into its mastodon equivalent +func (c *converter) VisToMasto(m gtsmodel.Visibility) mastotypes.Visibility { + switch m { + case gtsmodel.VisibilityPublic: + return mastotypes.VisibilityPublic + case gtsmodel.VisibilityUnlocked: + return mastotypes.VisibilityUnlisted + case gtsmodel.VisibilityFollowersOnly, gtsmodel.VisibilityMutualsOnly: + return mastotypes.VisibilityPrivate + case gtsmodel.VisibilityDirect: + return mastotypes.VisibilityDirect + } + return "" +} diff --git a/testrig/testmodels.go b/testrig/testmodels.go index 8a9843b46..ef629cdb3 100644 --- a/testrig/testmodels.go +++ b/testrig/testmodels.go @@ -284,16 +284,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account { URI: "http://localhost:8080/users/weed_lord420", URL: "http://localhost:8080/@weed_lord420", LastWebfingeredAt: time.Time{}, - InboxURL: "http://localhost:8080/users/weed_lord420/inbox", - OutboxURL: "http://localhost:8080/users/weed_lord420/outbox", - SharedInboxURL: "", - FollowersURL: "http://localhost:8080/users/weed_lord420/followers", - FeaturedCollectionURL: "http://localhost:8080/users/weed_lord420/collections/featured", + InboxURI: "http://localhost:8080/users/weed_lord420/inbox", + OutboxURI: "http://localhost:8080/users/weed_lord420/outbox", + FollowersURI: "http://localhost:8080/users/weed_lord420/followers", + FollowingURI: "http://localhost:8080/users/weed_lord420/following", + FeaturedCollectionURI: "http://localhost:8080/users/weed_lord420/collections/featured", ActorType: gtsmodel.ActivityStreamsPerson, AlsoKnownAs: "", PrivateKey: &rsa.PrivateKey{}, PublicKey: &rsa.PublicKey{}, - PublicKeyURI: "http://localhost:8080/users/weed_lord420/publickey", + PublicKeyURI: "http://localhost:8080/users/weed_lord420#main-key", SensitizedAt: time.Time{}, SilencedAt: time.Time{}, SuspendedAt: time.Time{}, @@ -321,13 +321,13 @@ func NewTestAccounts() map[string]*gtsmodel.Account { Language: "en", URI: "http://localhost:8080/users/admin", URL: "http://localhost:8080/@admin", - PublicKeyURI: "http://localhost:8080/users/admin/publickey", + PublicKeyURI: "http://localhost:8080/users/admin#main-key", LastWebfingeredAt: time.Time{}, - InboxURL: "http://localhost:8080/users/admin/inbox", - OutboxURL: "http://localhost:8080/users/admin/outbox", - SharedInboxURL: "", - FollowersURL: "http://localhost:8080/users/admin/followers", - FeaturedCollectionURL: "http://localhost:8080/users/admin/collections/featured", + InboxURI: "http://localhost:8080/users/admin/inbox", + OutboxURI: "http://localhost:8080/users/admin/outbox", + FollowersURI: "http://localhost:8080/users/admin/followers", + FollowingURI: "http://localhost:8080/users/admin/following", + FeaturedCollectionURI: "http://localhost:8080/users/admin/collections/featured", ActorType: gtsmodel.ActivityStreamsPerson, AlsoKnownAs: "", PrivateKey: &rsa.PrivateKey{}, @@ -360,16 +360,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account { URI: "http://localhost:8080/users/the_mighty_zork", URL: "http://localhost:8080/@the_mighty_zork", LastWebfingeredAt: time.Time{}, - InboxURL: "http://localhost:8080/users/the_mighty_zork/inbox", - OutboxURL: "http://localhost:8080/users/the_mighty_zork/outbox", - SharedInboxURL: "", - FollowersURL: "http://localhost:8080/users/the_mighty_zork/followers", - FeaturedCollectionURL: "http://localhost:8080/users/the_mighty_zork/collections/featured", + InboxURI: "http://localhost:8080/users/the_mighty_zork/inbox", + OutboxURI: "http://localhost:8080/users/the_mighty_zork/outbox", + FollowersURI: "http://localhost:8080/users/the_mighty_zork/followers", + FollowingURI: "http://localhost:8080/users/the_mighty_zork/following", + FeaturedCollectionURI: "http://localhost:8080/users/the_mighty_zork/collections/featured", ActorType: gtsmodel.ActivityStreamsPerson, AlsoKnownAs: "", PrivateKey: &rsa.PrivateKey{}, PublicKey: &rsa.PublicKey{}, - PublicKeyURI: "http://localhost:8080/users/the_mighty_zork/publickey", + PublicKeyURI: "http://localhost:8080/users/the_mighty_zork#main-key", SensitizedAt: time.Time{}, SilencedAt: time.Time{}, SuspendedAt: time.Time{}, @@ -398,16 +398,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account { URI: "http://localhost:8080/users/1happyturtle", URL: "http://localhost:8080/@1happyturtle", LastWebfingeredAt: time.Time{}, - InboxURL: "http://localhost:8080/users/1happyturtle/inbox", - OutboxURL: "http://localhost:8080/users/1happyturtle/outbox", - SharedInboxURL: "", - FollowersURL: "http://localhost:8080/users/1happyturtle/followers", - FeaturedCollectionURL: "http://localhost:8080/users/1happyturtle/collections/featured", + InboxURI: "http://localhost:8080/users/1happyturtle/inbox", + OutboxURI: "http://localhost:8080/users/1happyturtle/outbox", + FollowersURI: "http://localhost:8080/users/1happyturtle/followers", + FollowingURI: "http://localhost:8080/users/1happyturtle/following", + FeaturedCollectionURI: "http://localhost:8080/users/1happyturtle/collections/featured", ActorType: gtsmodel.ActivityStreamsPerson, AlsoKnownAs: "", PrivateKey: &rsa.PrivateKey{}, PublicKey: &rsa.PublicKey{}, - PublicKeyURI: "http://localhost:8080/users/1happyturtle/publickey", + PublicKeyURI: "http://localhost:8080/users/1happyturtle#main-key", SensitizedAt: time.Time{}, SilencedAt: time.Time{}, SuspendedAt: time.Time{}, @@ -443,16 +443,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account { URI: "https://fossbros-anonymous.io/users/foss_satan", URL: "https://fossbros-anonymous.io/@foss_satan", LastWebfingeredAt: time.Time{}, - InboxURL: "https://fossbros-anonymous.io/users/foss_satan/inbox", - OutboxURL: "https://fossbros-anonymous.io/users/foss_satan/outbox", - SharedInboxURL: "", - FollowersURL: "https://fossbros-anonymous.io/users/foss_satan/followers", - FeaturedCollectionURL: "https://fossbros-anonymous.io/users/foss_satan/collections/featured", + InboxURI: "https://fossbros-anonymous.io/users/foss_satan/inbox", + OutboxURI: "https://fossbros-anonymous.io/users/foss_satan/outbox", + FollowersURI: "https://fossbros-anonymous.io/users/foss_satan/followers", + FollowingURI: "https://fossbros-anonymous.io/users/foss_satan/following", + FeaturedCollectionURI: "https://fossbros-anonymous.io/users/foss_satan/collections/featured", ActorType: gtsmodel.ActivityStreamsPerson, AlsoKnownAs: "", PrivateKey: nil, PublicKey: &rsa.PublicKey{}, - PublicKeyURI: "http://fossbros-anonymous.io/users/foss_satan#publickey", + PublicKeyURI: "http://fossbros-anonymous.io/users/foss_satan#main-key", SensitizedAt: time.Time{}, SilencedAt: time.Time{}, SuspendedAt: time.Time{}, @@ -1034,7 +1034,7 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit URLMustParse("https://fossbros-anonymous.io/users/foss_satan"), time.Now(), dmForZork) - sig, digest, date := getSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURL)) + sig, digest, date := getSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI)) return map[string]ActivityWithSignature{ "dm_for_zork": { @@ -1050,6 +1050,8 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit // the HTTP Signature for the given activity, public key ID, private key, and destination. func getSignatureForActivity(activity pub.Activity, pubKeyID string, privkey crypto.PrivateKey, destination *url.URL) (signatureHeader string, digestHeader string, dateHeader string) { + streams.NewActivityStreamsPerson() + // create a client that basically just pulls the signature out of the request and sets it client := &mockHTTPClient{ do: func(req *http.Request) (*http.Response, error) {