diff options
author | 2023-08-09 19:14:33 +0200 | |
---|---|---|
committer | 2023-08-09 19:14:33 +0200 | |
commit | 9770d54237bea828cab7e50aec7dff452c203138 (patch) | |
tree | 59c444a02e81925bab47d3656a489a8c7087d530 /internal/processing/workers/federate.go | |
parent | [bugfix] Fix incorrect per-loop variable capture (#2092) (diff) | |
download | gotosocial-9770d54237bea828cab7e50aec7dff452c203138.tar.xz |
[feature] List replies policy, refactor async workers (#2087)
* Add/update some DB functions.
* move async workers into subprocessor
* rename FromFederator -> FromFediAPI
* update home timeline check to include check for current status first before moving to parent status
* change streamMap to pointer to mollify linter
* update followtoas func signature
* fix merge
* remove errant debug log
* don't use separate errs.Combine() check to wrap errs
* wrap parts of workers functionality in sub-structs
* populate report using new db funcs
* embed federator (tiny bit tidier)
* flesh out error msg, add continue(!)
* fix other error messages to be more specific
* better, nicer
* give parseURI util function a bit more util
* missing headers
* use pointers for subprocessors
Diffstat (limited to 'internal/processing/workers/federate.go')
-rw-r--r-- | internal/processing/workers/federate.go | 892 |
1 files changed, 892 insertions, 0 deletions
diff --git a/internal/processing/workers/federate.go b/internal/processing/workers/federate.go new file mode 100644 index 000000000..76bfc892e --- /dev/null +++ b/internal/processing/workers/federate.go @@ -0,0 +1,892 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package workers + +import ( + "context" + "net/url" + + "github.com/superseriousbusiness/activity/pub" + "github.com/superseriousbusiness/activity/streams" + "github.com/superseriousbusiness/gotosocial/internal/federation" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/state" + "github.com/superseriousbusiness/gotosocial/internal/typeutils" +) + +// federate wraps functions for federating +// something out via ActivityPub in response +// to message processing. +type federate struct { + // Embed federator to give access + // to send and retrieve functions. + federation.Federator + state *state.State + tc typeutils.TypeConverter +} + +// parseURI is a cheeky little +// shortcut to wrap parsing errors. +// +// The returned err will be prepended +// with the name of the function that +// called this function, so it can be +// returned without further wrapping. +func parseURI(s string) (*url.URL, error) { + const ( + // Provides enough calldepth to + // prepend the name of whatever + // function called *this* one, + // so that they don't have to + // wrap the error themselves. + calldepth = 3 + errFmt = "error parsing uri %s: %w" + ) + + uri, err := url.Parse(s) + if err != nil { + return nil, gtserror.NewfAt(calldepth, errFmt, s, err) + } + + return uri, err +} + +func (f *federate) DeleteAccount(ctx context.Context, account *gtsmodel.Account) error { + // Do nothing if it's not our + // account that's been deleted. + if !account.IsLocal() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(account.OutboxURI) + if err != nil { + return err + } + + actorIRI, err := parseURI(account.URI) + if err != nil { + return err + } + + followersIRI, err := parseURI(account.FollowersURI) + if err != nil { + return err + } + + publicIRI, err := parseURI(pub.PublicActivityPubIRI) + if err != nil { + return err + } + + // Create a new delete. + // todo: tc.AccountToASDelete + delete := streams.NewActivityStreamsDelete() + + // Set the Actor for the delete; no matter + // who actually did the delete, we should + // use the account owner for this. + deleteActor := streams.NewActivityStreamsActorProperty() + deleteActor.AppendIRI(actorIRI) + delete.SetActivityStreamsActor(deleteActor) + + // Set the account's IRI as the 'object' property. + deleteObject := streams.NewActivityStreamsObjectProperty() + deleteObject.AppendIRI(actorIRI) + delete.SetActivityStreamsObject(deleteObject) + + // Address the delete To followers. + deleteTo := streams.NewActivityStreamsToProperty() + deleteTo.AppendIRI(followersIRI) + delete.SetActivityStreamsTo(deleteTo) + + // Address the delete CC public. + deleteCC := streams.NewActivityStreamsCcProperty() + deleteCC.AppendIRI(publicIRI) + delete.SetActivityStreamsCc(deleteCC) + + // Send the Delete via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, delete, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + delete, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) CreateStatus(ctx context.Context, status *gtsmodel.Status) error { + // Do nothing if the status + // shouldn't be federated. + if !*status.Federated { + return nil + } + + // Do nothing if this + // isn't our status. + if !*status.Local { + return nil + } + + // Populate model. + if err := f.state.DB.PopulateStatus(ctx, status); err != nil { + return gtserror.Newf("error populating status: %w", err) + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(status.Account.OutboxURI) + if err != nil { + return err + } + + // Convert status to an ActivityStreams + // Note, wrapped in a Create activity. + asStatus, err := f.tc.StatusToAS(ctx, status) + if err != nil { + return gtserror.Newf("error converting status to AS: %w", err) + } + + create, err := f.tc.WrapNoteInCreate(asStatus, false) + if err != nil { + return gtserror.Newf("error wrapping status in create: %w", err) + } + + // Send the Create via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, create, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + create, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) DeleteStatus(ctx context.Context, status *gtsmodel.Status) error { + // Do nothing if the status + // shouldn't be federated. + if !*status.Federated { + return nil + } + + // Do nothing if this + // isn't our status. + if !*status.Local { + return nil + } + + // Populate model. + if err := f.state.DB.PopulateStatus(ctx, status); err != nil { + return gtserror.Newf("error populating status: %w", err) + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(status.Account.OutboxURI) + if err != nil { + return err + } + + // Wrap the status URI in a Delete activity. + delete, err := f.tc.StatusToASDelete(ctx, status) + if err != nil { + return gtserror.Newf("error creating Delete: %w", err) + } + + // Send the Delete via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, delete, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + delete, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) Follow(ctx context.Context, follow *gtsmodel.Follow) error { + // Populate model. + if err := f.state.DB.PopulateFollow(ctx, follow); err != nil { + return gtserror.Newf("error populating follow: %w", err) + } + + // Do nothing if both accounts are local. + if follow.Account.IsLocal() && + follow.TargetAccount.IsLocal() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(follow.Account.OutboxURI) + if err != nil { + return err + } + + // Convert follow to ActivityStreams Follow. + asFollow, err := f.tc.FollowToAS(ctx, follow) + if err != nil { + return gtserror.Newf("error converting follow to AS: %s", err) + } + + // Send the Follow via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, asFollow, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + asFollow, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) UndoFollow(ctx context.Context, follow *gtsmodel.Follow) error { + // Populate model. + if err := f.state.DB.PopulateFollow(ctx, follow); err != nil { + return gtserror.Newf("error populating follow: %w", err) + } + + // Do nothing if both accounts are local. + if follow.Account.IsLocal() && + follow.TargetAccount.IsLocal() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(follow.Account.OutboxURI) + if err != nil { + return err + } + + targetAccountIRI, err := parseURI(follow.TargetAccount.URI) + if err != nil { + return err + } + + // Recreate the ActivityStreams Follow. + asFollow, err := f.tc.FollowToAS(ctx, follow) + if err != nil { + return gtserror.Newf("error converting follow to AS: %w", err) + } + + // Create a new Undo. + // todo: tc.FollowToASUndo + undo := streams.NewActivityStreamsUndo() + + // Set the Actor for the Undo: + // same as the actor for the Follow. + undo.SetActivityStreamsActor(asFollow.GetActivityStreamsActor()) + + // Set recreated Follow as the 'object' property. + // + // For most AP implementations, it's not enough + // to just send the URI of the original Follow, + // we have to send the whole object again. + undoObject := streams.NewActivityStreamsObjectProperty() + undoObject.AppendActivityStreamsFollow(asFollow) + undo.SetActivityStreamsObject(undoObject) + + // Address the Undo To the target account. + undoTo := streams.NewActivityStreamsToProperty() + undoTo.AppendIRI(targetAccountIRI) + undo.SetActivityStreamsTo(undoTo) + + // Send the Undo via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, undo, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + undo, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) UndoLike(ctx context.Context, fave *gtsmodel.StatusFave) error { + // Populate model. + if err := f.state.DB.PopulateStatusFave(ctx, fave); err != nil { + return gtserror.Newf("error populating fave: %w", err) + } + + // Do nothing if both accounts are local. + if fave.Account.IsLocal() && + fave.TargetAccount.IsLocal() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(fave.Account.OutboxURI) + if err != nil { + return err + } + + targetAccountIRI, err := parseURI(fave.TargetAccount.URI) + if err != nil { + return err + } + + // Recreate the ActivityStreams Like. + like, err := f.tc.FaveToAS(ctx, fave) + if err != nil { + return gtserror.Newf("error converting fave to AS: %w", err) + } + + // Create a new Undo. + // todo: tc.FaveToASUndo + undo := streams.NewActivityStreamsUndo() + + // Set the Actor for the Undo: + // same as the actor for the Like. + undo.SetActivityStreamsActor(like.GetActivityStreamsActor()) + + // Set recreated Like as the 'object' property. + // + // For most AP implementations, it's not enough + // to just send the URI of the original Like, + // we have to send the whole object again. + undoObject := streams.NewActivityStreamsObjectProperty() + undoObject.AppendActivityStreamsLike(like) + undo.SetActivityStreamsObject(undoObject) + + // Address the Undo To the target account. + undoTo := streams.NewActivityStreamsToProperty() + undoTo.AppendIRI(targetAccountIRI) + undo.SetActivityStreamsTo(undoTo) + + // Send the Undo via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, undo, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + undo, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) UndoAnnounce(ctx context.Context, boost *gtsmodel.Status) error { + // Populate model. + if err := f.state.DB.PopulateStatus(ctx, boost); err != nil { + return gtserror.Newf("error populating status: %w", err) + } + + // Do nothing if boosting + // account isn't ours. + if !boost.Account.IsLocal() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(boost.Account.OutboxURI) + if err != nil { + return err + } + + // Recreate the ActivityStreams Announce. + asAnnounce, err := f.tc.BoostToAS( + ctx, + boost, + boost.Account, + boost.BoostOfAccount, + ) + if err != nil { + return gtserror.Newf("error converting boost to AS: %w", err) + } + + // Create a new Undo. + // todo: tc.AnnounceToASUndo + undo := streams.NewActivityStreamsUndo() + + // Set the Actor for the Undo: + // same as the actor for the Announce. + undo.SetActivityStreamsActor(asAnnounce.GetActivityStreamsActor()) + + // Set recreated Announce as the 'object' property. + // + // For most AP implementations, it's not enough + // to just send the URI of the original Announce, + // we have to send the whole object again. + undoObject := streams.NewActivityStreamsObjectProperty() + undoObject.AppendActivityStreamsAnnounce(asAnnounce) + undo.SetActivityStreamsObject(undoObject) + + // Address the Undo To the Announce To. + undo.SetActivityStreamsTo(asAnnounce.GetActivityStreamsTo()) + + // Address the Undo CC the Announce CC. + undo.SetActivityStreamsCc(asAnnounce.GetActivityStreamsCc()) + + // Send the Undo via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, undo, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + undo, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) AcceptFollow(ctx context.Context, follow *gtsmodel.Follow) error { + // Populate model. + if err := f.state.DB.PopulateFollow(ctx, follow); err != nil { + return gtserror.Newf("error populating follow: %w", err) + } + + // Bail if requesting account is ours: + // we've already accepted internally and + // shouldn't send an Accept to ourselves. + if follow.Account.IsLocal() { + return nil + } + + // Bail if target account isn't ours: + // we can't Accept a follow on + // another instance's behalf. + if follow.TargetAccount.IsRemote() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(follow.TargetAccount.OutboxURI) + if err != nil { + return err + } + + acceptingAccountIRI, err := parseURI(follow.TargetAccount.URI) + if err != nil { + return err + } + + requestingAccountIRI, err := parseURI(follow.Account.URI) + if err != nil { + return err + } + + // Recreate the ActivityStreams Follow. + asFollow, err := f.tc.FollowToAS(ctx, follow) + if err != nil { + return gtserror.Newf("error converting follow to AS: %w", err) + } + + // Create a new Accept. + // todo: tc.FollowToASAccept + accept := streams.NewActivityStreamsAccept() + + // Set the requestee as Actor of the Accept. + acceptActorProp := streams.NewActivityStreamsActorProperty() + acceptActorProp.AppendIRI(acceptingAccountIRI) + accept.SetActivityStreamsActor(acceptActorProp) + + // Set recreated Follow as the 'object' property. + // + // For most AP implementations, it's not enough + // to just send the URI of the original Follow, + // we have to send the whole object again. + acceptObject := streams.NewActivityStreamsObjectProperty() + acceptObject.AppendActivityStreamsFollow(asFollow) + accept.SetActivityStreamsObject(acceptObject) + + // Address the Accept To the Follow requester. + acceptTo := streams.NewActivityStreamsToProperty() + acceptTo.AppendIRI(requestingAccountIRI) + accept.SetActivityStreamsTo(acceptTo) + + // Send the Accept via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, accept, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + accept, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) RejectFollow(ctx context.Context, follow *gtsmodel.Follow) error { + // Ensure follow populated before proceeding. + if err := f.state.DB.PopulateFollow(ctx, follow); err != nil { + return gtserror.Newf("error populating follow: %w", err) + } + + // Bail if requesting account is ours: + // we've already rejected internally and + // shouldn't send an Reject to ourselves. + if follow.Account.IsLocal() { + return nil + } + + // Bail if target account isn't ours: + // we can't Reject a follow on + // another instance's behalf. + if follow.TargetAccount.IsRemote() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(follow.TargetAccount.OutboxURI) + if err != nil { + return err + } + + rejectingAccountIRI, err := parseURI(follow.TargetAccount.URI) + if err != nil { + return err + } + + requestingAccountIRI, err := parseURI(follow.Account.URI) + if err != nil { + return err + } + + // Recreate the ActivityStreams Follow. + asFollow, err := f.tc.FollowToAS(ctx, follow) + if err != nil { + return gtserror.Newf("error converting follow to AS: %w", err) + } + + // Create a new Reject. + // todo: tc.FollowRequestToASReject + reject := streams.NewActivityStreamsReject() + + // Set the requestee as Actor of the Reject. + rejectActorProp := streams.NewActivityStreamsActorProperty() + rejectActorProp.AppendIRI(rejectingAccountIRI) + reject.SetActivityStreamsActor(rejectActorProp) + + // Set recreated Follow as the 'object' property. + // + // For most AP implementations, it's not enough + // to just send the URI of the original Follow, + // we have to send the whole object again. + rejectObject := streams.NewActivityStreamsObjectProperty() + rejectObject.AppendActivityStreamsFollow(asFollow) + reject.SetActivityStreamsObject(rejectObject) + + // Address the Reject To the Follow requester. + rejectTo := streams.NewActivityStreamsToProperty() + rejectTo.AppendIRI(requestingAccountIRI) + reject.SetActivityStreamsTo(rejectTo) + + // Send the Reject via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, reject, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + reject, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) Like(ctx context.Context, fave *gtsmodel.StatusFave) error { + // Populate model. + if err := f.state.DB.PopulateStatusFave(ctx, fave); err != nil { + return gtserror.Newf("error populating fave: %w", err) + } + + // Do nothing if both accounts are local. + if fave.Account.IsLocal() && + fave.TargetAccount.IsLocal() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(fave.Account.OutboxURI) + if err != nil { + return err + } + + // Create the ActivityStreams Like. + like, err := f.tc.FaveToAS(ctx, fave) + if err != nil { + return gtserror.Newf("error converting fave to AS Like: %w", err) + } + + // Send the Like via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, like, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + like, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) Announce(ctx context.Context, boost *gtsmodel.Status) error { + // Populate model. + if err := f.state.DB.PopulateStatus(ctx, boost); err != nil { + return gtserror.Newf("error populating status: %w", err) + } + + // Do nothing if boosting + // account isn't ours. + if !boost.Account.IsLocal() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(boost.Account.OutboxURI) + if err != nil { + return err + } + + // Create the ActivityStreams Announce. + announce, err := f.tc.BoostToAS( + ctx, + boost, + boost.Account, + boost.BoostOfAccount, + ) + if err != nil { + return gtserror.Newf("error converting boost to AS: %w", err) + } + + // Send the Announce via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, announce, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + announce, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) UpdateAccount(ctx context.Context, account *gtsmodel.Account) error { + // Populate model. + if err := f.state.DB.PopulateAccount(ctx, account); err != nil { + return gtserror.Newf("error populating account: %w", err) + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(account.OutboxURI) + if err != nil { + return err + } + + // Convert account to ActivityStreams Person. + person, err := f.tc.AccountToAS(ctx, account) + if err != nil { + return gtserror.Newf("error converting account to Person: %w", err) + } + + // Use ActivityStreams Person as Object of Update. + update, err := f.tc.WrapPersonInUpdate(person, account) + if err != nil { + return gtserror.Newf("error wrapping Person in Update: %w", err) + } + + // Send the Update via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, update, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + update, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) Block(ctx context.Context, block *gtsmodel.Block) error { + // Populate model. + if err := f.state.DB.PopulateBlock(ctx, block); err != nil { + return gtserror.Newf("error populating block: %w", err) + } + + // Do nothing if both accounts are local. + if block.Account.IsLocal() && + block.TargetAccount.IsLocal() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(block.Account.OutboxURI) + if err != nil { + return err + } + + // Convert block to ActivityStreams Block. + asBlock, err := f.tc.BlockToAS(ctx, block) + if err != nil { + return gtserror.Newf("error converting block to AS: %w", err) + } + + // Send the Block via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, asBlock, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + asBlock, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) UndoBlock(ctx context.Context, block *gtsmodel.Block) error { + // Populate model. + if err := f.state.DB.PopulateBlock(ctx, block); err != nil { + return gtserror.Newf("error populating block: %w", err) + } + + // Do nothing if both accounts are local. + if block.Account.IsLocal() && + block.TargetAccount.IsLocal() { + return nil + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(block.Account.OutboxURI) + if err != nil { + return err + } + + targetAccountIRI, err := parseURI(block.TargetAccount.URI) + if err != nil { + return err + } + + // Convert block to ActivityStreams Block. + asBlock, err := f.tc.BlockToAS(ctx, block) + if err != nil { + return gtserror.Newf("error converting block to AS: %w", err) + } + + // Create a new Undo. + // todo: tc.BlockToASUndo + undo := streams.NewActivityStreamsUndo() + + // Set the Actor for the Undo: + // same as the actor for the Block. + undo.SetActivityStreamsActor(asBlock.GetActivityStreamsActor()) + + // Set Block as the 'object' property. + // + // For most AP implementations, it's not enough + // to just send the URI of the original Block, + // we have to send the whole object again. + undoObject := streams.NewActivityStreamsObjectProperty() + undoObject.AppendActivityStreamsBlock(asBlock) + undo.SetActivityStreamsObject(undoObject) + + // Address the Undo To the target account. + undoTo := streams.NewActivityStreamsToProperty() + undoTo.AppendIRI(targetAccountIRI) + undo.SetActivityStreamsTo(undoTo) + + // Send the Undo via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, undo, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + undo, outboxIRI, err, + ) + } + + return nil +} + +func (f *federate) Flag(ctx context.Context, report *gtsmodel.Report) error { + // Populate model. + if err := f.state.DB.PopulateReport(ctx, report); err != nil { + return gtserror.Newf("error populating report: %w", err) + } + + // Do nothing if report target + // is not remote account. + if report.TargetAccount.IsLocal() { + return nil + } + + // Get our instance account from the db: + // to anonymize the report, we'll deliver + // using the outbox of the instance account. + instanceAcct, err := f.state.DB.GetInstanceAccount(ctx, "") + if err != nil { + return gtserror.Newf("error getting instance account: %w", err) + } + + // Parse relevant URI(s). + outboxIRI, err := parseURI(instanceAcct.OutboxURI) + if err != nil { + return err + } + + targetAccountIRI, err := parseURI(report.TargetAccount.URI) + if err != nil { + return err + } + + // Convert report to ActivityStreams Flag. + flag, err := f.tc.ReportToASFlag(ctx, report) + if err != nil { + return gtserror.Newf("error converting report to AS: %w", err) + } + + // To is not set explicitly on Flags. Instead, + // address Flag BTo report target account URI. + // This ensures that our federating actor still + // knows where to send the report, but the BTo + // property will be stripped before sending. + // + // Happily, BTo does not prevent federating + // actor from using shared inbox to deliver. + bTo := streams.NewActivityStreamsBtoProperty() + bTo.AppendIRI(targetAccountIRI) + flag.SetActivityStreamsBto(bTo) + + // Send the Flag via the Actor's outbox. + if _, err := f.FederatingActor().Send( + ctx, outboxIRI, flag, + ); err != nil { + return gtserror.Newf( + "error sending activity %T via outbox %s: %w", + flag, outboxIRI, err, + ) + } + + return nil +} |