mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-29 17:52:24 -05:00
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394)
~~Still WIP!~~ This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0. Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them. As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type. Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation). Migrations tested on both Postgres and SQLite. Co-authored-by: kim <grufwub@gmail.com> Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
This commit is contained in:
parent
33fed81a8d
commit
754b7be9cf
126 changed files with 6637 additions and 1778 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 := >smodel.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 := >smodel.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 := >smodel.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,
|
||||
|
|
|
|||
|
|
@ -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 := >smodel.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,
|
||||
>smodel.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 := >smodel.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 := >smodel.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,23 +1229,73 @@ 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 status.BoostOfID != "" {
|
||||
p.surface.invalidateStatusFromTimelines(status.BoostOfID)
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (p *fediAPI) AcceptAnnounce(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
boost, ok := fMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 := >smodel.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{})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue