mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 12:42:25 -05:00 
			
		
		
		
	start messing about with worker functions etc
This commit is contained in:
		
					parent
					
						
							
								b6ff55662e
							
						
					
				
			
			
				commit
				
					
						060ef4149b
					
				
			
		
					 15 changed files with 649 additions and 119 deletions
				
			
		|  | @ -102,13 +102,23 @@ const ( | ||||||
| 
 | 
 | ||||||
| 	/* GtS stuff */ | 	/* GtS stuff */ | ||||||
| 
 | 
 | ||||||
| 	ObjectLikeApproval     = "LikeApproval" | 	ObjectLikeAuthorization     = "LikeAuthorization" | ||||||
| 	ObjectReplyApproval    = "ReplyApproval" | 	ObjectReplyAuthorization    = "ReplyAuthorization" | ||||||
| 	ObjectAnnounceApproval = "AnnounceApproval" | 	ObjectAnnounceAuthorization = "AnnounceAuthorization" | ||||||
|  | 
 | ||||||
|  | 	ActivityLikeRequest     = "LikeRequest" | ||||||
|  | 	ActivityReplyRequest    = "ReplyRequest" | ||||||
|  | 	ActivityAnnounceRequest = "AnnounceRequest" | ||||||
| 
 | 
 | ||||||
| 	/* Funkwhale stuff */ | 	/* Funkwhale stuff */ | ||||||
| 
 | 
 | ||||||
| 	ObjectAlbum = "Album" | 	ObjectAlbum = "Album" | ||||||
|  | 
 | ||||||
|  | 	/* Deprecated stuff */ | ||||||
|  | 
 | ||||||
|  | 	ObjectLikeApproval     = "LikeApproval"     // deprecated, use LikeAuthorization. | ||||||
|  | 	ObjectReplyApproval    = "ReplyApproval"    // deprecated, use ReplyAuthorization. | ||||||
|  | 	ObjectAnnounceApproval = "AnnounceApproval" // deprecated, use AnnounceAuthorization. | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // isActivity returns whether AS type name is of an Activity (NOT IntransitiveActivity). | // isActivity returns whether AS type name is of an Activity (NOT IntransitiveActivity). | ||||||
|  |  | ||||||
|  | @ -218,7 +218,7 @@ func (i *interactionDB) PopulateInteractionRequest(ctx context.Context, req *gts | ||||||
| 			errs.Appendf("error populating interactionRequest Like: %w", err) | 			errs.Appendf("error populating interactionRequest Like: %w", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	case gtsmodel.InteractionReply: | 	case gtsmodel.InteractionReply, gtsmodel.InteractionReplyRequest: | ||||||
| 		req.Reply, err = i.state.DB.GetStatusByURI(ctx, req.InteractionURI) | 		req.Reply, err = i.state.DB.GetStatusByURI(ctx, req.InteractionURI) | ||||||
| 		if err != nil && !errors.Is(err, db.ErrNoEntries) { | 		if err != nil && !errors.Is(err, db.ErrNoEntries) { | ||||||
| 			errs.Appendf("error populating interactionRequest Reply: %w", err) | 			errs.Appendf("error populating interactionRequest Reply: %w", err) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | package migrations | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"github.com/uptrace/bun" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	up := func(ctx context.Context, db *bun.DB) error { | ||||||
|  | 		return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | ||||||
|  | 			return nil | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	down := func(ctx context.Context, db *bun.DB) error { | ||||||
|  | 		return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | ||||||
|  | 			return nil | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := Migrations.Register(up, down); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,33 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | package gtsmodel | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | type InteractionRequest struct { | ||||||
|  | 	ID                   string    `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` | ||||||
|  | 	CreatedAt            time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` | ||||||
|  | 	StatusID             string    `bun:"type:CHAR(26),nullzero,notnull"` | ||||||
|  | 	TargetAccountID      string    `bun:"type:CHAR(26),nullzero,notnull"` | ||||||
|  | 	InteractingAccountID string    `bun:"type:CHAR(26),nullzero,notnull"` | ||||||
|  | 	InteractionURI       string    `bun:",nullzero,notnull,unique"` | ||||||
|  | 	InteractionType      int       `bun:",notnull"` | ||||||
|  | 	AcceptedAt           time.Time `bun:"type:timestamptz,nullzero"` | ||||||
|  | 	RejectedAt           time.Time `bun:"type:timestamptz,nullzero"` | ||||||
|  | 	ResponseURI          string    `bun:"uri,nullzero,unique"` | ||||||
|  | } | ||||||
|  | @ -0,0 +1,33 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | package gtsmodel | ||||||
|  | 
 | ||||||
|  | type PolicyValue string | ||||||
|  | 
 | ||||||
|  | type PolicyValues []PolicyValue | ||||||
|  | 
 | ||||||
|  | type InteractionPolicy struct { | ||||||
|  | 	CanLike     PolicyRules | ||||||
|  | 	CanReply    PolicyRules | ||||||
|  | 	CanAnnounce PolicyRules | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type PolicyRules struct { | ||||||
|  | 	AutomaticApproval PolicyValues `json:"Always,omitempty"` | ||||||
|  | 	ManualApproval    PolicyValues `json:"WithApproval,omitempty"` | ||||||
|  | } | ||||||
|  | @ -51,6 +51,11 @@ var _ interface { | ||||||
| 	Move(context.Context, vocab.ActivityStreamsMove) error | 	Move(context.Context, vocab.ActivityStreamsMove) error | ||||||
| 	Flag(context.Context, vocab.ActivityStreamsFlag) error | 	Flag(context.Context, vocab.ActivityStreamsFlag) error | ||||||
| 
 | 
 | ||||||
|  | 	// Custom types. | ||||||
|  | 	LikeRequest(context.Context, vocab.GoToSocialLikeRequest) error | ||||||
|  | 	ReplyRequest(context.Context, vocab.GoToSocialReplyRequest) error | ||||||
|  | 	AnnounceRequest(context.Context, vocab.GoToSocialAnnounceRequest) error | ||||||
|  | 
 | ||||||
| 	/* | 	/* | ||||||
| 		Extra/convenience functionality. | 		Extra/convenience functionality. | ||||||
| 	*/ | 	*/ | ||||||
|  |  | ||||||
							
								
								
									
										152
									
								
								internal/federation/federatingdb/interactionreqs.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								internal/federation/federatingdb/interactionreqs.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,152 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | package federatingdb | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"net/http" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"code.superseriousbusiness.org/activity/streams/vocab" | ||||||
|  | 	"code.superseriousbusiness.org/gotosocial/internal/ap" | ||||||
|  | 	"code.superseriousbusiness.org/gotosocial/internal/gtserror" | ||||||
|  | 	"code.superseriousbusiness.org/gotosocial/internal/gtsmodel" | ||||||
|  | 	"code.superseriousbusiness.org/gotosocial/internal/id" | ||||||
|  | 	"code.superseriousbusiness.org/gotosocial/internal/log" | ||||||
|  | 	"code.superseriousbusiness.org/gotosocial/internal/messages" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (f *DB) LikeRequest( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	likeReq vocab.GoToSocialLikeRequest, | ||||||
|  | ) error { | ||||||
|  | 	log.DebugKV(ctx, "LikeRequest", serialize{likeReq}) | ||||||
|  | 
 | ||||||
|  | 	// Mark activity as handled. | ||||||
|  | 	f.storeActivityID(likeReq) | ||||||
|  | 
 | ||||||
|  | 	// Extract relevant values from passed ctx. | ||||||
|  | 	activityContext := getActivityContext(ctx) | ||||||
|  | 	if activityContext.internal { | ||||||
|  | 		return nil // Already processed. | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	requesting := activityContext.requestingAcct | ||||||
|  | 	receiving := activityContext.receivingAcct | ||||||
|  | 
 | ||||||
|  | 	if receiving.IsMoving() { | ||||||
|  | 		// A Moving account | ||||||
|  | 		// can't accept a Like. | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Convert received LikeRequest type to dummy | ||||||
|  | 	// fave, so that we can check against policies. | ||||||
|  | 	// This dummy won't be stored in the database, | ||||||
|  | 	// it's used purely for doing permission checks. | ||||||
|  | 	dummyFave, err := f.converter.ASLikeToFave(ctx, likeReq) | ||||||
|  | 	if err != nil { | ||||||
|  | 		err := gtserror.Newf("error converting from AS type: %w", err) | ||||||
|  | 		return gtserror.WrapWithCode(http.StatusBadRequest, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !*dummyFave.Status.Local { | ||||||
|  | 		// Only process like requests for local statuses. | ||||||
|  | 		// | ||||||
|  | 		// If the remote has sent us a like request for a | ||||||
|  | 		// status that's not ours, we should ignore it. | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Ensure fave would be enacted by correct account. | ||||||
|  | 	if dummyFave.AccountID != requesting.ID { | ||||||
|  | 		return gtserror.NewfWithCode(http.StatusForbidden, "requester %s is not expected actor %s", | ||||||
|  | 			requesting.URI, dummyFave.Account.URI) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Ensure fave would be received by correct account. | ||||||
|  | 	if dummyFave.TargetAccountID != receiving.ID { | ||||||
|  | 		return gtserror.NewfWithCode(http.StatusForbidden, "receiver %s is not expected object %s", | ||||||
|  | 			receiving.URI, dummyFave.TargetAccount.URI) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check how we should handle this request. | ||||||
|  | 	policyResult, err := f.intFilter.StatusLikeable(ctx, | ||||||
|  | 		requesting, | ||||||
|  | 		dummyFave.Status, | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return gtserror.Newf("error seeing if status %s is likeable: %w", dummyFave.Status.URI, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Determine whether to automatically accept, | ||||||
|  | 	// automatically reject, or pend approval. | ||||||
|  | 	var ( | ||||||
|  | 		acceptedAt time.Time | ||||||
|  | 		rejectedAt time.Time | ||||||
|  | 	) | ||||||
|  | 	if policyResult.AutomaticApproval() { | ||||||
|  | 		acceptedAt = time.Now() | ||||||
|  | 	} else if policyResult.Forbidden() { | ||||||
|  | 		rejectedAt = time.Now() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	interactionReq := >smodel.InteractionRequest{ | ||||||
|  | 		ID:                   id.NewULID(), | ||||||
|  | 		StatusID:             dummyFave.Status.ID, | ||||||
|  | 		Status:               dummyFave.Status, | ||||||
|  | 		TargetAccountID:      receiving.ID, | ||||||
|  | 		TargetAccount:        receiving, | ||||||
|  | 		InteractingAccountID: requesting.ID, | ||||||
|  | 		InteractingAccount:   requesting, | ||||||
|  | 		InteractionURI:       dummyFave.URI, | ||||||
|  | 		InteractionType:      gtsmodel.InteractionLikeRequest, | ||||||
|  | 		AcceptedAt:           acceptedAt, | ||||||
|  | 		RejectedAt:           rejectedAt, | ||||||
|  | 
 | ||||||
|  | 		// Empty as reject/accept | ||||||
|  | 		// response not yet sent. | ||||||
|  | 		URI: "", | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Send the interactionReq through to | ||||||
|  | 	// the processor to handle side effects. | ||||||
|  | 	f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{ | ||||||
|  | 		APObjectType:   ap.ActivityLikeRequest, | ||||||
|  | 		APActivityType: ap.ActivityCreate, | ||||||
|  | 		GTSModel:       interactionReq, | ||||||
|  | 		Receiving:      receiving, | ||||||
|  | 		Requesting:     requesting, | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *DB) ReplyRequest( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	replyReq vocab.GoToSocialReplyRequest, | ||||||
|  | ) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *DB) AnnounceRequest( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	announceReq vocab.GoToSocialAnnounceRequest, | ||||||
|  | ) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -99,6 +99,9 @@ func NewFederator( | ||||||
| 			federatingDB.Announce, | 			federatingDB.Announce, | ||||||
| 			federatingDB.Move, | 			federatingDB.Move, | ||||||
| 			federatingDB.Flag, | 			federatingDB.Flag, | ||||||
|  | 			federatingDB.LikeRequest, | ||||||
|  | 			federatingDB.ReplyRequest, | ||||||
|  | 			federatingDB.AnnounceRequest, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	actor := newFederatingActor(f, f, federatingDB, clock) | 	actor := newFederatingActor(f, f, federatingDB, clock) | ||||||
|  |  | ||||||
|  | @ -29,22 +29,47 @@ const ( | ||||||
| 	// If you need to add new interaction types, | 	// If you need to add new interaction types, | ||||||
| 	// add them *to the end* of the list. | 	// add them *to the end* of the list. | ||||||
| 
 | 
 | ||||||
|  | 	/* | ||||||
|  | 		Types for when a requester straight up | ||||||
|  | 		sends a Like or a Create or an Announce. | ||||||
|  | 
 | ||||||
|  | 		In this case we will have the Like or the | ||||||
|  | 		Reply or the Announce stored in the db | ||||||
|  | 		marked as pending or whatever. | ||||||
|  | 	*/ | ||||||
|  | 
 | ||||||
| 	InteractionLike InteractionType = iota | 	InteractionLike InteractionType = iota | ||||||
| 	InteractionReply | 	InteractionReply | ||||||
| 	InteractionAnnounce | 	InteractionAnnounce | ||||||
|  | 
 | ||||||
|  | 	/* | ||||||
|  | 		Types for when a requester politely sends | ||||||
|  | 		an XyzRequest asking for permission first. | ||||||
|  | 
 | ||||||
|  | 		In this case we don't store a Like or an | ||||||
|  | 		Announce in the db, as they don't exist yet, | ||||||
|  | 		but we do store a Reply if reply is pending | ||||||
|  | 		approval (for review) or approved, as the | ||||||
|  | 		proposed reply should have been sent along | ||||||
|  | 		as the object of the ReplyRequest. | ||||||
|  | 	*/ | ||||||
|  | 
 | ||||||
|  | 	InteractionLikeRequest | ||||||
|  | 	InteractionReplyRequest | ||||||
|  | 	InteractionAnnounceRequest | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Stringifies this InteractionType in a | // Stringifies this InteractionType in a | ||||||
| // manner suitable for serving via the API. | // manner suitable for serving via the API. | ||||||
| func (i InteractionType) String() string { | func (i InteractionType) String() string { | ||||||
| 	switch i { | 	switch i { | ||||||
| 	case InteractionLike: | 	case InteractionLike, InteractionLikeRequest: | ||||||
| 		const text = "favourite" | 		const text = "favourite" | ||||||
| 		return text | 		return text | ||||||
| 	case InteractionReply: | 	case InteractionReply, InteractionReplyRequest: | ||||||
| 		const text = "reply" | 		const text = "reply" | ||||||
| 		return text | 		return text | ||||||
| 	case InteractionAnnounce: | 	case InteractionAnnounce, InteractionAnnounceRequest: | ||||||
| 		const text = "reblog" | 		const text = "reblog" | ||||||
| 		return text | 		return text | ||||||
| 	default: | 	default: | ||||||
|  | @ -64,10 +89,10 @@ type InteractionRequest struct { | ||||||
| 	TargetAccount        *Account        `bun:"-"`                                                           // Not stored in DB. Account being interacted with. | 	TargetAccount        *Account        `bun:"-"`                                                           // Not stored in DB. Account being interacted with. | ||||||
| 	InteractingAccountID string          `bun:"type:CHAR(26),nullzero,notnull"`                              // id of the account requesting the interaction. | 	InteractingAccountID string          `bun:"type:CHAR(26),nullzero,notnull"`                              // id of the account requesting the interaction. | ||||||
| 	InteractingAccount   *Account        `bun:"-"`                                                           // Not stored in DB. Account corresponding to targetAccountID | 	InteractingAccount   *Account        `bun:"-"`                                                           // Not stored in DB. Account corresponding to targetAccountID | ||||||
| 	InteractionURI       string          `bun:",nullzero,notnull,unique"`                                    // URI of the interacting like, reply, or announce. Unique (only one interaction request allowed per interaction URI). | 	InteractionURI       string          `bun:",nullzero,notnull,unique"`                                    // URI of the interacting like (request), reply (request), or announce (request). Unique (only one interaction request allowed per interaction URI). | ||||||
| 	InteractionType      InteractionType `bun:",notnull"`                                                    // One of Like, Reply, or Announce. | 	InteractionType      InteractionType `bun:",notnull"`                                                    // One of Like, LikeRequest, Reply, ReplyRequest, or Announce, AnnounceRequest. | ||||||
| 	Like                 *StatusFave     `bun:"-"`                                                           // Not stored in DB. Only set if InteractionType = InteractionLike. | 	Like                 *StatusFave     `bun:"-"`                                                           // Not stored in DB. Only set if InteractionType = InteractionLike. | ||||||
| 	Reply                *Status         `bun:"-"`                                                           // Not stored in DB. Only set if InteractionType = InteractionReply. | 	Reply                *Status         `bun:"-"`                                                           // Not stored in DB. Only set if InteractionType = InteractionReply or InteractionType = InteractionReplyRequest. | ||||||
| 	Announce             *Status         `bun:"-"`                                                           // Not stored in DB. Only set if InteractionType = InteractionAnnounce. | 	Announce             *Status         `bun:"-"`                                                           // Not stored in DB. Only set if InteractionType = InteractionAnnounce. | ||||||
| 	AcceptedAt           time.Time       `bun:"type:timestamptz,nullzero"`                                   // If interaction request was accepted, time at which this occurred. | 	AcceptedAt           time.Time       `bun:"type:timestamptz,nullzero"`                                   // If interaction request was accepted, time at which this occurred. | ||||||
| 	RejectedAt           time.Time       `bun:"type:timestamptz,nullzero"`                                   // If interaction request was rejected, time at which this occurred. | 	RejectedAt           time.Time       `bun:"type:timestamptz,nullzero"`                                   // If interaction request was rejected, time at which this occurred. | ||||||
|  |  | ||||||
|  | @ -19,7 +19,8 @@ package gtsmodel | ||||||
| 
 | 
 | ||||||
| import "time" | import "time" | ||||||
| 
 | 
 | ||||||
| // StatusFave refers to a 'fave' or 'like' in the database, from one account, targeting the status of another account | // StatusFave refers to a 'fave' or 'like' in the database, | ||||||
|  | // from one account, targeting the status of another account | ||||||
| type StatusFave struct { | type StatusFave struct { | ||||||
| 	ID              string    `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                      // id of this item in the database | 	ID              string    `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                      // id of this item in the database | ||||||
| 	CreatedAt       time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`   // when was item created | 	CreatedAt       time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`   // when was item created | ||||||
|  |  | ||||||
|  | @ -203,15 +203,11 @@ func (p *Processor) acceptAnnounce( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	req *gtsmodel.InteractionRequest, | 	req *gtsmodel.InteractionRequest, | ||||||
| ) gtserror.WithCode { | ) gtserror.WithCode { | ||||||
| 	// If the Announce is missing, that means it's | 	// If the Announce is set, that means it comes | ||||||
| 	// probably already been undone by someone, | 	// from someone straight up sending the Announce | ||||||
| 	// so there's nothing to actually accept. | 	// instead of AnnounceRequest, so we already have | ||||||
| 	if req.Reply == nil { | 	// the Announce in the db. We can update it now. | ||||||
| 		err := gtserror.Newf("no Announce found for interaction request %s", req.ID) | 	if req.Announce != nil { | ||||||
| 		return gtserror.NewErrorNotFound(err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Update the Announce. |  | ||||||
| 		req.Announce.PendingApproval = util.Ptr(false) | 		req.Announce.PendingApproval = util.Ptr(false) | ||||||
| 		req.Announce.PreApproved = false | 		req.Announce.PreApproved = false | ||||||
| 		req.Announce.ApprovedByURI = req.URI | 		req.Announce.ApprovedByURI = req.URI | ||||||
|  | @ -224,6 +220,7 @@ func (p *Processor) acceptAnnounce( | ||||||
| 			err := gtserror.Newf("db error updating status announce: %w", err) | 			err := gtserror.Newf("db error updating status announce: %w", err) | ||||||
| 			return gtserror.NewErrorInternalError(err) | 			return gtserror.NewErrorInternalError(err) | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// Send the accepted request off through the | 	// Send the accepted request off through the | ||||||
| 	// client API processor to handle side effects. | 	// client API processor to handle side effects. | ||||||
|  |  | ||||||
|  | @ -287,7 +287,7 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA | ||||||
| 		// and/or notify the account that's being | 		// and/or notify the account that's being | ||||||
| 		// interacted with (if it's local): they can | 		// interacted with (if it's local): they can | ||||||
| 		// approve or deny the interaction later. | 		// approve or deny the interaction later. | ||||||
| 		if err := p.utils.requestReply(ctx, status); err != nil { | 		if err := p.utils.replyToRequestReply(ctx, status); err != nil { | ||||||
| 			return gtserror.Newf("error pending reply: %w", err) | 			return gtserror.Newf("error pending reply: %w", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -494,7 +494,7 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI | ||||||
| 		// and/or notify the account that's being | 		// and/or notify the account that's being | ||||||
| 		// interacted with (if it's local): they can | 		// interacted with (if it's local): they can | ||||||
| 		// approve or deny the interaction later. | 		// approve or deny the interaction later. | ||||||
| 		if err := p.utils.requestFave(ctx, fave); err != nil { | 		if err := p.utils.faveToPendingFave(ctx, fave); err != nil { | ||||||
| 			return gtserror.Newf("error pending fave: %w", err) | 			return gtserror.Newf("error pending fave: %w", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -555,7 +555,11 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI | ||||||
| 		// Don't return, just continue as normal. | 		// Don't return, just continue as normal. | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := p.surface.notifyFave(ctx, fave); err != nil { | 	if err := p.surface.notifyFave(ctx, | ||||||
|  | 		fave.Account, | ||||||
|  | 		fave.TargetAccount, | ||||||
|  | 		fave.Status, | ||||||
|  | 	); err != nil { | ||||||
| 		log.Errorf(ctx, "error notifying fave: %v", err) | 		log.Errorf(ctx, "error notifying fave: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -589,7 +593,7 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien | ||||||
| 		// and/or notify the account that's being | 		// and/or notify the account that's being | ||||||
| 		// interacted with (if it's local): they can | 		// interacted with (if it's local): they can | ||||||
| 		// approve or deny the interaction later. | 		// approve or deny the interaction later. | ||||||
| 		if err := p.utils.requestAnnounce(ctx, boost); err != nil { | 		if err := p.utils.announceToRequestAnnounce(ctx, boost); err != nil { | ||||||
| 			return gtserror.Newf("error pending boost: %w", err) | 			return gtserror.Newf("error pending boost: %w", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -661,7 +665,11 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Notify the boost target account. | 	// Notify the boost target account. | ||||||
| 	if err := p.surface.notifyAnnounce(ctx, boost); err != nil { | 	if err := p.surface.notifyAnnounce(ctx, | ||||||
|  | 		boost.Account, | ||||||
|  | 		boost.BoostOfAccount, | ||||||
|  | 		boost.BoostOf, | ||||||
|  | 	); err != nil { | ||||||
| 		log.Errorf(ctx, "error notifying boost: %v", err) | 		log.Errorf(ctx, "error notifying boost: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -1217,8 +1225,13 @@ func (p *clientAPI) AcceptLike(ctx context.Context, cMsg *messages.FromClientAPI | ||||||
| 		return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", cMsg.GTSModel) | 		return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", cMsg.GTSModel) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Notify the fave (distinct from the notif for the pending fave). | 	// Notify the fave (distinct from | ||||||
| 	if err := p.surface.notifyFave(ctx, req.Like); err != nil { | 	// the notif for the pending fave). | ||||||
|  | 	if err := p.surface.notifyFave(ctx, | ||||||
|  | 		req.InteractingAccount, | ||||||
|  | 		req.TargetAccount, | ||||||
|  | 		req.Status, | ||||||
|  | 	); err != nil { | ||||||
| 		log.Errorf(ctx, "error notifying fave: %v", err) | 		log.Errorf(ctx, "error notifying fave: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -1273,6 +1286,25 @@ func (p *clientAPI) AcceptAnnounce(ctx context.Context, cMsg *messages.FromClien | ||||||
| 		return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", cMsg.GTSModel) | 		return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", cMsg.GTSModel) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Send out the Accept. | ||||||
|  | 	if err := p.federate.AcceptInteraction(ctx, req); err != nil { | ||||||
|  | 		log.Errorf(ctx, "error federating approval of announce: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If req.Announce is not set, that means we got | ||||||
|  | 	// the request politely as AnnounceRequest, and | ||||||
|  | 	// so we don't have the Announce wrapper stored | ||||||
|  | 	// because it hasn't been sent yet. | ||||||
|  | 	if req.Announce == nil { | ||||||
|  | 		// Nothing to do but wait for the | ||||||
|  | 		// remote to send the Announce. | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If it *is* set, that means it comes from someone | ||||||
|  | 	// straight up sending the Announce first instead of | ||||||
|  | 	// AnnounceRequest, so we already have the Announce | ||||||
|  | 	// in the db, and we can process it now. | ||||||
| 	var ( | 	var ( | ||||||
| 		interactingAcct = req.InteractingAccount | 		interactingAcct = req.InteractingAccount | ||||||
| 		boost           = req.Announce | 		boost           = req.Announce | ||||||
|  | @ -1288,16 +1320,17 @@ func (p *clientAPI) AcceptAnnounce(ctx context.Context, cMsg *messages.FromClien | ||||||
| 		log.Errorf(ctx, "error timelining and notifying status: %v", err) | 		log.Errorf(ctx, "error timelining and notifying status: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Notify the announce (distinct from the notif for the pending announce). | 	// Notify the announce (distinct from | ||||||
| 	if err := p.surface.notifyAnnounce(ctx, boost); err != nil { | 	// the notif for the pending announce). | ||||||
|  | 	if err := p.surface.notifyAnnounce( | ||||||
|  | 		ctx, | ||||||
|  | 		boost.Account, | ||||||
|  | 		boost.BoostOfAccount, | ||||||
|  | 		boost.BoostOf, | ||||||
|  | 	); err != nil { | ||||||
| 		log.Errorf(ctx, "error notifying announce: %v", err) | 		log.Errorf(ctx, "error notifying announce: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Send out the Accept. |  | ||||||
| 	if err := p.federate.AcceptInteraction(ctx, req); err != nil { |  | ||||||
| 		log.Errorf(ctx, "error federating approval of announce: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Interaction counts changed on the original status; | 	// Interaction counts changed on the original status; | ||||||
| 	// uncache the prepared version from all timelines. | 	// uncache the prepared version from all timelines. | ||||||
| 	p.surface.invalidateStatusFromTimelines(boost.BoostOfID) | 	p.surface.invalidateStatusFromTimelines(boost.BoostOfID) | ||||||
|  |  | ||||||
|  | @ -96,6 +96,10 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF | ||||||
| 		case ap.ActivityLike: | 		case ap.ActivityLike: | ||||||
| 			return p.fediAPI.CreateLike(ctx, fMsg) | 			return p.fediAPI.CreateLike(ctx, fMsg) | ||||||
| 
 | 
 | ||||||
|  | 		// CREATE LIKE/FAVE REQUEST | ||||||
|  | 		case ap.ActivityLikeRequest: | ||||||
|  | 			return p.fediAPI.CreateLikeRequest(ctx, fMsg) | ||||||
|  | 
 | ||||||
| 		// CREATE ANNOUNCE/BOOST | 		// CREATE ANNOUNCE/BOOST | ||||||
| 		case ap.ActivityAnnounce: | 		case ap.ActivityAnnounce: | ||||||
| 			return p.fediAPI.CreateAnnounce(ctx, fMsg) | 			return p.fediAPI.CreateAnnounce(ctx, fMsg) | ||||||
|  | @ -291,7 +295,7 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI) | ||||||
| 		// preapproved, then just notify the account | 		// preapproved, then just notify the account | ||||||
| 		// that's being interacted with: they can | 		// that's being interacted with: they can | ||||||
| 		// approve or deny the interaction later. | 		// approve or deny the interaction later. | ||||||
| 		if err := p.utils.requestReply(ctx, status); err != nil { | 		if err := p.utils.replyToRequestReply(ctx, status); err != nil { | ||||||
| 			return gtserror.Newf("error pending reply: %w", err) | 			return gtserror.Newf("error pending reply: %w", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -525,7 +529,7 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er | ||||||
| 		// preapproved, then just notify the account | 		// preapproved, then just notify the account | ||||||
| 		// that's being interacted with: they can | 		// that's being interacted with: they can | ||||||
| 		// approve or deny the interaction later. | 		// approve or deny the interaction later. | ||||||
| 		if err := p.utils.requestFave(ctx, fave); err != nil { | 		if err := p.utils.faveToPendingFave(ctx, fave); err != nil { | ||||||
| 			return gtserror.Newf("error pending fave: %w", err) | 			return gtserror.Newf("error pending fave: %w", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -580,7 +584,11 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er | ||||||
| 		// Don't return, just continue as normal. | 		// Don't return, just continue as normal. | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := p.surface.notifyFave(ctx, fave); err != nil { | 	if err := p.surface.notifyFave(ctx, | ||||||
|  | 		fave.Account, | ||||||
|  | 		fave.TargetAccount, | ||||||
|  | 		fave.Status, | ||||||
|  | 	); err != nil { | ||||||
| 		log.Errorf(ctx, "error notifying fave: %v", err) | 		log.Errorf(ctx, "error notifying fave: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -591,6 +599,76 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (p *fediAPI) CreateLikeRequest( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	fMsg *messages.FromFediAPI, | ||||||
|  | ) error { | ||||||
|  | 	// Unlike InteractionReq from xyz, InteractionReq from | ||||||
|  | 	// xyzRequest will only ever have xyz set on it if xyz | ||||||
|  | 	// is a reply, not a Like or an Announce. | ||||||
|  | 	// | ||||||
|  | 	// In this case, that means there's no Fave set on it. | ||||||
|  | 	interactionReq, ok := fMsg.GTSModel.(*gtsmodel.InteractionRequest) | ||||||
|  | 	if !ok { | ||||||
|  | 		return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", fMsg.GTSModel) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Whatever happens, we'll need to store the request. | ||||||
|  | 	// | ||||||
|  | 	// AcceptedAt or RejectedAt should already be set on the | ||||||
|  | 	// request if it's automatically accepted, or not permitted, | ||||||
|  | 	// so we don't have to do that here. | ||||||
|  | 	err := p.state.DB.PutInteractionRequest(ctx, interactionReq) | ||||||
|  | 	switch { | ||||||
|  | 	case err == nil: | ||||||
|  | 		// All good, | ||||||
|  | 		// it's stored. | ||||||
|  | 
 | ||||||
|  | 	case errors.Is(err, db.ErrAlreadyExists): | ||||||
|  | 		// Request already stored, | ||||||
|  | 		// did something race? | ||||||
|  | 		// Nothing to in that case. | ||||||
|  | 		return nil | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		// Real error, cannot continue. | ||||||
|  | 		return gtserror.Newf("db error storing like request: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Process side effects. | ||||||
|  | 	switch { | ||||||
|  | 	case interactionReq.IsPending(): | ||||||
|  | 		// If pending, ie., manual approval | ||||||
|  | 		// required, the only side effect is | ||||||
|  | 		// to notify the interactee. | ||||||
|  | 		if err := p.utils.surface.notifyPendingFave( | ||||||
|  | 			ctx, | ||||||
|  | 			interactionReq.InteractingAccount, | ||||||
|  | 			interactionReq.TargetAccount, | ||||||
|  | 			interactionReq.Status, | ||||||
|  | 		); err != nil { | ||||||
|  | 			log.Errorf(ctx, "error storing pending like notif: %v", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case interactionReq.IsAccepted(): | ||||||
|  | 		// If accepted, ie., automatic approval, | ||||||
|  | 		// just send out the Accept message and | ||||||
|  | 		// wait for the Like to be delivered. | ||||||
|  | 		if err := p.federate.AcceptInteraction(ctx, interactionReq); err != nil { | ||||||
|  | 			log.Errorf(ctx, "error sending like accept: %v", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case interactionReq.IsRejected(): | ||||||
|  | 		// If rejected, ie., not permitted, | ||||||
|  | 		// just send out the Reject message. | ||||||
|  | 		if err := p.federate.RejectInteraction(ctx, interactionReq); err != nil { | ||||||
|  | 			log.Errorf(ctx, "error sending like reject: %v", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI) error { | func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI) error { | ||||||
| 	boost, ok := fMsg.GTSModel.(*gtsmodel.Status) | 	boost, ok := fMsg.GTSModel.(*gtsmodel.Status) | ||||||
| 	if !ok { | 	if !ok { | ||||||
|  | @ -632,7 +710,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI | ||||||
| 		// preapproved, then just notify the account | 		// preapproved, then just notify the account | ||||||
| 		// that's being interacted with: they can | 		// that's being interacted with: they can | ||||||
| 		// approve or deny the interaction later. | 		// approve or deny the interaction later. | ||||||
| 		if err := p.utils.requestAnnounce(ctx, boost); err != nil { | 		if err := p.utils.announceToRequestAnnounce(ctx, boost); err != nil { | ||||||
| 			return gtserror.Newf("error pending boost: %w", err) | 			return gtserror.Newf("error pending boost: %w", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -697,7 +775,12 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI | ||||||
| 		log.Errorf(ctx, "error timelining and notifying status: %v", err) | 		log.Errorf(ctx, "error timelining and notifying status: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := p.surface.notifyAnnounce(ctx, boost); err != nil { | 	if err := p.surface.notifyAnnounce( | ||||||
|  | 		ctx, | ||||||
|  | 		boost.Account, | ||||||
|  | 		boost.BoostOfAccount, | ||||||
|  | 		boost.BoostOf, | ||||||
|  | 	); err != nil { | ||||||
| 		log.Errorf(ctx, "error notifying announce: %v", err) | 		log.Errorf(ctx, "error notifying announce: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -708,6 +791,76 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (p *fediAPI) CreateAnnounceRequest( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	fMsg *messages.FromFediAPI, | ||||||
|  | ) error { | ||||||
|  | 	// Unlike InteractionReq from xyz, InteractionReq from | ||||||
|  | 	// xyzRequest will only ever have xyz set on it if xyz | ||||||
|  | 	// is a reply, not a Like or an Announce. | ||||||
|  | 	// | ||||||
|  | 	// In this case, that means there's no Announce set on it. | ||||||
|  | 	interactionReq, ok := fMsg.GTSModel.(*gtsmodel.InteractionRequest) | ||||||
|  | 	if !ok { | ||||||
|  | 		return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", fMsg.GTSModel) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Whatever happens, we'll need to store the request. | ||||||
|  | 	// | ||||||
|  | 	// AcceptedAt or RejectedAt should already be set on the | ||||||
|  | 	// request if it's automatically accepted, or not permitted, | ||||||
|  | 	// so we don't have to do that here. | ||||||
|  | 	err := p.state.DB.PutInteractionRequest(ctx, interactionReq) | ||||||
|  | 	switch { | ||||||
|  | 	case err == nil: | ||||||
|  | 		// All good, | ||||||
|  | 		// it's stored. | ||||||
|  | 
 | ||||||
|  | 	case errors.Is(err, db.ErrAlreadyExists): | ||||||
|  | 		// Request already stored, | ||||||
|  | 		// did something race? | ||||||
|  | 		// Nothing to in that case. | ||||||
|  | 		return nil | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		// Real error, cannot continue. | ||||||
|  | 		return gtserror.Newf("db error storing announce request: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Process side effects. | ||||||
|  | 	switch { | ||||||
|  | 	case interactionReq.IsPending(): | ||||||
|  | 		// If pending, ie., manual approval | ||||||
|  | 		// required, the only side effect is | ||||||
|  | 		// to notify the interactee. | ||||||
|  | 		if err := p.utils.surface.notifyPendingAnnounce( | ||||||
|  | 			ctx, | ||||||
|  | 			interactionReq.InteractingAccount, | ||||||
|  | 			interactionReq.TargetAccount, | ||||||
|  | 			interactionReq.Status, | ||||||
|  | 		); err != nil { | ||||||
|  | 			log.Errorf(ctx, "error storing pending announce notif: %v", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case interactionReq.IsAccepted(): | ||||||
|  | 		// If accepted, ie., automatic approval, | ||||||
|  | 		// just send out the Accept message and | ||||||
|  | 		// wait for the Announce to be delivered. | ||||||
|  | 		if err := p.federate.AcceptInteraction(ctx, interactionReq); err != nil { | ||||||
|  | 			log.Errorf(ctx, "error sending announce accept: %v", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case interactionReq.IsRejected(): | ||||||
|  | 		// If rejected, ie., not permitted, | ||||||
|  | 		// just send out the Reject message. | ||||||
|  | 		if err := p.federate.RejectInteraction(ctx, interactionReq); err != nil { | ||||||
|  | 			log.Errorf(ctx, "error sending announce reject: %v", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (p *fediAPI) CreateBlock(ctx context.Context, fMsg *messages.FromFediAPI) error { | func (p *fediAPI) CreateBlock(ctx context.Context, fMsg *messages.FromFediAPI) error { | ||||||
| 	block, ok := fMsg.GTSModel.(*gtsmodel.Block) | 	block, ok := fMsg.GTSModel.(*gtsmodel.Block) | ||||||
| 	if !ok { | 	if !ok { | ||||||
|  |  | ||||||
|  | @ -253,13 +253,19 @@ func (s *Surface) notifyFollow( | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // notifyFave notifies the target of the given | // notifyFave notifies the target of of a | ||||||
| // fave that their status has been liked/faved. | // fave that their status has been faved. | ||||||
| func (s *Surface) notifyFave( | func (s *Surface) notifyFave( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	fave *gtsmodel.StatusFave, | 	account *gtsmodel.Account, | ||||||
|  | 	targetAccount *gtsmodel.Account, | ||||||
|  | 	status *gtsmodel.Status, | ||||||
| ) error { | ) error { | ||||||
| 	notifyable, err := s.notifyableFave(ctx, fave) | 	notifyable, err := s.notifyableFave(ctx, | ||||||
|  | 		account, | ||||||
|  | 		targetAccount, | ||||||
|  | 		status, | ||||||
|  | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -273,24 +279,30 @@ func (s *Surface) notifyFave( | ||||||
| 	// of fave by account. | 	// of fave by account. | ||||||
| 	if err := s.Notify(ctx, | 	if err := s.Notify(ctx, | ||||||
| 		gtsmodel.NotificationFavourite, | 		gtsmodel.NotificationFavourite, | ||||||
| 		fave.TargetAccount, | 		targetAccount, | ||||||
| 		fave.Account, | 		account, | ||||||
| 		fave.StatusID, | 		status.ID, | ||||||
| 	); err != nil { | 	); err != nil { | ||||||
| 		return gtserror.Newf("error notifying status author %s: %w", fave.TargetAccountID, err) | 		return gtserror.Newf("error notifying status author %s: %w", targetAccount.ID, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // notifyPendingFave notifies the target of the | // notifyPendingFave notifies the target of a | ||||||
| // given fave that their status has been faved | // fave that their status has been faved and | ||||||
| // and that approval is required. | // that approval is required. | ||||||
| func (s *Surface) notifyPendingFave( | func (s *Surface) notifyPendingFave( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	fave *gtsmodel.StatusFave, | 	account *gtsmodel.Account, | ||||||
|  | 	targetAccount *gtsmodel.Account, | ||||||
|  | 	status *gtsmodel.Status, | ||||||
| ) error { | ) error { | ||||||
| 	notifyable, err := s.notifyableFave(ctx, fave) | 	notifyable, err := s.notifyableFave(ctx, | ||||||
|  | 		account, | ||||||
|  | 		targetAccount, | ||||||
|  | 		status, | ||||||
|  | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -304,34 +316,31 @@ func (s *Surface) notifyPendingFave( | ||||||
| 	// of fave by account. | 	// of fave by account. | ||||||
| 	if err := s.Notify(ctx, | 	if err := s.Notify(ctx, | ||||||
| 		gtsmodel.NotificationPendingFave, | 		gtsmodel.NotificationPendingFave, | ||||||
| 		fave.TargetAccount, | 		targetAccount, | ||||||
| 		fave.Account, | 		account, | ||||||
| 		fave.StatusID, | 		status.ID, | ||||||
| 	); err != nil { | 	); err != nil { | ||||||
| 		return gtserror.Newf("error notifying status author %s: %w", fave.TargetAccountID, err) | 		return gtserror.Newf("error notifying status author %s: %w", targetAccount.ID, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // notifyableFave checks that the given | // notifyableFave checks if a fave should | ||||||
| // fave should be notified, taking account | // be notified, taking account of localness | ||||||
| // of localness of receiving account, and mutes. | // of target account, and thread mutes. | ||||||
| func (s *Surface) notifyableFave( | func (s *Surface) notifyableFave( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	fave *gtsmodel.StatusFave, | 	account *gtsmodel.Account, | ||||||
|  | 	targetAccount *gtsmodel.Account, | ||||||
|  | 	status *gtsmodel.Status, | ||||||
| ) (bool, error) { | ) (bool, error) { | ||||||
| 	if fave.TargetAccountID == fave.AccountID { | 	if targetAccount.ID == account.ID { | ||||||
| 		// Self-fave, nothing to do. | 		// Self-fave, nothing to do. | ||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Beforehand, ensure the passed status fave is fully populated. | 	if targetAccount.IsRemote() { | ||||||
| 	if err := s.State.DB.PopulateStatusFave(ctx, fave); err != nil { |  | ||||||
| 		return false, gtserror.Newf("error populating fave %s: %w", fave.ID, err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if fave.TargetAccount.IsRemote() { |  | ||||||
| 		// no need to notify | 		// no need to notify | ||||||
| 		// remote accounts. | 		// remote accounts. | ||||||
| 		return false, nil | 		return false, nil | ||||||
|  | @ -341,11 +350,11 @@ func (s *Surface) notifyableFave( | ||||||
| 	// muted the thread. | 	// muted the thread. | ||||||
| 	muted, err := s.State.DB.IsThreadMutedByAccount( | 	muted, err := s.State.DB.IsThreadMutedByAccount( | ||||||
| 		ctx, | 		ctx, | ||||||
| 		fave.Status.ThreadID, | 		status.ThreadID, | ||||||
| 		fave.TargetAccountID, | 		targetAccount.ID, | ||||||
| 	) | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, gtserror.Newf("error checking status thread mute %s: %w", fave.StatusID, err) | 		return false, gtserror.Newf("error checking status thread mute %s: %w", status.ID, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if muted { | 	if muted { | ||||||
|  | @ -357,13 +366,20 @@ func (s *Surface) notifyableFave( | ||||||
| 	return true, nil | 	return true, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // notifyAnnounce notifies the status boost target | // notifyAnnounce notifies the target | ||||||
| // account that their status has been boosted. | // acct that their status has been boosted. | ||||||
| func (s *Surface) notifyAnnounce( | func (s *Surface) notifyAnnounce( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	boost *gtsmodel.Status, | 	account *gtsmodel.Account, | ||||||
|  | 	targetAccount *gtsmodel.Account, | ||||||
|  | 	targetStatus *gtsmodel.Status, | ||||||
| ) error { | ) error { | ||||||
| 	notifyable, err := s.notifyableAnnounce(ctx, boost) | 	notifyable, err := s.notifyableAnnounce( | ||||||
|  | 		ctx, | ||||||
|  | 		account, | ||||||
|  | 		targetAccount, | ||||||
|  | 		targetStatus, | ||||||
|  | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -377,11 +393,11 @@ func (s *Surface) notifyAnnounce( | ||||||
| 	// of boost by account. | 	// of boost by account. | ||||||
| 	if err := s.Notify(ctx, | 	if err := s.Notify(ctx, | ||||||
| 		gtsmodel.NotificationReblog, | 		gtsmodel.NotificationReblog, | ||||||
| 		boost.BoostOfAccount, | 		targetAccount, | ||||||
| 		boost.Account, | 		account, | ||||||
| 		boost.ID, | 		targetStatus.ID, | ||||||
| 	); err != nil { | 	); err != nil { | ||||||
| 		return gtserror.Newf("error notifying boost target %s: %w", boost.BoostOfAccountID, err) | 		return gtserror.Newf("error notifying boost target %s: %w", targetAccount.ID, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  | @ -392,9 +408,16 @@ func (s *Surface) notifyAnnounce( | ||||||
| // and that the boost requires approval. | // and that the boost requires approval. | ||||||
| func (s *Surface) notifyPendingAnnounce( | func (s *Surface) notifyPendingAnnounce( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	boost *gtsmodel.Status, | 	account *gtsmodel.Account, | ||||||
|  | 	targetAccount *gtsmodel.Account, | ||||||
|  | 	targetStatus *gtsmodel.Status, | ||||||
| ) error { | ) error { | ||||||
| 	notifyable, err := s.notifyableAnnounce(ctx, boost) | 	notifyable, err := s.notifyableAnnounce( | ||||||
|  | 		ctx, | ||||||
|  | 		account, | ||||||
|  | 		targetAccount, | ||||||
|  | 		targetStatus, | ||||||
|  | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -408,11 +431,11 @@ func (s *Surface) notifyPendingAnnounce( | ||||||
| 	// of boost by account. | 	// of boost by account. | ||||||
| 	if err := s.Notify(ctx, | 	if err := s.Notify(ctx, | ||||||
| 		gtsmodel.NotificationPendingReblog, | 		gtsmodel.NotificationPendingReblog, | ||||||
| 		boost.BoostOfAccount, | 		targetAccount, | ||||||
| 		boost.Account, | 		account, | ||||||
| 		boost.ID, | 		targetStatus.ID, | ||||||
| 	); err != nil { | 	); err != nil { | ||||||
| 		return gtserror.Newf("error notifying boost target %s: %w", boost.BoostOfAccountID, err) | 		return gtserror.Newf("error notifying pending boost target %s: %w", targetAccount.ID, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  | @ -423,24 +446,21 @@ func (s *Surface) notifyPendingAnnounce( | ||||||
| // of localness of receiving account, and mutes. | // of localness of receiving account, and mutes. | ||||||
| func (s *Surface) notifyableAnnounce( | func (s *Surface) notifyableAnnounce( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	status *gtsmodel.Status, | 	account *gtsmodel.Account, | ||||||
|  | 	targetAccount *gtsmodel.Account, | ||||||
|  | 	targetStatus *gtsmodel.Status, | ||||||
| ) (bool, error) { | ) (bool, error) { | ||||||
| 	if status.BoostOfID == "" { | 	if account.ID == targetStatus.AccountID { | ||||||
| 		// Not a boost, nothing to do. |  | ||||||
| 		return false, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if status.BoostOfAccountID == status.AccountID { |  | ||||||
| 		// Self-boost, nothing to do. | 		// Self-boost, nothing to do. | ||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Beforehand, ensure the passed status is fully populated. | 	// Ensure boosted status is populated. | ||||||
| 	if err := s.State.DB.PopulateStatus(ctx, status); err != nil { | 	if err := s.State.DB.PopulateStatus(ctx, targetStatus); err != nil { | ||||||
| 		return false, gtserror.Newf("error populating status %s: %w", status.ID, err) | 		return false, gtserror.Newf("error populating status %s: %w", targetStatus.ID, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if status.BoostOfAccount.IsRemote() { | 	if targetStatus.Account.IsRemote() { | ||||||
| 		// no need to notify | 		// no need to notify | ||||||
| 		// remote accounts. | 		// remote accounts. | ||||||
| 		return false, nil | 		return false, nil | ||||||
|  | @ -450,12 +470,11 @@ func (s *Surface) notifyableAnnounce( | ||||||
| 	// muted the thread. | 	// muted the thread. | ||||||
| 	muted, err := s.State.DB.IsThreadMutedByAccount( | 	muted, err := s.State.DB.IsThreadMutedByAccount( | ||||||
| 		ctx, | 		ctx, | ||||||
| 		status.BoostOf.ThreadID, | 		targetStatus.ThreadID, | ||||||
| 		status.BoostOfAccountID, | 		targetAccount.ID, | ||||||
| 	) | 	) | ||||||
| 
 |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, gtserror.Newf("error checking status thread mute %s: %w", status.BoostOfID, err) | 		return false, gtserror.Newf("error checking status thread mute %s: %w", targetStatus.ID, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if muted { | 	if muted { | ||||||
|  |  | ||||||
|  | @ -526,9 +526,14 @@ func (u *utils) decrementFollowRequestsCount( | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // requestFave stores an interaction request | // faveToPendingFave stores an interaction request | ||||||
| // for the given fave, and notifies the interactee. | // for the given fave, and notifies the interactee. | ||||||
| func (u *utils) requestFave( | // | ||||||
|  | // This is useful when a local account needs to send | ||||||
|  | // out a LikeRequest, or when a remote account has | ||||||
|  | // sent us a Like instead of a LikeRequest for a | ||||||
|  | // status where Liking requires approval. | ||||||
|  | func (u *utils) faveToPendingFave( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	fave *gtsmodel.StatusFave, | 	fave *gtsmodel.StatusFave, | ||||||
| ) error { | ) error { | ||||||
|  | @ -561,17 +566,26 @@ func (u *utils) requestFave( | ||||||
| 		return gtserror.Newf("db error storing interaction request: %w", err) | 		return gtserror.Newf("db error storing interaction request: %w", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Notify *local* account of pending announce. | 	// Notify *local* account of pending fave. | ||||||
| 	if err := u.surface.notifyPendingFave(ctx, fave); err != nil { | 	if err := u.surface.notifyPendingFave(ctx, | ||||||
|  | 		fave.Account, | ||||||
|  | 		fave.TargetAccount, | ||||||
|  | 		fave.Status, | ||||||
|  | 	); err != nil { | ||||||
| 		return gtserror.Newf("error notifying pending fave: %w", err) | 		return gtserror.Newf("error notifying pending fave: %w", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // requestReply stores an interaction request | // replyToRequestReply stores an interaction request | ||||||
| // for the given reply, and notifies the interactee. | // for the given reply, and notifies the interactee. | ||||||
| func (u *utils) requestReply( | // | ||||||
|  | // This is useful when a local account needs to send | ||||||
|  | // out a ReplyRequest, or when a remote account has | ||||||
|  | // sent us a Create instead of a ReplyRequest for a | ||||||
|  | // status where replying requires approval. | ||||||
|  | func (u *utils) replyToRequestReply( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	reply *gtsmodel.Status, | 	reply *gtsmodel.Status, | ||||||
| ) error { | ) error { | ||||||
|  | @ -612,9 +626,14 @@ func (u *utils) requestReply( | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // requestAnnounce stores an interaction request | // announceToRequestAnnounce stores an interaction request | ||||||
| // for the given announce, and notifies the interactee. | // for the given announce, and notifies the interactee. | ||||||
| func (u *utils) requestAnnounce( | // | ||||||
|  | // This is useful when a local account needs to send | ||||||
|  | // out an AnnounceRequest, or when a remote account has | ||||||
|  | // sent us an Announce instead of an AnnounceRequest for | ||||||
|  | // a status where announcing requires approval. | ||||||
|  | func (u *utils) announceToRequestAnnounce( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	boost *gtsmodel.Status, | 	boost *gtsmodel.Status, | ||||||
| ) error { | ) error { | ||||||
|  | @ -648,7 +667,12 @@ func (u *utils) requestAnnounce( | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Notify *local* account of pending announce. | 	// Notify *local* account of pending announce. | ||||||
| 	if err := u.surface.notifyPendingAnnounce(ctx, boost); err != nil { | 	if err := u.surface.notifyPendingAnnounce( | ||||||
|  | 		ctx, | ||||||
|  | 		boost.Account, | ||||||
|  | 		boost.BoostOfAccount, | ||||||
|  | 		boost.BoostOf, | ||||||
|  | 	); err != nil { | ||||||
| 		return gtserror.Newf("error notifying pending announce: %w", err) | 		return gtserror.Newf("error notifying pending announce: %w", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue