summaryrefslogtreecommitdiff
path: root/internal/typeutils
diff options
context:
space:
mode:
Diffstat (limited to 'internal/typeutils')
-rw-r--r--internal/typeutils/asinterfaces.go9
-rw-r--r--internal/typeutils/astointernal.go44
-rw-r--r--internal/typeutils/converter.go31
-rw-r--r--internal/typeutils/internal.go76
-rw-r--r--internal/typeutils/internaltoas.go12
-rw-r--r--internal/typeutils/internaltofrontend.go76
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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 &gtsmodel.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 &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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
+}