diff options
Diffstat (limited to 'internal/typeutils')
-rw-r--r-- | internal/typeutils/astointernal.go | 55 | ||||
-rw-r--r-- | internal/typeutils/converter.go | 18 | ||||
-rw-r--r-- | internal/typeutils/internaltoas.go | 303 | ||||
-rw-r--r-- | internal/typeutils/internaltofrontend.go | 18 |
4 files changed, 384 insertions, 10 deletions
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index 4aa6e2b19..bf1ef7f45 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -37,7 +37,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode uri := uriProp.GetIRI() acct := >smodel.Account{} - err := c.db.GetWhere("uri", uri.String(), acct) + err := c.db.GetWhere([]db.Where{{Key: "uri", Value: uri.String()}}, acct) if err == nil { // we already know this account so we can skip generating it return acct, nil @@ -90,7 +90,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode } // check for bot and actor type - switch gtsmodel.ActivityStreamsActor(accountable.GetTypeName()) { + switch accountable.GetTypeName() { case gtsmodel.ActivityStreamsPerson, gtsmodel.ActivityStreamsGroup, gtsmodel.ActivityStreamsOrganization: // people, groups, and organizations aren't bots acct.Bot = false @@ -101,7 +101,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode // we don't know what this is! return nil, fmt.Errorf("type name %s not recognised or not convertible to gtsmodel.ActivityStreamsActor", accountable.GetTypeName()) } - acct.ActorType = gtsmodel.ActivityStreamsActor(accountable.GetTypeName()) + acct.ActorType = accountable.GetTypeName() // TODO: locked aka manuallyApprovesFollowers @@ -220,7 +220,7 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e status.APStatusOwnerURI = attributedTo.String() statusOwner := >smodel.Account{} - if err := c.db.GetWhere("uri", attributedTo.String(), statusOwner); err != nil { + if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: attributedTo.String()}}, statusOwner); err != nil { return nil, fmt.Errorf("couldn't get status owner from db: %s", err) } status.AccountID = statusOwner.ID @@ -235,7 +235,7 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e // now we can check if we have the replied-to status in our db already inReplyToStatus := >smodel.Status{} - if err := c.db.GetWhere("uri", inReplyToURI.String(), inReplyToStatus); err == nil { + if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: inReplyToURI.String()}}, inReplyToStatus); err == nil { // we have the status in our database already // so we can set these fields here and then... status.InReplyToID = inReplyToStatus.ID @@ -281,7 +281,10 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e // if it's CC'ed to public, it's public or unlocked // mentioned SPECIFIC ACCOUNTS also get added to CC'es if it's not a direct message - if isPublic(cc) || isPublic(to) { + if isPublic(cc) { + visibility = gtsmodel.VisibilityUnlocked + } + if isPublic(to) { visibility = gtsmodel.VisibilityPublic } @@ -301,7 +304,7 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e // we might be able to extract this from the contentMap field // ActivityStreamsType - status.ActivityStreamsType = gtsmodel.ActivityStreamsObject(statusable.GetTypeName()) + status.ActivityStreamsType = statusable.GetTypeName() return status, nil } @@ -319,7 +322,7 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo return nil, errors.New("error extracting actor property from follow") } originAccount := >smodel.Account{} - if err := c.db.GetWhere("uri", origin.String(), originAccount); err != nil { + 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) } @@ -328,7 +331,7 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo return nil, errors.New("error extracting object property from follow") } targetAccount := >smodel.Account{} - if err := c.db.GetWhere("uri", target.String(), targetAccount); err != nil { + if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: target.String()}}, targetAccount); err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -341,6 +344,40 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo return followRequest, nil } +func (c *converter) ASFollowToFollow(followable Followable) (*gtsmodel.Follow, error) { + idProp := followable.GetJSONLDId() + if idProp == nil || !idProp.IsIRI() { + return nil, errors.New("no id property set on follow, or was not an iri") + } + uri := idProp.GetIRI().String() + + origin, err := extractActor(followable) + if err != nil { + return nil, errors.New("error extracting actor property from follow") + } + 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(followable) + if err != nil { + return nil, errors.New("error extracting object property from follow") + } + targetAccount := >smodel.Account{} + if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: target.String()}}, targetAccount); err != nil { + return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) + } + + follow := >smodel.Follow{ + URI: uri, + AccountID: originAccount.ID, + TargetAccountID: targetAccount.ID, + } + + return follow, 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 8f310c921..ac2ce4317 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -26,6 +26,10 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) +const ( + asPublicURI = "https://www.w3.org/ns/activitystreams#Public" +) + // TypeConverter is an interface for the common action of converting between apimodule (frontend, serializable) models, // internal gts models used in the database, and activitypub models used in federation. // @@ -77,6 +81,9 @@ type TypeConverter interface { // 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) + /* FRONTEND (mastodon) MODEL TO INTERNAL (gts) MODEL */ @@ -94,6 +101,8 @@ type TypeConverter interface { ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error) // ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request. ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error) + // ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow. + ASFollowToFollow(followable Followable) (*gtsmodel.Follow, error) /* INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL @@ -104,6 +113,15 @@ type TypeConverter interface { // 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) } type converter struct { diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index 0216dea5e..072c4e690 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -21,10 +21,12 @@ package typeutils import ( "crypto/x509" "encoding/pem" + "fmt" "net/url" "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" + "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -256,5 +258,304 @@ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerso } func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) { - return nil, nil + // 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.GTSAccount == 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 + } + + // 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.GTSReplyToStatus == nil { + rs := >smodel.Status{} + if err := c.db.GetByID(s.InReplyToID, rs); err != nil { + return nil, fmt.Errorf("StatusToAS: error retrieving replied-to status from db: %s", err) + } + s.GTSReplyToStatus = rs + } + rURI, err := url.Parse(s.GTSReplyToStatus.URI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSReplyToStatus.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.GTSAccount.URI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSAccount.URI, err) + } + attributedToProp := streams.NewActivityStreamsAttributedToProperty() + attributedToProp.AppendIRI(authorAccountURI) + status.SetActivityStreamsAttributedTo(attributedToProp) + + // tags + tagProp := streams.NewActivityStreamsTagProperty() + + // tag -- mentions + for _, m := range s.GTSMentions { + asMention, err := c.MentionToAS(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.GTSAccount.FollowersURI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSAccount.FollowersURI, err) + } + + publicURI, err := url.Parse(asPublicURI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", asPublicURI, err) + } + + // to and cc + toProp := streams.NewActivityStreamsToProperty() + ccProp := streams.NewActivityStreamsCcProperty() + switch s.Visibility { + case gtsmodel.VisibilityDirect: + // if DIRECT, then only mentioned users should be added to TO, and nothing to CC + for _, m := range s.GTSMentions { + iri, err := url.Parse(m.GTSAccount.URI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.GTSAccount.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.GTSMentions { + iri, err := url.Parse(m.GTSAccount.URI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.GTSAccount.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.GTSMentions { + iri, err := url.Parse(m.GTSAccount.URI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.GTSAccount.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.GTSMentions { + iri, err := url.Parse(m.GTSAccount.URI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing uri %s: %s", m.GTSAccount.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.GTSMediaAttachments { + doc, err := c.AttachmentToAS(a) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error converting attachment: %s", err) + } + attachmentProp.AppendActivityStreamsDocument(doc) + } + status.SetActivityStreamsAttachment(attachmentProp) + + // replies + // TODO + + return status, nil +} + +func (c *converter) FollowToAS(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(m *gtsmodel.Mention) (vocab.ActivityStreamsMention, error) { + if m.GTSAccount == nil { + a := >smodel.Account{} + if err := c.db.GetWhere([]db.Where{{Key: "target_account_id", Value: m.TargetAccountID}}, a); err != nil { + return nil, fmt.Errorf("MentionToAS: error getting target account from db: %s", err) + } + m.GTSAccount = 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.GTSAccount.URI) + if err != nil { + return nil, fmt.Errorf("MentionToAS: error parsing uri %s: %s", m.GTSAccount.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.GTSAccount.Domain == "" { + domain = c.config.Host + } else { + domain = m.GTSAccount.Domain + } + username := m.GTSAccount.Username + nameString := fmt.Sprintf("@%s@%s", username, domain) + nameProp := streams.NewActivityStreamsNameProperty() + nameProp.AppendXMLSchemaString(nameString) + mention.SetActivityStreamsName(nameProp) + + return mention, nil +} + +func (c *converter) AttachmentToAS(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 } diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index e4ccab988..70f8a8d3c 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -572,3 +572,21 @@ func (c *converter) InstanceToMasto(i *gtsmodel.Instance) (*model.Instance, erro return mi, nil } + +func (c *converter) RelationshipToMasto(r *gtsmodel.Relationship) (*model.Relationship, error) { + return &model.Relationship{ + ID: r.ID, + Following: r.Following, + ShowingReblogs: r.ShowingReblogs, + Notifying: r.Notifying, + FollowedBy: r.FollowedBy, + Blocking: r.Blocking, + BlockedBy: r.BlockedBy, + Muting: r.Muting, + MutingNotifications: r.MutingNotifications, + Requested: r.Requested, + DomainBlocking: r.DomainBlocking, + Endorsed: r.Endorsed, + Note: r.Note, + }, nil +} |