diff options
Diffstat (limited to 'internal/typeutils')
-rw-r--r-- | internal/typeutils/asinterfaces.go | 9 | ||||
-rw-r--r-- | internal/typeutils/astointernal.go | 44 | ||||
-rw-r--r-- | internal/typeutils/converter.go | 31 | ||||
-rw-r--r-- | internal/typeutils/internal.go | 76 | ||||
-rw-r--r-- | internal/typeutils/internaltoas.go | 12 | ||||
-rw-r--r-- | internal/typeutils/internaltofrontend.go | 76 |
6 files changed, 220 insertions, 28 deletions
diff --git a/internal/typeutils/asinterfaces.go b/internal/typeutils/asinterfaces.go index c31a37a25..eea7fd7d9 100644 --- a/internal/typeutils/asinterfaces.go +++ b/internal/typeutils/asinterfaces.go @@ -102,6 +102,15 @@ type Followable interface { withObject } +// Likeable represents the minimum interface for an activitystreams 'like' activity. +type Likeable interface { + withJSONLDId + withTypeName + + withActor + withObject +} + type withJSONLDId interface { GetJSONLDId() vocab.JSONLDIdProperty } diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index dcc2674cd..0fee13b13 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -226,7 +226,7 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e return nil, fmt.Errorf("couldn't get status owner from db: %s", err) } status.AccountID = statusOwner.ID - status.GTSAccount = statusOwner + status.GTSAuthorAccount = statusOwner // check if there's a post that this is a reply to inReplyToURI, err := extractInReplyToURI(statusable) @@ -380,6 +380,48 @@ func (c *converter) ASFollowToFollow(followable Followable) (*gtsmodel.Follow, e return follow, nil } +func (c *converter) ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error) { + idProp := likeable.GetJSONLDId() + if idProp == nil || !idProp.IsIRI() { + return nil, errors.New("no id property set on like, or was not an iri") + } + uri := idProp.GetIRI().String() + + origin, err := extractActor(likeable) + if err != nil { + return nil, errors.New("error extracting actor property from like") + } + originAccount := >smodel.Account{} + if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: origin.String()}}, originAccount); err != nil { + return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) + } + + target, err := extractObject(likeable) + if err != nil { + return nil, errors.New("error extracting object property from like") + } + + targetStatus := >smodel.Status{} + if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: target.String()}}, targetStatus); err != nil { + return nil, fmt.Errorf("error extracting status with uri %s from the database: %s", target.String(), err) + } + + targetAccount := >smodel.Account{} + if err := c.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { + return nil, fmt.Errorf("error extracting account with id %s from the database: %s", targetStatus.AccountID, err) + } + + return >smodel.StatusFave{ + TargetAccountID: targetAccount.ID, + StatusID: targetStatus.ID, + AccountID: originAccount.ID, + URI: uri, + GTSStatus: targetStatus, + GTSTargetAccount: targetAccount, + GTSFavingAccount: originAccount, + }, nil +} + func isPublic(tos []*url.URL) bool { for _, entry := range tos { if strings.EqualFold(entry.String(), "https://www.w3.org/ns/activitystreams#Public") { diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index 3ced20926..cf94faf2e 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -44,45 +44,36 @@ type TypeConverter interface { // if something goes wrong. The returned account should be ready to serialize on an API level, and may have sensitive fields, // so serve it only to an authorized user who should have permission to see it. AccountToMastoSensitive(account *gtsmodel.Account) (*model.Account, error) - // AccountToMastoPublic takes a db model account as a param, and returns a populated mastotype account, or an error // if something goes wrong. The returned account should be ready to serialize on an API level, and may NOT have sensitive fields. // In other words, this is the public record that the server has of an account. AccountToMastoPublic(account *gtsmodel.Account) (*model.Account, error) - // AppToMastoSensitive takes a db model application as a param, and returns a populated mastotype application, or an error // if something goes wrong. The returned application should be ready to serialize on an API level, and may have sensitive fields // (such as client id and client secret), so serve it only to an authorized user who should have permission to see it. AppToMastoSensitive(application *gtsmodel.Application) (*model.Application, error) - // AppToMastoPublic takes a db model application as a param, and returns a populated mastotype application, or an error // if something goes wrong. The returned application should be ready to serialize on an API level, and has sensitive // fields sanitized so that it can be served to non-authorized accounts without revealing any private information. AppToMastoPublic(application *gtsmodel.Application) (*model.Application, error) - // AttachmentToMasto converts a gts model media attacahment into its mastodon representation for serialization on the API. AttachmentToMasto(attachment *gtsmodel.MediaAttachment) (model.Attachment, error) - // MentionToMasto converts a gts model mention into its mastodon (frontend) representation for serialization on the API. MentionToMasto(m *gtsmodel.Mention) (model.Mention, error) - // EmojiToMasto converts a gts model emoji into its mastodon (frontend) representation for serialization on the API. EmojiToMasto(e *gtsmodel.Emoji) (model.Emoji, error) - // TagToMasto converts a gts model tag into its mastodon (frontend) representation for serialization on the API. TagToMasto(t *gtsmodel.Tag) (model.Tag, error) - // StatusToMasto converts a gts model status into its mastodon (frontend) representation for serialization on the API. - StatusToMasto(s *gtsmodel.Status, targetAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, boostOfAccount *gtsmodel.Account, replyToAccount *gtsmodel.Account, reblogOfStatus *gtsmodel.Status) (*model.Status, error) - + StatusToMasto(s *gtsmodel.Status, statusAuthor *gtsmodel.Account, requestingAccount *gtsmodel.Account, boostOfAccount *gtsmodel.Account, replyToAccount *gtsmodel.Account, reblogOfStatus *gtsmodel.Status) (*model.Status, error) // VisToMasto converts a gts visibility into its mastodon equivalent VisToMasto(m gtsmodel.Visibility) model.Visibility - // InstanceToMasto converts a gts instance into its mastodon equivalent for serving at /api/v1/instance InstanceToMasto(i *gtsmodel.Instance) (*model.Instance, error) - // RelationshipToMasto converts a gts relationship into its mastodon equivalent for serving in various places RelationshipToMasto(r *gtsmodel.Relationship) (*model.Relationship, error) + // NotificationToMasto converts a gts notification into a mastodon notification + NotificationToMasto(n *gtsmodel.Notification) (*model.Notification, error) /* FRONTEND (mastodon) MODEL TO INTERNAL (gts) MODEL @@ -107,6 +98,8 @@ type TypeConverter interface { ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error) // ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow. ASFollowToFollow(followable Followable) (*gtsmodel.Follow, error) + // ASLikeToFave converts a remote activitystreams 'like' representation into a gts model status fave. + ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error) /* INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL @@ -114,21 +107,25 @@ type TypeConverter interface { // 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) - // FollowToASFollow converts a gts model Follow into an activity streams Follow, suitable for federation FollowToAS(f *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (vocab.ActivityStreamsFollow, error) - // MentionToAS converts a gts model mention into an activity streams Mention, suitable for federation MentionToAS(m *gtsmodel.Mention) (vocab.ActivityStreamsMention, error) - // AttachmentToAS converts a gts model media attachment into an activity streams Attachment, suitable for federation AttachmentToAS(a *gtsmodel.MediaAttachment) (vocab.ActivityStreamsDocument, error) - // FaveToAS converts a gts model status fave into an activityStreams LIKE, suitable for federation. FaveToAS(f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, error) + + /* + INTERNAL (gts) MODEL TO INTERNAL MODEL + */ + + // FollowRequestToFollow just converts a follow request into a follow, that's it! No bells and whistles. + FollowRequestToFollow(f *gtsmodel.FollowRequest) *gtsmodel.Follow + // StatusToBoost wraps the given status into a boosting status. + StatusToBoost(s *gtsmodel.Status, boostingAccount *gtsmodel.Account) (*gtsmodel.Status, error) } type converter struct { diff --git a/internal/typeutils/internal.go b/internal/typeutils/internal.go new file mode 100644 index 000000000..3110b382c --- /dev/null +++ b/internal/typeutils/internal.go @@ -0,0 +1,76 @@ +package typeutils + +import ( + "fmt" + "time" + + "github.com/google/uuid" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/util" +) + +func (c *converter) FollowRequestToFollow(f *gtsmodel.FollowRequest) *gtsmodel.Follow { + return >smodel.Follow{ + ID: f.ID, + CreatedAt: f.CreatedAt, + UpdatedAt: f.UpdatedAt, + AccountID: f.AccountID, + TargetAccountID: f.TargetAccountID, + ShowReblogs: f.ShowReblogs, + URI: f.URI, + Notify: f.Notify, + } +} + +func (c *converter) StatusToBoost(s *gtsmodel.Status, boostingAccount *gtsmodel.Account) (*gtsmodel.Status, error) { + // the wrapper won't use the same ID as the boosted status so we generate some new UUIDs + uris := util.GenerateURIsForAccount(boostingAccount.Username, c.config.Protocol, c.config.Host) + boostWrapperStatusID := uuid.NewString() + boostWrapperStatusURI := fmt.Sprintf("%s/%s", uris.StatusesURI, boostWrapperStatusID) + boostWrapperStatusURL := fmt.Sprintf("%s/%s", uris.StatusesURL, boostWrapperStatusID) + + local := true + if boostingAccount.Domain != "" { + local = false + } + + boostWrapperStatus := >smodel.Status{ + ID: boostWrapperStatusID, + URI: boostWrapperStatusURI, + URL: boostWrapperStatusURL, + + // the boosted status is not created now, but the boost certainly is + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Local: local, + AccountID: boostingAccount.ID, + + // replies can be boosted, but boosts are never replies + InReplyToID: "", + InReplyToAccountID: "", + + // these will all be wrapped in the boosted status so set them empty here + Attachments: []string{}, + Tags: []string{}, + Mentions: []string{}, + Emojis: []string{}, + + // the below fields will be taken from the target status + Content: util.HTMLFormat(s.Content), + ContentWarning: s.ContentWarning, + ActivityStreamsType: s.ActivityStreamsType, + Sensitive: s.Sensitive, + Language: s.Language, + Text: s.Text, + BoostOfID: s.ID, + Visibility: s.Visibility, + VisibilityAdvanced: s.VisibilityAdvanced, + + // attach these here for convenience -- the boosted status/account won't go in the DB + // but they're needed in the processor and for the frontend. Since we have them, we can + // attach them so we don't need to fetch them again later (save some DB calls) + GTSBoostedStatus: s, + } + + return boostWrapperStatus, nil +} diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index 821720e0d..805f69afb 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -262,12 +262,12 @@ func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, e // 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.GTSAccount == nil { + if s.GTSAuthorAccount == nil { a := >smodel.Account{} if err := c.db.GetByID(s.AccountID, a); err != nil { return nil, fmt.Errorf("StatusToAS: error retrieving author account from db: %s", err) } - s.GTSAccount = a + s.GTSAuthorAccount = a } // create the Note! @@ -328,9 +328,9 @@ func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, e } // attributedTo - authorAccountURI, err := url.Parse(s.GTSAccount.URI) + authorAccountURI, err := url.Parse(s.GTSAuthorAccount.URI) if err != nil { - return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSAccount.URI, err) + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSAuthorAccount.URI, err) } attributedToProp := streams.NewActivityStreamsAttributedToProperty() attributedToProp.AppendIRI(authorAccountURI) @@ -357,9 +357,9 @@ func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, e status.SetActivityStreamsTag(tagProp) // parse out some URIs we need here - authorFollowersURI, err := url.Parse(s.GTSAccount.FollowersURI) + authorFollowersURI, err := url.Parse(s.GTSAuthorAccount.FollowersURI) if err != nil { - return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSAccount.FollowersURI, err) + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSAuthorAccount.FollowersURI, err) } publicURI, err := url.Parse(asPublicURI) diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 7fbe9eb3f..de3b94e01 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -138,6 +138,9 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e fields = append(fields, mField) } + emojis := []model.Emoji{} + // TODO: account emojis + var acct string if a.Domain != "" { // this is a remote user @@ -165,7 +168,7 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e FollowingCount: followingCount, StatusesCount: statusesCount, LastStatusAt: lastStatusAt, - Emojis: nil, // TODO: implement this + Emojis: emojis, // TODO: implement this Fields: fields, }, nil } @@ -267,7 +270,7 @@ func (c *converter) TagToMasto(t *gtsmodel.Tag) (model.Tag, error) { func (c *converter) StatusToMasto( s *gtsmodel.Status, - targetAccount *gtsmodel.Account, + statusAuthor *gtsmodel.Account, requestingAccount *gtsmodel.Account, boostOfAccount *gtsmodel.Account, replyToAccount *gtsmodel.Account, @@ -379,7 +382,7 @@ func (c *converter) StatusToMasto( } } - mastoTargetAccount, err := c.AccountToMastoPublic(targetAccount) + mastoAuthorAccount, err := c.AccountToMastoPublic(statusAuthor) if err != nil { return nil, fmt.Errorf("error parsing account of status author: %s", err) } @@ -517,7 +520,7 @@ func (c *converter) StatusToMasto( Content: s.Content, Reblog: mastoRebloggedStatus, Application: mastoApplication, - Account: mastoTargetAccount, + Account: mastoAuthorAccount, MediaAttachments: mastoAttachments, Mentions: mastoMentions, Tags: mastoTags, @@ -594,3 +597,68 @@ func (c *converter) RelationshipToMasto(r *gtsmodel.Relationship) (*model.Relati Note: r.Note, }, nil } + +func (c *converter) NotificationToMasto(n *gtsmodel.Notification) (*model.Notification, error) { + + if n.GTSTargetAccount == nil { + tAccount := >smodel.Account{} + if err := c.db.GetByID(n.TargetAccountID, tAccount); err != nil { + return nil, fmt.Errorf("NotificationToMasto: error getting target account with id %s from the db: %s", n.TargetAccountID, err) + } + n.GTSTargetAccount = tAccount + } + + if n.GTSOriginAccount == nil { + ogAccount := >smodel.Account{} + if err := c.db.GetByID(n.OriginAccountID, ogAccount); err != nil { + return nil, fmt.Errorf("NotificationToMasto: error getting origin account with id %s from the db: %s", n.OriginAccountID, err) + } + n.GTSOriginAccount = ogAccount + } + mastoAccount, err := c.AccountToMastoPublic(n.GTSOriginAccount) + if err != nil { + return nil, fmt.Errorf("NotificationToMasto: error converting account to masto: %s", err) + } + + var mastoStatus *model.Status + if n.StatusID != "" { + if n.GTSStatus == nil { + status := >smodel.Status{} + if err := c.db.GetByID(n.StatusID, status); err != nil { + return nil, fmt.Errorf("NotificationToMasto: error getting status with id %s from the db: %s", n.StatusID, err) + } + n.GTSStatus = status + } + + var replyToAccount *gtsmodel.Account + if n.GTSStatus.InReplyToAccountID != "" { + r := >smodel.Account{} + if err := c.db.GetByID(n.GTSStatus.InReplyToAccountID, r); err != nil { + return nil, fmt.Errorf("NotificationToMasto: error getting replied to account with id %s from the db: %s", n.GTSStatus.InReplyToAccountID, err) + } + replyToAccount = r + } + + if n.GTSStatus.GTSAuthorAccount == nil { + if n.GTSStatus.AccountID == n.GTSTargetAccount.ID { + n.GTSStatus.GTSAuthorAccount = n.GTSTargetAccount + } else if n.GTSStatus.AccountID == n.GTSOriginAccount.ID { + n.GTSStatus.GTSAuthorAccount = n.GTSOriginAccount + } + } + + var err error + mastoStatus, err = c.StatusToMasto(n.GTSStatus, n.GTSStatus.GTSAuthorAccount, n.GTSTargetAccount, nil, replyToAccount, nil) + if err != nil { + return nil, fmt.Errorf("NotificationToMasto: error converting status to masto: %s", err) + } + } + + return &model.Notification{ + ID: n.ID, + Type: string(n.NotificationType), + CreatedAt: n.CreatedAt.Format(time.RFC3339), + Account: mastoAccount, + Status: mastoStatus, + }, nil +} |