summaryrefslogtreecommitdiff
path: root/internal/processing/workers
diff options
context:
space:
mode:
Diffstat (limited to 'internal/processing/workers')
-rw-r--r--internal/processing/workers/federate.go18
-rw-r--r--internal/processing/workers/fromclientapi.go93
-rw-r--r--internal/processing/workers/fromfediapi.go503
-rw-r--r--internal/processing/workers/fromfediapi_test.go120
-rw-r--r--internal/processing/workers/util.go36
5 files changed, 637 insertions, 133 deletions
diff --git a/internal/processing/workers/federate.go b/internal/processing/workers/federate.go
index 3cc3d130c..7459f0114 100644
--- a/internal/processing/workers/federate.go
+++ b/internal/processing/workers/federate.go
@@ -115,7 +115,7 @@ func (f *federate) DeleteAccount(ctx context.Context, account *gtsmodel.Account)
// Address the delete CC public.
deleteCC := streams.NewActivityStreamsCcProperty()
- deleteCC.AppendIRI(ap.PublicURI())
+ deleteCC.AppendIRI(ap.PublicIRI())
delete.SetActivityStreamsCc(deleteCC)
// Send the Delete via the Actor's outbox.
@@ -491,12 +491,7 @@ func (f *federate) UndoAnnounce(ctx context.Context, boost *gtsmodel.Status) err
}
// Recreate the ActivityStreams Announce.
- asAnnounce, err := f.converter.BoostToAS(
- ctx,
- boost,
- boost.Account,
- boost.BoostOfAccount,
- )
+ asAnnounce, err := f.converter.BoostToAS(ctx, boost)
if err != nil {
return gtserror.Newf("error converting boost to AS: %w", err)
}
@@ -767,12 +762,7 @@ func (f *federate) Announce(ctx context.Context, boost *gtsmodel.Status) error {
}
// Create the ActivityStreams Announce.
- announce, err := f.converter.BoostToAS(
- ctx,
- boost,
- boost.Account,
- boost.BoostOfAccount,
- )
+ announce, err := f.converter.BoostToAS(ctx, boost)
if err != nil {
return gtserror.Newf("error converting boost to AS: %w", err)
}
@@ -1104,7 +1094,7 @@ func (f *federate) MoveAccount(ctx context.Context, account *gtsmodel.Account) e
ap.AppendTo(move, followersIRI)
// Address the move CC public.
- ap.AppendCc(move, ap.PublicURI())
+ ap.AppendCc(move, ap.PublicIRI())
// Send the Move via the Actor's outbox.
if _, err := f.FederatingActor().Send(
diff --git a/internal/processing/workers/fromclientapi.go b/internal/processing/workers/fromclientapi.go
index 22e7780f6..9cdbcc548 100644
--- a/internal/processing/workers/fromclientapi.go
+++ b/internal/processing/workers/fromclientapi.go
@@ -287,7 +287,7 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA
// and/or notify the account that's being
// interacted with (if it's local): they can
// approve or deny the interaction later.
- if err := p.utils.requestReply(ctx, status); err != nil {
+ if err := p.utils.impoliteReplyRequest(ctx, status); err != nil {
return gtserror.Newf("error pending reply: %w", err)
}
@@ -310,19 +310,22 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA
// URI attached.
// Store an already-accepted interaction request.
- id := id.NewULID()
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: status.InReplyToID,
- TargetAccountID: status.InReplyToAccountID,
- TargetAccount: status.InReplyToAccount,
- InteractingAccountID: status.AccountID,
- InteractingAccount: status.Account,
- InteractionURI: status.URI,
- InteractionType: gtsmodel.InteractionReply,
- Reply: status,
- URI: uris.GenerateURIForAccept(status.InReplyToAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: status.InReplyToID,
+ TargetAccountID: status.InReplyToAccountID,
+ TargetAccount: status.InReplyToAccount,
+ InteractingAccountID: status.AccountID,
+ InteractingAccount: status.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(status.URI, gtsmodel.ReplyRequestSuffix),
+ InteractionURI: status.URI,
+ InteractionType: gtsmodel.InteractionReply,
+ Polite: util.Ptr(false), // TODO: Change this in v0.21.0 when we only send out polite requests.
+ Reply: status,
+ ResponseURI: uris.GenerateURIForAccept(status.InReplyToAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(status.InReplyToAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -331,7 +334,7 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA
// Mark the status as now approved.
status.PendingApproval = util.Ptr(false)
status.PreApproved = false
- status.ApprovedByURI = approval.URI
+ status.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatus(
ctx,
status,
@@ -494,7 +497,7 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI
// and/or notify the account that's being
// interacted with (if it's local): they can
// approve or deny the interaction later.
- if err := p.utils.requestFave(ctx, fave); err != nil {
+ if err := p.utils.impoliteFaveRequest(ctx, fave); err != nil {
return gtserror.Newf("error pending fave: %w", err)
}
@@ -517,19 +520,22 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI
// URI attached.
// Store an already-accepted interaction request.
- id := id.NewULID()
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: fave.StatusID,
- TargetAccountID: fave.TargetAccountID,
- TargetAccount: fave.TargetAccount,
- InteractingAccountID: fave.AccountID,
- InteractingAccount: fave.Account,
- InteractionURI: fave.URI,
- InteractionType: gtsmodel.InteractionLike,
- Like: fave,
- URI: uris.GenerateURIForAccept(fave.TargetAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: fave.StatusID,
+ TargetAccountID: fave.TargetAccountID,
+ TargetAccount: fave.TargetAccount,
+ InteractingAccountID: fave.AccountID,
+ InteractingAccount: fave.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(fave.URI, gtsmodel.LikeRequestSuffix),
+ InteractionURI: fave.URI,
+ InteractionType: gtsmodel.InteractionLike,
+ Polite: util.Ptr(false), // TODO: Change this in v0.21.0 when we only send out polite requests.
+ Like: fave,
+ ResponseURI: uris.GenerateURIForAccept(fave.TargetAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(fave.TargetAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -538,7 +544,7 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI
// Mark the fave itself as now approved.
fave.PendingApproval = util.Ptr(false)
fave.PreApproved = false
- fave.ApprovedByURI = approval.URI
+ fave.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatusFave(
ctx,
fave,
@@ -589,7 +595,7 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien
// and/or notify the account that's being
// interacted with (if it's local): they can
// approve or deny the interaction later.
- if err := p.utils.requestAnnounce(ctx, boost); err != nil {
+ if err := p.utils.impoliteAnnounceRequest(ctx, boost); err != nil {
return gtserror.Newf("error pending boost: %w", err)
}
@@ -612,19 +618,22 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien
// URI attached.
// Store an already-accepted interaction request.
- id := id.NewULID()
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: boost.BoostOfID,
- TargetAccountID: boost.BoostOfAccountID,
- TargetAccount: boost.BoostOfAccount,
- InteractingAccountID: boost.AccountID,
- InteractingAccount: boost.Account,
- InteractionURI: boost.URI,
- InteractionType: gtsmodel.InteractionAnnounce,
- Announce: boost,
- URI: uris.GenerateURIForAccept(boost.BoostOfAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: boost.BoostOfID,
+ TargetAccountID: boost.BoostOfAccountID,
+ TargetAccount: boost.BoostOfAccount,
+ InteractingAccountID: boost.AccountID,
+ InteractingAccount: boost.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(boost.URI, gtsmodel.AnnounceRequestSuffix),
+ InteractionURI: boost.URI,
+ InteractionType: gtsmodel.InteractionAnnounce,
+ Polite: util.Ptr(false), // TODO: Change this in v0.21.0 when we only send out polite requests.
+ Announce: boost,
+ ResponseURI: uris.GenerateURIForAccept(boost.BoostOfAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(boost.BoostOfAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -633,7 +642,7 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien
// Mark the boost itself as now approved.
boost.PendingApproval = util.Ptr(false)
boost.PreApproved = false
- boost.ApprovedByURI = approval.URI
+ boost.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatus(
ctx,
boost,
diff --git a/internal/processing/workers/fromfediapi.go b/internal/processing/workers/fromfediapi.go
index 09c1df480..797a2d9c6 100644
--- a/internal/processing/workers/fromfediapi.go
+++ b/internal/processing/workers/fromfediapi.go
@@ -88,6 +88,10 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
case ap.ObjectNote:
return p.fediAPI.CreateStatus(ctx, fMsg)
+ // REQUEST TO REPLY TO A STATUS
+ case ap.ActivityReplyRequest:
+ return p.fediAPI.CreateReplyRequest(ctx, fMsg)
+
// CREATE FOLLOW (request)
case ap.ActivityFollow:
return p.fediAPI.CreateFollowReq(ctx, fMsg)
@@ -96,10 +100,18 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
case ap.ActivityLike:
return p.fediAPI.CreateLike(ctx, fMsg)
+ // REQUEST TO LIKE A STATUS
+ case ap.ActivityLikeRequest:
+ return p.fediAPI.CreateLikeRequest(ctx, fMsg)
+
// CREATE ANNOUNCE/BOOST
case ap.ActivityAnnounce:
return p.fediAPI.CreateAnnounce(ctx, fMsg)
+ // REQUEST TO BOOST A STATUS
+ case ap.ActivityAnnounceRequest:
+ return p.fediAPI.CreateAnnounceRequest(ctx, fMsg)
+
// CREATE BLOCK
case ap.ActivityBlock:
return p.fediAPI.CreateBlock(ctx, fMsg)
@@ -146,11 +158,15 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
case ap.ObjectNote:
return p.fediAPI.AcceptReply(ctx, fMsg)
+ // ACCEPT (pending) POLITE REPLY REQUEST
+ case ap.ActivityReplyRequest:
+ return p.fediAPI.AcceptPoliteReplyRequest(ctx, fMsg)
+
// ACCEPT (pending) ANNOUNCE
case ap.ActivityAnnounce:
return p.fediAPI.AcceptAnnounce(ctx, fMsg)
- // ACCEPT (remote) REPLY or ANNOUNCE
+ // ACCEPT (remote) IMPOLITE REPLY or ANNOUNCE
case ap.ObjectUnknown:
return p.fediAPI.AcceptRemoteStatus(ctx, fMsg)
}
@@ -219,6 +235,9 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
return gtserror.Newf("unhandled: %s %s", fMsg.APActivityType, fMsg.APObjectType)
}
+// CreateStatus handles the creation of a status/post sent as a Create message.
+// It is also capable of handling impolite reply requests to local + remote statuses,
+// ie., replies sent directly without doing the ReplyRequest process first.
func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI) error {
var (
status *gtsmodel.Status
@@ -291,7 +310,7 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
// preapproved, then just notify the account
// that's being interacted with: they can
// approve or deny the interaction later.
- if err := p.utils.requestReply(ctx, status); err != nil {
+ if err := p.utils.impoliteReplyRequest(ctx, status); err != nil {
return gtserror.Newf("error pending reply: %w", err)
}
@@ -306,20 +325,24 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
// collection. Do the Accept immediately and
// then process everything else as normal.
- // Store an already-accepted interaction request.
- id := id.NewULID()
+ // Store an already-accepted
+ // impolite interaction request.
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: status.InReplyToID,
- TargetAccountID: status.InReplyToAccountID,
- TargetAccount: status.InReplyToAccount,
- InteractingAccountID: status.AccountID,
- InteractingAccount: status.Account,
- InteractionURI: status.URI,
- InteractionType: gtsmodel.InteractionReply,
- Reply: status,
- URI: uris.GenerateURIForAccept(status.InReplyToAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: status.InReplyToID,
+ TargetAccountID: status.InReplyToAccountID,
+ TargetAccount: status.InReplyToAccount,
+ InteractingAccountID: status.AccountID,
+ InteractingAccount: status.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(status.URI, gtsmodel.ReplyRequestSuffix),
+ InteractionURI: status.URI,
+ InteractionType: gtsmodel.InteractionReply,
+ Polite: util.Ptr(false),
+ Reply: status,
+ ResponseURI: uris.GenerateURIForAccept(status.InReplyToAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(status.InReplyToAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -328,7 +351,7 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
// Mark the status as now approved.
status.PendingApproval = util.Ptr(false)
status.PreApproved = false
- status.ApprovedByURI = approval.URI
+ status.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatus(
ctx,
status,
@@ -365,6 +388,118 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
return nil
}
+// CreateReplyRequest handles a polite ReplyRequest.
+// This is distinct from CreateStatus, which is capable
+// of handling both "normal" top-level status creation,
+// in addition to *impolite* reply requests.
+func (p *fediAPI) CreateReplyRequest(ctx context.Context, fMsg *messages.FromFediAPI) error {
+ // Extract the ap model Statusable
+ // set by the federating db.
+ statusable, ok := fMsg.APObject.(ap.Statusable)
+ if !ok {
+ return gtserror.Newf("cannot cast %T -> ap.Statusable", fMsg.APObject)
+ }
+
+ // Call RefreshStatus to parse and process the
+ // statusable. This will also check permissions.
+ replyURI := ap.GetJSONLDId(statusable).String()
+ reply, _, err := p.federate.RefreshStatus(ctx,
+ fMsg.Receiving.Username,
+ &gtsmodel.Status{
+ URI: replyURI,
+ Local: util.Ptr(false),
+ },
+ statusable,
+ // Force refresh within 5min window.
+ dereferencing.Fresh,
+ )
+
+ switch {
+ case err == nil:
+ // All fine.
+
+ case gtserror.IsNotPermitted(err):
+ // Reply is straight up not permitted by
+ // the interaction policy of the status
+ // it's replying to. Nothing more to do.
+ log.Debugf(ctx,
+ "dropping unpermitted ReplyRequest with instrument %s",
+ replyURI,
+ )
+ return nil
+
+ default:
+ // There's some real error.
+ return gtserror.Newf(
+ "error processing ReplyRequest with instrument %s: %w",
+ replyURI, err,
+ )
+ }
+
+ // The reply is permitted. Check if we
+ // should send out an Accept immediately.
+ manualApproval := *reply.PendingApproval && !reply.PreApproved
+ if manualApproval {
+ // The reply requires manual approval.
+ //
+ // Just notify target account about
+ // the requested interaction.
+ if err := p.surface.notifyPendingReply(ctx, reply); err != nil {
+ return gtserror.Newf("error notifying pending reply: %w", err)
+ }
+
+ return nil
+ }
+
+ // The reply is automatically approved,
+ // handle side effects of this.
+ req, ok := fMsg.GTSModel.(*gtsmodel.InteractionRequest)
+ if !ok {
+ return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", fMsg.GTSModel)
+ }
+
+ // Mark the request as accepted.
+ req.AcceptedAt = time.Now()
+ req.ResponseURI = uris.GenerateURIForAccept(
+ req.TargetAccount.Username, req.ID,
+ )
+ req.AuthorizationURI = uris.GenerateURIForAuthorization(
+ req.TargetAccount.Username, req.ID,
+ )
+
+ // Update in the db.
+ if err := p.state.DB.UpdateInteractionRequest(
+ ctx,
+ req,
+ "accepted_at",
+ "response_uri",
+ "authorization_uri",
+ ); err != nil {
+ return gtserror.Newf("db error updating interaction request: %w", err)
+ }
+
+ // Send out the accept.
+ if err := p.federate.AcceptInteraction(ctx, req); err != nil {
+ log.Errorf(ctx, "error federating accept: %v", err)
+ }
+
+ // Update stats for the replying account.
+ if err := p.utils.incrementStatusesCount(ctx, fMsg.Requesting, reply); err != nil {
+ log.Errorf(ctx, "error updating account stats: %v", err)
+ }
+
+ // Timeline the reply + notify recipient(s).
+ if err := p.surface.timelineAndNotifyStatus(ctx, reply); err != nil {
+ log.Errorf(ctx, "error timelining and notifying status: %v", err)
+ }
+
+ // Interaction counts changed on the replied status;
+ // uncache the prepared version from all timelines.
+ p.surface.invalidateStatusFromTimelines(reply.InReplyToID)
+
+ return nil
+}
+
func (p *fediAPI) CreatePollVote(ctx context.Context, fMsg *messages.FromFediAPI) error {
// Cast poll vote type from the worker message.
vote, ok := fMsg.GTSModel.(*gtsmodel.PollVote)
@@ -430,18 +565,18 @@ func (p *fediAPI) UpdatePollVote(ctx context.Context, fMsg *messages.FromFediAPI
}
// Get the origin status.
- status := vote.Poll.Status
+ reply := vote.Poll.Status
- if *status.Local {
+ if *reply.Local {
// These were poll votes in a local status, we need to
// federate the updated status model with latest vote counts.
- if err := p.federate.UpdateStatus(ctx, status); err != nil {
+ if err := p.federate.UpdateStatus(ctx, reply); err != nil {
log.Errorf(ctx, "error federating status update: %v", err)
}
}
// Interaction counts changed, uncache from timelines.
- p.surface.invalidateStatusFromTimelines(status.ID)
+ p.surface.invalidateStatusFromTimelines(reply.ID)
return nil
}
@@ -503,6 +638,8 @@ func (p *fediAPI) CreateFollowReq(ctx context.Context, fMsg *messages.FromFediAP
return nil
}
+// CreateLike handles an impolite Like, ie., a Like sent directly.
+// This is different from the CreateLikeRequest function, which handles polite LikeRequests.
func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) error {
fave, ok := fMsg.GTSModel.(*gtsmodel.StatusFave)
if !ok {
@@ -525,7 +662,7 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
// preapproved, then just notify the account
// that's being interacted with: they can
// approve or deny the interaction later.
- if err := p.utils.requestFave(ctx, fave); err != nil {
+ if err := p.utils.impoliteFaveRequest(ctx, fave); err != nil {
return gtserror.Newf("error pending fave: %w", err)
}
@@ -540,20 +677,24 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
// collection. Do the Accept immediately and
// then process everything else as normal.
- // Store an already-accepted interaction request.
- id := id.NewULID()
+ // Store an already-accepted
+ // impolite interaction request.
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: fave.StatusID,
- TargetAccountID: fave.TargetAccountID,
- TargetAccount: fave.TargetAccount,
- InteractingAccountID: fave.AccountID,
- InteractingAccount: fave.Account,
- InteractionURI: fave.URI,
- InteractionType: gtsmodel.InteractionLike,
- Like: fave,
- URI: uris.GenerateURIForAccept(fave.TargetAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: fave.StatusID,
+ TargetAccountID: fave.TargetAccountID,
+ TargetAccount: fave.TargetAccount,
+ InteractingAccountID: fave.AccountID,
+ InteractingAccount: fave.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(fave.URI, gtsmodel.LikeRequestSuffix),
+ InteractionURI: fave.URI,
+ InteractionType: gtsmodel.InteractionLike,
+ Polite: util.Ptr(false),
+ Like: fave,
+ ResponseURI: uris.GenerateURIForAccept(fave.TargetAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(fave.TargetAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -562,7 +703,7 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
// Mark the fave itself as now approved.
fave.PendingApproval = util.Ptr(false)
fave.PreApproved = false
- fave.ApprovedByURI = approval.URI
+ fave.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatusFave(
ctx,
fave,
@@ -591,6 +732,87 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
return nil
}
+// CreateLikeRequest handles a polite LikeRequest, as
+// opposed to CreateLike, which handles *impolite* like
+// requests (ie., Likes sent directly).
+func (p *fediAPI) CreateLikeRequest(ctx context.Context, fMsg *messages.FromFediAPI) error {
+ req, ok := fMsg.GTSModel.(*gtsmodel.InteractionRequest)
+ if !ok {
+ return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", fMsg.GTSModel)
+ }
+
+ // At this point the not-yet-approved
+ // interaction request, and the pending
+ // fave, are both in the database.
+
+ if !req.Like.PreApproved {
+ // The fave is *not* pre-approved, and
+ // therefore requires manual approval.
+ //
+ // Just notify target account about
+ // the requested interaction.
+ if err := p.surface.notifyPendingFave(ctx, req.Like); err != nil {
+ return gtserror.Newf("error notifying pending like: %w", err)
+ }
+
+ return nil
+ }
+
+ // If it's pre-approved on the other hand
+ // we can handle everything immediately.
+
+ // Mark the request as accepted.
+ req.AcceptedAt = time.Now()
+ req.ResponseURI = uris.GenerateURIForAccept(
+ req.TargetAccount.Username, req.ID,
+ )
+ req.AuthorizationURI = uris.GenerateURIForAuthorization(
+ req.TargetAccount.Username, req.ID,
+ )
+
+ // Update in the db.
+ if err := p.state.DB.UpdateInteractionRequest(
+ ctx,
+ req,
+ "accepted_at",
+ "response_uri",
+ "authorization_uri",
+ ); err != nil {
+ return gtserror.Newf("db error updating interaction request: %w", err)
+ }
+
+ // Send out the accept.
+ if err := p.federate.AcceptInteraction(ctx, req); err != nil {
+ log.Errorf(ctx, "error federating accept: %v", err)
+ }
+
+ // Mark the fave as approved.
+ req.Like.PendingApproval = util.Ptr(false)
+ req.Like.ApprovedByURI = req.AuthorizationURI
+ req.Like.PreApproved = false
+
+ // Update in the db.
+ if err := p.state.DB.UpdateStatusFave(
+ ctx,
+ req.Like,
+ "pending_approval",
+ "approved_by_uri",
+ ); err != nil {
+ return gtserror.Newf("db error updating status fave: %w", err)
+ }
+
+ // Notify the faved account.
+ if err := p.surface.notifyFave(ctx, req.Like); err != nil {
+ log.Errorf(ctx, "error notifying fave: %v", err)
+ }
+
+ // Interaction counts changed on the faved status;
+ // uncache the prepared version from all timelines.
+ p.surface.invalidateStatusFromTimelines(req.Like.StatusID)
+
+ return nil
+}
+
func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI) error {
boost, ok := fMsg.GTSModel.(*gtsmodel.Status)
if !ok {
@@ -610,7 +832,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
)
if err != nil {
if gtserror.IsUnretrievable(err) ||
- gtserror.NotPermitted(err) {
+ gtserror.IsNotPermitted(err) {
// Boosted status domain blocked, or
// otherwise not permitted, nothing to do.
log.Debugf(ctx, "skipping announce: %v", err)
@@ -632,7 +854,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
// preapproved, then just notify the account
// that's being interacted with: they can
// approve or deny the interaction later.
- if err := p.utils.requestAnnounce(ctx, boost); err != nil {
+ if err := p.utils.impoliteAnnounceRequest(ctx, boost); err != nil {
return gtserror.Newf("error pending boost: %w", err)
}
@@ -647,20 +869,24 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
// collection. Do the Accept immediately and
// then process everything else as normal.
- // Store an already-accepted interaction request.
- id := id.NewULID()
+ // Store an already-accepted
+ // impolite interaction request.
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: boost.BoostOfID,
- TargetAccountID: boost.BoostOfAccountID,
- TargetAccount: boost.BoostOfAccount,
- InteractingAccountID: boost.AccountID,
- InteractingAccount: boost.Account,
- InteractionURI: boost.URI,
- InteractionType: gtsmodel.InteractionAnnounce,
- Announce: boost,
- URI: uris.GenerateURIForAccept(boost.BoostOfAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: boost.BoostOfID,
+ TargetAccountID: boost.BoostOfAccountID,
+ TargetAccount: boost.BoostOfAccount,
+ InteractingAccountID: boost.AccountID,
+ InteractingAccount: boost.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(boost.URI, gtsmodel.AnnounceRequestSuffix),
+ InteractionURI: boost.URI,
+ InteractionType: gtsmodel.InteractionAnnounce,
+ Polite: util.Ptr(false),
+ Announce: boost,
+ ResponseURI: uris.GenerateURIForAccept(boost.BoostOfAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(boost.BoostOfAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -669,7 +895,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
// Mark the boost itself as now approved.
boost.PendingApproval = util.Ptr(false)
boost.PreApproved = false
- boost.ApprovedByURI = approval.URI
+ boost.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatus(
ctx,
boost,
@@ -708,6 +934,103 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
return nil
}
+func (p *fediAPI) CreateAnnounceRequest(ctx context.Context, fMsg *messages.FromFediAPI) error {
+ req, ok := fMsg.GTSModel.(*gtsmodel.InteractionRequest)
+ if !ok {
+ return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", fMsg.GTSModel)
+ }
+
+ // At this point the not-yet-handled interaction req
+ // is in the database, but the announce isn't yet.
+ //
+ // We can check permissions for the announce *and*
+ // put it in the db (if acceptable) by doing Enrich.
+ boost, err := p.federate.EnrichAnnounce(
+ ctx,
+ req.Announce,
+ fMsg.Receiving.Username,
+ )
+
+ switch {
+ case err == nil:
+ // All fine.
+
+ case gtserror.IsNotPermitted(err):
+ // Announce is straight up not permitted
+ // by the interaction policy of the status
+ // it's targeting. Nothing more to do.
+ log.Debugf(ctx,
+ "dropping unpermitted AnnounceRequest with instrument %s",
+ req.Announce.URI,
+ )
+ return nil
+
+ default:
+ // There's some real error.
+ return gtserror.Newf(
+ "error processing AnnounceRequest with instrument %s: %w",
+ req.Announce.URI, err,
+ )
+ }
+
+ // The announce is permitted. Check if we
+ // should send out an Accept immediately.
+ manualApproval := *boost.PendingApproval && !boost.PreApproved
+ if manualApproval {
+ // The announce requires manual approval.
+ //
+ // Just notify target account about
+ // the requested interaction.
+ if err := p.surface.notifyPendingAnnounce(ctx, boost); err != nil {
+ return gtserror.Newf("error notifying pending announce: %w", err)
+ }
+
+ return nil
+ }
+
+ // The announce is automatically approved,
+ // mark the request as accepted.
+ req.AcceptedAt = time.Now()
+ req.ResponseURI = uris.GenerateURIForAccept(
+ req.TargetAccount.Username, req.ID,
+ )
+ req.AuthorizationURI = uris.GenerateURIForAuthorization(
+ req.TargetAccount.Username, req.ID,
+ )
+
+ // Update in the db.
+ if err := p.state.DB.UpdateInteractionRequest(
+ ctx,
+ req,
+ "accepted_at",
+ "response_uri",
+ "authorization_uri",
+ ); err != nil {
+ return gtserror.Newf("db error updating interaction request: %w", err)
+ }
+
+ // Send out the accept.
+ if err := p.federate.AcceptInteraction(ctx, req); err != nil {
+ log.Errorf(ctx, "error federating accept: %v", err)
+ }
+
+ // Update stats for the boosting account.
+ if err := p.utils.incrementStatusesCount(ctx, fMsg.Requesting, boost); err != nil {
+ log.Errorf(ctx, "error updating account stats: %v", err)
+ }
+
+ // Timeline the boost + notify recipient(s).
+ if err := p.surface.timelineAndNotifyStatus(ctx, boost); err != nil {
+ log.Errorf(ctx, "error timelining and notifying status: %v", err)
+ }
+
+ // Interaction counts changed on the boosted status;
+ // uncache the prepared version from all timelines.
+ p.surface.invalidateStatusFromTimelines(boost.BoostOfID)
+
+ return nil
+}
+
func (p *fediAPI) CreateBlock(ctx context.Context, fMsg *messages.FromFediAPI) error {
block, ok := fMsg.GTSModel.(*gtsmodel.Block)
if !ok {
@@ -842,29 +1165,29 @@ func (p *fediAPI) AcceptLike(ctx context.Context, fMsg *messages.FromFediAPI) er
}
func (p *fediAPI) AcceptReply(ctx context.Context, fMsg *messages.FromFediAPI) error {
- status, ok := fMsg.GTSModel.(*gtsmodel.Status)
+ reply, ok := fMsg.GTSModel.(*gtsmodel.Status)
if !ok {
return gtserror.Newf("%T not parseable as *gtsmodel.Status", fMsg.GTSModel)
}
// Update stats for the actor account.
- if err := p.utils.incrementStatusesCount(ctx, status.Account, status); err != nil {
+ if err := p.utils.incrementStatusesCount(ctx, reply.Account, reply); err != nil {
log.Errorf(ctx, "error updating account stats: %v", err)
}
// Timeline and notify the status.
- if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
+ if err := p.surface.timelineAndNotifyStatus(ctx, reply); err != nil {
log.Errorf(ctx, "error timelining and notifying status: %v", err)
}
// Send out the reply again, fully this time.
- if err := p.federate.CreateStatus(ctx, status); err != nil {
+ if err := p.federate.CreateStatus(ctx, reply); err != nil {
log.Errorf(ctx, "error federating announce: %v", err)
}
// Interaction counts changed on the replied-to status;
// uncache the prepared version from all timelines.
- p.surface.invalidateStatusFromTimelines(status.InReplyToID)
+ p.surface.invalidateStatusFromTimelines(reply.InReplyToID)
return nil
}
@@ -893,9 +1216,9 @@ func (p *fediAPI) AcceptRemoteStatus(ctx context.Context, fMsg *messages.FromFed
// barebones status and insert it into the database,
// if indeed it's actually a status URI we can fetch.
//
- // This will also check whether the given AcceptIRI
+ // This will also check whether the given approvedByURI
// actually grants permission for this status.
- status, _, err := p.federate.RefreshStatus(ctx,
+ reply, _, err := p.federate.RefreshStatus(ctx,
fMsg.Receiving.Username,
bareStatus,
nil, nil,
@@ -906,20 +1229,70 @@ func (p *fediAPI) AcceptRemoteStatus(ctx context.Context, fMsg *messages.FromFed
// No error means it was indeed a remote status, and the
// given approvedByURI permitted it. Timeline and notify it.
- if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
+ if err := p.surface.timelineAndNotifyStatus(ctx, reply); err != nil {
log.Errorf(ctx, "error timelining and notifying status: %v", err)
}
// Interaction counts changed on the interacted status;
// uncache the prepared version from all timelines.
- if status.InReplyToID != "" {
- p.surface.invalidateStatusFromTimelines(status.InReplyToID)
+ if reply.InReplyToID != "" {
+ p.surface.invalidateStatusFromTimelines(reply.InReplyToID)
+ }
+
+ if reply.BoostOfID != "" {
+ p.surface.invalidateStatusFromTimelines(reply.BoostOfID)
+ }
+
+ return nil
+}
+
+func (p *fediAPI) AcceptPoliteReplyRequest(ctx context.Context, fMsg *messages.FromFediAPI) error {
+ if util.IsNil(fMsg.GTSModel) {
+ // If the interaction request is nil, this
+ // must be an accept of a remote ReplyRequest
+ // not targeting one of our statuses.
+ //
+ // Just pass it to the AcceptRemoteStatus
+ // func to do dereferencing + side effects.
+ log.Debug(ctx, "accepting remote ReplyRequest for remote reply")
+ return p.AcceptRemoteStatus(ctx, fMsg)
+ }
+
+ // If the interaction request is not nil, this will
+ // be an accept of one of our replies to a remote.
+ //
+ // Since the int req + reply have already been updated
+ // in the federatingDB, we just need to do side effects.
+ intReq, ok := fMsg.GTSModel.(*gtsmodel.InteractionRequest)
+ if !ok {
+ return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", fMsg.GTSModel)
}
- if status.BoostOfID != "" {
- p.surface.invalidateStatusFromTimelines(status.BoostOfID)
+ // Ensure reply populated.
+ reply := intReq.Reply
+ if err := p.state.DB.PopulateStatus(ctx, reply); err != nil {
+ return gtserror.Newf("error populating status: %w", err)
}
+ // Update stats for the actor account.
+ if err := p.utils.incrementStatusesCount(ctx, reply.Account, reply); err != nil {
+ log.Errorf(ctx, "error updating account stats: %v", err)
+ }
+
+ // Timeline and notify the status.
+ if err := p.surface.timelineAndNotifyStatus(ctx, reply); err != nil {
+ log.Errorf(ctx, "error timelining and notifying status: %v", err)
+ }
+
+ // Send out the reply with approval attached.
+ if err := p.federate.CreateStatus(ctx, reply); err != nil {
+ log.Errorf(ctx, "error federating announce: %v", err)
+ }
+
+ // Interaction counts changed on the replied-to status;
+ // uncache the prepared version from all timelines.
+ p.surface.invalidateStatusFromTimelines(reply.InReplyToID)
+
return nil
}
@@ -1169,7 +1542,7 @@ func (p *fediAPI) RejectReply(ctx context.Context, fMsg *messages.FromFediAPI) e
// be in the database, we just need to do side effects.
// Get the rejected status.
- status, err := p.state.DB.GetStatusByURI(
+ reply, err := p.state.DB.GetStatusByURI(
gtscontext.SetBarebones(ctx),
req.InteractionURI,
)
@@ -1189,7 +1562,7 @@ func (p *fediAPI) RejectReply(ctx context.Context, fMsg *messages.FromFediAPI) e
// Perform the actual status deletion.
if err := p.utils.wipeStatus(
ctx,
- status,
+ reply,
deleteAttachments,
copyToSinBin,
); err != nil {
diff --git a/internal/processing/workers/fromfediapi_test.go b/internal/processing/workers/fromfediapi_test.go
index 7811e9f3d..790c78b70 100644
--- a/internal/processing/workers/fromfediapi_test.go
+++ b/internal/processing/workers/fromfediapi_test.go
@@ -18,6 +18,7 @@
package workers_test
import (
+ "bytes"
"context"
"encoding/json"
"errors"
@@ -26,11 +27,13 @@ import (
"testing"
"time"
+ "code.superseriousbusiness.org/activity/streams/vocab"
"code.superseriousbusiness.org/gotosocial/internal/ap"
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
"code.superseriousbusiness.org/gotosocial/internal/db"
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
+ "code.superseriousbusiness.org/gotosocial/internal/id"
"code.superseriousbusiness.org/gotosocial/internal/messages"
"code.superseriousbusiness.org/gotosocial/internal/stream"
"code.superseriousbusiness.org/gotosocial/internal/util"
@@ -781,6 +784,123 @@ func (suite *FromFediAPITestSuite) TestUpdateNote() {
}
}
+func (suite *FromFediAPITestSuite) TestCreateReplyRequest() {
+ var (
+ ctx = suite.T().Context()
+ testStructs = testrig.SetupTestStructs(rMediaPath, rTemplatePath)
+ requesting = suite.testAccounts["remote_account_1"]
+ receiving = suite.testAccounts["admin_account"]
+ testStatus = suite.testStatuses["admin_account_status_1"]
+ intReqURI = "http://fossbros-anonymous.io/requests/87fb1478-ac46-406a-8463-96ce05645219"
+ intURI = "http://fossbros-anonymous.io/users/foss_satan/statuses/87fb1478-ac46-406a-8463-96ce05645219"
+ jsonStr = `{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://gotosocial.org/ns",
+ {
+ "sensitive": "as:sensitive"
+ }
+ ],
+ "type": "ReplyRequest",
+ "id": "` + intReqURI + `",
+ "actor": "` + requesting.URI + `",
+ "object": "` + testStatus.URI + `",
+ "to": "` + receiving.URI + `",
+ "instrument": {
+ "attributedTo": "` + requesting.URI + `",
+ "cc": "` + requesting.FollowersURI + `",
+ "content": "\u003cp\u003ethis is a reply!\u003c/p\u003e",
+ "id": "` + intURI + `",
+ "inReplyTo": "` + testStatus.URI + `",
+ "tag": {
+ "href": "` + receiving.URI + `",
+ "name": "@` + receiving.Username + `@localhost:8080",
+ "type": "Mention"
+ },
+ "to": "https://www.w3.org/ns/activitystreams#Public",
+ "type": "Note"
+ }
+}`
+ )
+ defer testrig.TearDownTestStructs(testStructs)
+
+ suite.T().Logf("testing reply request:\n\n%s", jsonStr)
+
+ // Decode the reply request + embedded statusable.
+ t, err := ap.DecodeType(ctx, io.NopCloser(bytes.NewBufferString(jsonStr)))
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+ replyReq := t.(vocab.GoToSocialReplyRequest)
+ statusable := replyReq.GetActivityStreamsInstrument().At(0).GetActivityStreamsNote().(ap.Statusable)
+
+ // Create a pending interaction request in the
+ // database, as though the reply req had already
+ // passed through the federatingdb function.
+ intReq := &gtsmodel.InteractionRequest{
+ ID: id.NewULID(),
+ TargetStatusID: testStatus.ID,
+ TargetStatus: testStatus,
+ TargetAccountID: receiving.ID,
+ TargetAccount: receiving,
+ InteractingAccountID: requesting.ID,
+ InteractingAccount: requesting,
+ InteractionRequestURI: intReqURI,
+ InteractionURI: ap.GetJSONLDId(statusable).String(),
+ InteractionType: gtsmodel.InteractionReply,
+ Polite: util.Ptr(true),
+ Reply: nil, // Not settable yet.
+ }
+ if err := testStructs.State.DB.PutInteractionRequest(ctx, intReq); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // Process the message.
+ if err = testStructs.Processor.Workers().ProcessFromFediAPI(
+ ctx,
+ &messages.FromFediAPI{
+ APObjectType: ap.ActivityReplyRequest,
+ APActivityType: ap.ActivityCreate,
+ GTSModel: intReq,
+ APObject: statusable,
+ Receiving: receiving,
+ Requesting: requesting,
+ },
+ ); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // The interaction request should be accepted.
+ intReq, err = testStructs.State.DB.GetInteractionRequestByID(ctx, intReq.ID)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+ suite.WithinDuration(time.Now(), intReq.AcceptedAt, 1*time.Minute)
+ suite.NotEmpty(intReq.AuthorizationURI)
+ suite.NotEmpty(intReq.ResponseURI)
+
+ // Federator should send out an Accept that looks something like:
+ //
+ // {
+ // "@context": [
+ // "https://gotosocial.org/ns",
+ // "https://www.w3.org/ns/activitystreams"
+ // ],
+ // "actor": "http://localhost:8080/users/admin",
+ // "id": "http://localhost:8080/users/admin/accepts/01K2CV90660VRPZM39R35NMSG9",
+ // "object": {
+ // "actor": "http://fossbros-anonymous.io/users/foss_satan",
+ // "id": "http://fossbros-anonymous.io/requests/87fb1478-ac46-406a-8463-96ce05645219",
+ // "instrument": "http://fossbros-anonymous.io/users/foss_satan/statuses/87fb1478-ac46-406a-8463-96ce05645219",
+ // "object": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
+ // "type": "ReplyRequest"
+ // },
+ // "result": "http://localhost:8080/users/admin/authorizations/01K2CV90660VRPZM39R35NMSG9",
+ // "to": "http://fossbros-anonymous.io/users/foss_satan",
+ // "type": "Accept"
+ // }
+}
+
func TestFromFederatorTestSuite(t *testing.T) {
suite.Run(t, &FromFediAPITestSuite{})
}
diff --git a/internal/processing/workers/util.go b/internal/processing/workers/util.go
index 3c17eaaf5..6382887eb 100644
--- a/internal/processing/workers/util.go
+++ b/internal/processing/workers/util.go
@@ -526,9 +526,13 @@ func (u *utils) decrementFollowRequestsCount(
return nil
}
-// requestFave stores an interaction request
+// impoliteFaveRequest stores an interaction request
// for the given fave, and notifies the interactee.
-func (u *utils) requestFave(
+//
+// It should be used only when an actor has sent a Like
+// directly in response to a post that requires approval
+// for it, instead of sending a LikeRequest.
+func (u *utils) impoliteFaveRequest(
ctx context.Context,
fave *gtsmodel.StatusFave,
) error {
@@ -555,8 +559,8 @@ func (u *utils) requestFave(
return nil
}
- // Create + store new interaction request.
- req = typeutils.StatusFaveToInteractionRequest(fave)
+ // Create + store new impolite interaction request.
+ req = typeutils.StatusFaveToImpoliteInteractionRequest(fave)
if err := u.state.DB.PutInteractionRequest(ctx, req); err != nil {
return gtserror.Newf("db error storing interaction request: %w", err)
}
@@ -569,9 +573,13 @@ func (u *utils) requestFave(
return nil
}
-// requestReply stores an interaction request
+// impoliteReplyRequest stores an interaction request
// for the given reply, and notifies the interactee.
-func (u *utils) requestReply(
+//
+// It should be used only when an actor has sent a reply
+// directly in response to a post that requires approval
+// for it, instead of sending a ReplyRequest.
+func (u *utils) impoliteReplyRequest(
ctx context.Context,
reply *gtsmodel.Status,
) error {
@@ -598,8 +606,8 @@ func (u *utils) requestReply(
return nil
}
- // Create + store interaction request.
- req = typeutils.StatusToInteractionRequest(reply)
+ // Create + store impolite interaction request.
+ req = typeutils.StatusToImpoliteInteractionRequest(reply)
if err := u.state.DB.PutInteractionRequest(ctx, req); err != nil {
return gtserror.Newf("db error storing interaction request: %w", err)
}
@@ -612,9 +620,13 @@ func (u *utils) requestReply(
return nil
}
-// requestAnnounce stores an interaction request
+// impoliteAnnounceRequest stores an interaction request
// for the given announce, and notifies the interactee.
-func (u *utils) requestAnnounce(
+//
+// It should be used only when an actor has sent an Announce
+// directly in response to a post that requires approval
+// for it, instead of sending an AnnounceRequest.
+func (u *utils) impoliteAnnounceRequest(
ctx context.Context,
boost *gtsmodel.Status,
) error {
@@ -641,8 +653,8 @@ func (u *utils) requestAnnounce(
return nil
}
- // Create + store interaction request.
- req = typeutils.StatusToInteractionRequest(boost)
+ // Create + store impolite interaction request.
+ req = typeutils.StatusToImpoliteInteractionRequest(boost)
if err := u.state.DB.PutInteractionRequest(ctx, req); err != nil {
return gtserror.Newf("db error storing interaction request: %w", err)
}