// GoToSocial // Copyright (C) GoToSocial Authors admin@gotosocial.org // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package interactionrequests import ( "context" "time" "github.com/superseriousbusiness/gotosocial/internal/ap" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/uris" "github.com/superseriousbusiness/gotosocial/internal/util" ) // Accept accepts an interaction request with the given ID, // on behalf of the given account (whose post it must target). func (p *Processor) Accept( ctx context.Context, acct *gtsmodel.Account, reqID string, ) (*apimodel.InteractionRequest, gtserror.WithCode) { req, err := p.state.DB.GetInteractionRequestByID(ctx, reqID) if err != nil { err := gtserror.Newf("db error getting interaction request: %w", err) return nil, gtserror.NewErrorInternalError(err) } if req.TargetAccountID != acct.ID { err := gtserror.Newf( "interaction request %s does not belong to account %s", reqID, acct.ID, ) return nil, gtserror.NewErrorNotFound(err) } if !req.IsPending() { err := gtserror.Newf( "interaction request %s has already been handled", reqID, ) return nil, gtserror.NewErrorNotFound(err) } // Lock on the interaction req URI to // ensure nobody else is modifying it rn. unlock := p.state.ProcessingLocks.Lock(req.InteractionURI) defer unlock() // Mark the request as accepted // and generate a URI for it. req.AcceptedAt = time.Now() req.URI = uris.GenerateURIForAccept(acct.Username, req.ID) if err := p.state.DB.UpdateInteractionRequest( ctx, req, "accepted_at", "uri", ); err != nil { err := gtserror.Newf("db error updating interaction request: %w", err) return nil, gtserror.NewErrorInternalError(err) } switch req.InteractionType { case gtsmodel.InteractionLike: if errWithCode := p.acceptLike(ctx, req); errWithCode != nil { return nil, errWithCode } case gtsmodel.InteractionReply: if errWithCode := p.acceptReply(ctx, req); errWithCode != nil { return nil, errWithCode } case gtsmodel.InteractionAnnounce: if errWithCode := p.acceptAnnounce(ctx, req); errWithCode != nil { return nil, errWithCode } default: err := gtserror.Newf("unknown interaction type for interaction request %s", reqID) return nil, gtserror.NewErrorInternalError(err) } // Return the now-accepted req to the caller so // they can do something with it if they need to. apiReq, err := p.converter.InteractionReqToAPIInteractionReq( ctx, req, acct, ) if err != nil { err := gtserror.Newf("error converting interaction request: %w", err) return nil, gtserror.NewErrorInternalError(err) } return apiReq, nil } // Package-internal convenience // function to accept a like. func (p *Processor) acceptLike( ctx context.Context, req *gtsmodel.InteractionRequest, ) gtserror.WithCode { // If the Like is missing, that means it's // probably already been undone by someone, // so there's nothing to actually accept. if req.Like == nil { err := gtserror.Newf("no Like found for interaction request %s", req.ID) return gtserror.NewErrorNotFound(err) } // Update the Like. req.Like.PendingApproval = util.Ptr(false) req.Like.PreApproved = false req.Like.ApprovedByURI = req.URI if err := p.state.DB.UpdateStatusFave( ctx, req.Like, "pending_approval", "approved_by_uri", ); err != nil { err := gtserror.Newf("db error updating status fave: %w", err) return gtserror.NewErrorInternalError(err) } // Send the accepted request off through the // client API processor to handle side effects. p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ APObjectType: ap.ActivityLike, APActivityType: ap.ActivityAccept, GTSModel: req, Origin: req.TargetAccount, Target: req.InteractingAccount, }) return nil } // Package-internal convenience // function to accept a reply. func (p *Processor) acceptReply( ctx context.Context, req *gtsmodel.InteractionRequest, ) gtserror.WithCode { // If the Reply is missing, that means it's // probably already been undone by someone, // so there's nothing to actually accept. if req.Reply == nil { err := gtserror.Newf("no Reply found for interaction request %s", req.ID) return gtserror.NewErrorNotFound(err) } // Update the Reply. req.Reply.PendingApproval = util.Ptr(false) req.Reply.PreApproved = false req.Reply.ApprovedByURI = req.URI if err := p.state.DB.UpdateStatus( ctx, req.Reply, "pending_approval", "approved_by_uri", ); err != nil { err := gtserror.Newf("db error updating status reply: %w", err) return gtserror.NewErrorInternalError(err) } // Send the accepted request off through the // client API processor to handle side effects. p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ APObjectType: ap.ObjectNote, APActivityType: ap.ActivityAccept, GTSModel: req, Origin: req.TargetAccount, Target: req.InteractingAccount, }) return nil } // Package-internal convenience // function to accept an announce. func (p *Processor) acceptAnnounce( ctx context.Context, req *gtsmodel.InteractionRequest, ) gtserror.WithCode { // If the Announce is missing, that means it's // probably already been undone by someone, // so there's nothing to actually accept. if req.Reply == nil { err := gtserror.Newf("no Announce found for interaction request %s", req.ID) return gtserror.NewErrorNotFound(err) } // Update the Announce. req.Announce.PendingApproval = util.Ptr(false) req.Announce.PreApproved = false req.Announce.ApprovedByURI = req.URI if err := p.state.DB.UpdateStatus( ctx, req.Announce, "pending_approval", "approved_by_uri", ); err != nil { err := gtserror.Newf("db error updating status announce: %w", err) return gtserror.NewErrorInternalError(err) } // Send the accepted request off through the // client API processor to handle side effects. p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ APObjectType: ap.ActivityAnnounce, APActivityType: ap.ActivityAccept, GTSModel: req, Origin: req.TargetAccount, Target: req.InteractingAccount, }) return nil }