diff --git a/internal/api/client/status/status.go b/internal/api/client/status/status.go index ba92956..a91f1fa 100644 --- a/internal/api/client/status/status.go +++ b/internal/api/client/status/status.go @@ -96,6 +96,8 @@ func (m *Module) Route(r router.Router) error { r.AttachHandler(http.MethodPost, FavouritePath, m.StatusFavePOSTHandler) r.AttachHandler(http.MethodPost, UnfavouritePath, m.StatusUnfavePOSTHandler) + r.AttachHandler(http.MethodPost, ReblogPath, m.StatusBoostPOSTHandler) + r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler) return nil } diff --git a/internal/federation/federatingdb/util.go b/internal/federation/federatingdb/util.go index be3cff9..53fe194 100644 --- a/internal/federation/federatingdb/util.go +++ b/internal/federation/federatingdb/util.go @@ -130,6 +130,19 @@ func (f *federatingDB) NewID(c context.Context, t vocab.Type) (id *url.URL, err return idProp.GetIRI(), nil } } + case gtsmodel.ActivityStreamsAnnounce: + // ANNOUNCE aka BOOST + // ID might already be set on an announce we've created, so check it here and return it if it is + announce, ok := t.(vocab.ActivityStreamsAnnounce) + if !ok { + return nil, errors.New("newid: fave couldn't be parsed into vocab.ActivityStreamsAnnounce") + } + idProp := announce.GetJSONLDId() + if idProp != nil { + if idProp.IsIRI() { + return idProp.GetIRI(), nil + } + } } // fallback default behavior: just return a random UUID after our protocol and host diff --git a/internal/message/fromclientapiprocess.go b/internal/message/fromclientapiprocess.go index 12e4bd3..1d30b52 100644 --- a/internal/message/fromclientapiprocess.go +++ b/internal/message/fromclientapiprocess.go @@ -72,6 +72,19 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error } return p.federateFave(fave, clientMsg.OriginAccount, clientMsg.TargetAccount) + + case gtsmodel.ActivityStreamsAnnounce: + // CREATE BOOST/ANNOUNCE + boostWrapperStatus, ok := clientMsg.GTSModel.(*gtsmodel.Status) + if !ok { + return errors.New("boost was not parseable as *gtsmodel.Status") + } + + if err := p.notifyAnnounce(boostWrapperStatus); err != nil { + return err + } + + return p.federateAnnounce(boostWrapperStatus, clientMsg.OriginAccount, clientMsg.TargetAccount) } case gtsmodel.ActivityStreamsUpdate: // UPDATE @@ -253,3 +266,18 @@ func (p *processor) federateFave(fave *gtsmodel.StatusFave, originAccount *gtsmo _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, asFave) return err } + +func (p *processor) federateAnnounce(boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) error { + announce, err := p.tc.BoostToAS(boostWrapperStatus, boostingAccount, boostedAccount) + if err != nil { + return fmt.Errorf("federateAnnounce: error converting status to announce: %s", err) + } + + outboxIRI, err := url.Parse(boostingAccount.OutboxURI) + if err != nil { + return fmt.Errorf("federateAnnounce: error parsing outboxURI %s: %s", boostingAccount.OutboxURI, err) + } + + _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, announce) + return err +} diff --git a/internal/message/statusprocess.go b/internal/message/statusprocess.go index f64c359..40c7b30 100644 --- a/internal/message/statusprocess.go +++ b/internal/message/statusprocess.go @@ -291,6 +291,15 @@ func (p *processor) StatusBoost(authed *oauth.Auth, targetStatusID string) (*api return nil, NewErrorInternalError(err) } + // send it to the processor for async processing + p.fromClientAPI <- gtsmodel.FromClientAPI{ + APObjectType: gtsmodel.ActivityStreamsAnnounce, + APActivityType: gtsmodel.ActivityStreamsCreate, + GTSModel: boostWrapperStatus, + OriginAccount: authed.Account, + TargetAccount: targetAccount, + } + // return the frontend representation of the new status to the submitter mastoStatus, err := p.tc.StatusToMasto(boostWrapperStatus, authed.Account, authed.Account, targetAccount, nil, targetStatus) if err != nil { diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index 07919ff..93ac6bd 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -130,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 805f69a..cceb1b1 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 +}