diff options
author | 2021-05-28 19:57:04 +0200 | |
---|---|---|
committer | 2021-05-28 19:57:04 +0200 | |
commit | 87177d840b9703f572392ef4bd0f5013fd5c3a77 (patch) | |
tree | c59388998d5defd5ec3577483f70736238953f72 /internal/typeutils | |
parent | Notifications (#34) (diff) | |
download | gotosocial-87177d840b9703f572392ef4bd0f5013fd5c3a77.tar.xz |
Announce/boost (#35)
Remote boosts incoming/outgoing now working.
Diffstat (limited to 'internal/typeutils')
-rw-r--r-- | internal/typeutils/asinterfaces.go | 12 | ||||
-rw-r--r-- | internal/typeutils/astointernal.go | 93 | ||||
-rw-r--r-- | internal/typeutils/converter.go | 15 | ||||
-rw-r--r-- | internal/typeutils/internaltoas.go | 73 |
4 files changed, 193 insertions, 0 deletions
diff --git a/internal/typeutils/asinterfaces.go b/internal/typeutils/asinterfaces.go index eea7fd7d9..aae3ecf93 100644 --- a/internal/typeutils/asinterfaces.go +++ b/internal/typeutils/asinterfaces.go @@ -111,6 +111,18 @@ type Likeable interface { withObject } +// Announceable represents the minimum interface for an activitystreams 'announce' activity. +type Announceable interface { + withJSONLDId + withTypeName + + withActor + withObject + withPublished + withTo + withCC +} + type withJSONLDId interface { GetJSONLDId() vocab.JSONLDIdProperty } diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index 0fee13b13..936cd9a22 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -422,6 +422,99 @@ func (c *converter) ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error }, nil } +func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Status, bool, error) { + status := >smodel.Status{} + isNew := true + + // check if we already have the boost in the database + idProp := announceable.GetJSONLDId() + if idProp == nil || !idProp.IsIRI() { + return nil, isNew, errors.New("no id property set on announce, or was not an iri") + } + uri := idProp.GetIRI().String() + + if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: uri}}, status); err == nil { + // we already have it, great, just return it as-is :) + isNew = false + return status, isNew, nil + } + status.URI = uri + + // get the URI of the announced/boosted status + boostedStatusURI, err := extractObject(announceable) + if err != nil { + return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error getting object from announce: %s", err) + } + + // set the URI on the new status for dereferencing later + status.GTSBoostedStatus = >smodel.Status{ + URI: boostedStatusURI.String(), + } + + // get the published time for the announce + published, err := extractPublished(announceable) + if err != nil { + return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error extracting published time: %s", err) + } + status.CreatedAt = published + status.UpdatedAt = published + + // get the actor's IRI (ie., the person who boosted the status) + actor, err := extractActor(announceable) + if err != nil { + return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error extracting actor: %s", err) + } + + // get the boosting account based on the URI + // this should have been dereferenced already before we hit this point so we can confidently error out if we don't have it + boostingAccount := >smodel.Account{} + if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: actor.String()}}, boostingAccount); err != nil { + return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error in db fetching account with uri %s: %s", actor.String(), err) + } + status.AccountID = boostingAccount.ID + + // these will all be wrapped in the boosted status so set them empty here + status.Attachments = []string{} + status.Tags = []string{} + status.Mentions = []string{} + status.Emojis = []string{} + + // parse the visibility from the To and CC entries + var visibility gtsmodel.Visibility + + to, err := extractTos(announceable) + if err != nil { + return nil, isNew, fmt.Errorf("error extracting TO values: %s", err) + } + + cc, err := extractCCs(announceable) + if err != nil { + return nil, isNew, fmt.Errorf("error extracting CC values: %s", err) + } + + if len(to) == 0 && len(cc) == 0 { + return nil, isNew, errors.New("message wasn't TO or CC anyone") + } + + // if it's CC'ed to public, it's public or unlocked + if isPublic(cc) { + visibility = gtsmodel.VisibilityUnlocked + } + if isPublic(to) { + visibility = gtsmodel.VisibilityPublic + } + + // we should have a visibility by now + if visibility == "" { + return nil, isNew, errors.New("couldn't derive visibility") + } + status.Visibility = visibility + + // the rest of the fields will be taken from the target status, but it's not our job to do the dereferencing here + + return status, isNew, 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 cf94faf2e..93ac6bd84 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -100,6 +100,19 @@ type TypeConverter interface { 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) + // ASAnnounceToStatus converts an activitystreams 'announce' into a status. + // + // The returned bool indicates whether this status is new (true) or not new (false). + // + // In other words, if the status is already in the database with the ID set on the announceable, then that will be returned, + // the returned bool will be false, and no further processing is necessary. If the returned bool is true, indicating + // that this is a new announce, then further processing will be necessary, because the returned status will be bareboned and + // require further dereferencing. + // + // This is useful when multiple users on an instance might receive the same boost, and we only want to process the boost once. + // + // NOTE -- this is different from one status being boosted multiple times! In this case, new boosts should indeed be created. + ASAnnounceToStatus(announceable Announceable) (status *gtsmodel.Status, new bool, err error) /* INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL @@ -117,6 +130,8 @@ type TypeConverter interface { 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) + // BoostToAS converts a gts model boost into an activityStreams ANNOUNCE, suitable for federation + BoostToAS(boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) (vocab.ActivityStreamsAnnounce, error) /* INTERNAL (gts) MODEL TO INTERNAL MODEL diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index 805f69afb..cceb1b11b 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -640,3 +640,76 @@ func (c *converter) FaveToAS(f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, return like, nil } + +func (c *converter) BoostToAS(boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) (vocab.ActivityStreamsAnnounce, error) { + // the boosted status is probably pinned to the boostWrapperStatus but double check to make sure + if boostWrapperStatus.GTSBoostedStatus == nil { + b := >smodel.Status{} + if err := c.db.GetByID(boostWrapperStatus.BoostOfID, b); err != nil { + return nil, fmt.Errorf("BoostToAS: error getting status with ID %s from the db: %s", boostWrapperStatus.BoostOfID, err) + } + boostWrapperStatus = b + } + + // create the announce + announce := streams.NewActivityStreamsAnnounce() + + // set the actor + boosterURI, err := url.Parse(boostingAccount.URI) + if err != nil { + return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostingAccount.URI, err) + } + actorProp := streams.NewActivityStreamsActorProperty() + actorProp.AppendIRI(boosterURI) + announce.SetActivityStreamsActor(actorProp) + + // set the ID + boostIDURI, err := url.Parse(boostWrapperStatus.URI) + if err != nil { + return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostWrapperStatus.URI, err) + } + idProp := streams.NewJSONLDIdProperty() + idProp.SetIRI(boostIDURI) + announce.SetJSONLDId(idProp) + + // set the object + boostedStatusURI, err := url.Parse(boostWrapperStatus.GTSBoostedStatus.URI) + if err != nil { + return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostWrapperStatus.GTSBoostedStatus.URI, err) + } + objectProp := streams.NewActivityStreamsObjectProperty() + objectProp.AppendIRI(boostedStatusURI) + announce.SetActivityStreamsObject(objectProp) + + // set the published time + publishedProp := streams.NewActivityStreamsPublishedProperty() + publishedProp.Set(boostWrapperStatus.CreatedAt) + announce.SetActivityStreamsPublished(publishedProp) + + // set the to + followersURI, err := url.Parse(boostingAccount.FollowersURI) + if err != nil { + return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostingAccount.FollowersURI, err) + } + toProp := streams.NewActivityStreamsToProperty() + toProp.AppendIRI(followersURI) + announce.SetActivityStreamsTo(toProp) + + // set the cc + boostedURI, err := url.Parse(boostedAccount.URI) + if err != nil { + return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostedAccount.URI, err) + } + + publicURI, err := url.Parse(asPublicURI) + if err != nil { + return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", asPublicURI, err) + } + + ccProp := streams.NewActivityStreamsCcProperty() + ccProp.AppendIRI(boostedURI) + ccProp.AppendIRI(publicURI) + announce.SetActivityStreamsCc(ccProp) + + return announce, nil +} |