| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // 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/>. | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | package federatingdb | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	"codeberg.org/gruf/go-logger/v2/level" | 
					
						
							| 
									
										
										
										
											2021-11-13 17:29:43 +01:00
										 |  |  | 	"github.com/superseriousbusiness/activity/streams/vocab" | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error { | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 	l := log.WithContext(ctx) | 
					
						
							| 
									
										
										
										
											2021-10-04 15:24:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	if log.Level() >= level.DEBUG { | 
					
						
							| 
									
										
										
										
											2021-10-04 15:24:19 +02:00
										 |  |  | 		i, err := marshalItem(undo) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		l = l.WithField("undo", i) | 
					
						
							|  |  |  | 		l.Debug("entering Undo") | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-10-04 15:24:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 	receivingAccount, _, internal := extractFromCtx(ctx) | 
					
						
							|  |  |  | 	if internal { | 
					
						
							|  |  |  | 		return nil // Already processed. | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 	var errs gtserror.MultiError | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 	for _, object := range ap.ExtractObjects(undo) { | 
					
						
							|  |  |  | 		// Try to get object as vocab.Type, | 
					
						
							|  |  |  | 		// else skip handling (likely) IRI. | 
					
						
							|  |  |  | 		objType := object.GetType() | 
					
						
							|  |  |  | 		if objType == nil { | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 		switch objType.GetTypeName() { | 
					
						
							| 
									
										
										
										
											2021-09-03 10:30:40 +02:00
										 |  |  | 		case ap.ActivityFollow: | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 			if err := f.undoFollow(ctx, receivingAccount, undo, objType); err != nil { | 
					
						
							|  |  |  | 				errs.Appendf("error undoing follow: %w", err) | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-09-03 10:30:40 +02:00
										 |  |  | 		case ap.ActivityLike: | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 			if err := f.undoLike(ctx, receivingAccount, undo, objType); err != nil { | 
					
						
							|  |  |  | 				errs.Appendf("error undoing like: %w", err) | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-09-03 10:30:40 +02:00
										 |  |  | 		case ap.ActivityAnnounce: | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 			// TODO: actually handle this ! | 
					
						
							|  |  |  | 			log.Warn(ctx, "skipped undo announce") | 
					
						
							| 
									
										
										
										
											2021-09-03 10:30:40 +02:00
										 |  |  | 		case ap.ActivityBlock: | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 			if err := f.undoBlock(ctx, receivingAccount, undo, objType); err != nil { | 
					
						
							|  |  |  | 				errs.Appendf("error undoing block: %w", err) | 
					
						
							| 
									
										
										
										
											2021-07-11 16:22:21 +02:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (f *federatingDB) undoFollow( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	receivingAccount *gtsmodel.Account, | 
					
						
							|  |  |  | 	undo vocab.ActivityStreamsUndo, | 
					
						
							|  |  |  | 	t vocab.Type, | 
					
						
							|  |  |  | ) error { | 
					
						
							|  |  |  | 	Follow, ok := t.(vocab.ActivityStreamsFollow) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return errors.New("undoFollow: couldn't parse vocab.Type into vocab.ActivityStreamsFollow") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make sure the undo actor owns the target. | 
					
						
							|  |  |  | 	if !sameActor(undo.GetActivityStreamsActor(), Follow.GetActivityStreamsActor()) { | 
					
						
							|  |  |  | 		// Ignore this Activity. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-23 17:44:11 +01:00
										 |  |  | 	follow, err := f.converter.ASFollowToFollow(ctx, Follow) | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("undoFollow: error converting ActivityStreams Follow to follow: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Ensure addressee is follow target. | 
					
						
							|  |  |  | 	if follow.TargetAccountID != receivingAccount.ID { | 
					
						
							|  |  |  | 		// Ignore this Activity. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Delete any existing follow with this URI. | 
					
						
							|  |  |  | 	if err := f.state.DB.DeleteFollowByURI(ctx, follow.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		return fmt.Errorf("undoFollow: db error removing follow: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Delete any existing follow request with this URI. | 
					
						
							|  |  |  | 	if err := f.state.DB.DeleteFollowRequestByURI(ctx, follow.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		return fmt.Errorf("undoFollow: db error removing follow request: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	log.Debug(ctx, "Follow undone") | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (f *federatingDB) undoLike( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	receivingAccount *gtsmodel.Account, | 
					
						
							|  |  |  | 	undo vocab.ActivityStreamsUndo, | 
					
						
							|  |  |  | 	t vocab.Type, | 
					
						
							|  |  |  | ) error { | 
					
						
							|  |  |  | 	Like, ok := t.(vocab.ActivityStreamsLike) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return errors.New("undoLike: couldn't parse vocab.Type into vocab.ActivityStreamsLike") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make sure the undo actor owns the target. | 
					
						
							|  |  |  | 	if !sameActor(undo.GetActivityStreamsActor(), Like.GetActivityStreamsActor()) { | 
					
						
							|  |  |  | 		// Ignore this Activity. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-23 17:44:11 +01:00
										 |  |  | 	fave, err := f.converter.ASLikeToFave(ctx, Like) | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("undoLike: error converting ActivityStreams Like to fave: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Ensure addressee is fave target. | 
					
						
							|  |  |  | 	if fave.TargetAccountID != receivingAccount.ID { | 
					
						
							|  |  |  | 		// Ignore this Activity. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Ignore URI on Likes, since we often get multiple Likes | 
					
						
							|  |  |  | 	// with the same target and account ID, but differing URIs. | 
					
						
							|  |  |  | 	// Instead, we'll select using account and target status. | 
					
						
							|  |  |  | 	// Regardless of the URI, we can read an Undo Like to mean | 
					
						
							|  |  |  | 	// "I don't want to fave this post anymore". | 
					
						
							|  |  |  | 	fave, err = f.state.DB.GetStatusFave(gtscontext.SetBarebones(ctx), fave.AccountID, fave.StatusID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 			// We didn't have a like/fave | 
					
						
							|  |  |  | 			// for this combo anyway, ignore. | 
					
						
							| 
									
										
										
										
											2021-07-11 16:22:21 +02:00
										 |  |  | 			return nil | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 		// Real error. | 
					
						
							|  |  |  | 		return fmt.Errorf("undoLike: db error getting fave from %s targeting %s: %w", fave.AccountID, fave.StatusID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Delete the status fave. | 
					
						
							|  |  |  | 	if err := f.state.DB.DeleteStatusFaveByID(ctx, fave.ID); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("undoLike: db error deleting fave %s: %w", fave.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	log.Debug(ctx, "Like undone") | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (f *federatingDB) undoBlock( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	receivingAccount *gtsmodel.Account, | 
					
						
							|  |  |  | 	undo vocab.ActivityStreamsUndo, | 
					
						
							|  |  |  | 	t vocab.Type, | 
					
						
							|  |  |  | ) error { | 
					
						
							|  |  |  | 	Block, ok := t.(vocab.ActivityStreamsBlock) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return errors.New("undoBlock: couldn't parse vocab.Type into vocab.ActivityStreamsBlock") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make sure the undo actor owns the target. | 
					
						
							|  |  |  | 	if !sameActor(undo.GetActivityStreamsActor(), Block.GetActivityStreamsActor()) { | 
					
						
							|  |  |  | 		// Ignore this Activity. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-23 17:44:11 +01:00
										 |  |  | 	block, err := f.converter.ASBlockToBlock(ctx, Block) | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("undoBlock: error converting ActivityStreams Block to block: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Ensure addressee is block target. | 
					
						
							|  |  |  | 	if block.TargetAccountID != receivingAccount.ID { | 
					
						
							|  |  |  | 		// Ignore this Activity. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Delete any existing BLOCK | 
					
						
							|  |  |  | 	if err := f.state.DB.DeleteBlockByURI(ctx, block.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		return fmt.Errorf("undoBlock: db error removing block: %w", err) | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 	log.Debug(ctx, "Block undone") | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } |