undo.go (6386B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package federatingdb 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 25 "codeberg.org/gruf/go-logger/v2/level" 26 "github.com/superseriousbusiness/activity/streams/vocab" 27 "github.com/superseriousbusiness/gotosocial/internal/ap" 28 "github.com/superseriousbusiness/gotosocial/internal/db" 29 "github.com/superseriousbusiness/gotosocial/internal/gtscontext" 30 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 31 "github.com/superseriousbusiness/gotosocial/internal/log" 32 ) 33 34 func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error { 35 l := log.WithContext(ctx) 36 37 if log.Level() >= level.DEBUG { 38 i, err := marshalItem(undo) 39 if err != nil { 40 return err 41 } 42 l = l.WithField("undo", i) 43 l.Debug("entering Undo") 44 } 45 46 receivingAccount, _, internal := extractFromCtx(ctx) 47 if internal { 48 return nil // Already processed. 49 } 50 51 undoObject := undo.GetActivityStreamsObject() 52 if undoObject == nil { 53 return errors.New("UNDO: no object set on vocab.ActivityStreamsUndo") 54 } 55 56 for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() { 57 t := iter.GetType() 58 if t == nil { 59 continue 60 } 61 62 switch t.GetTypeName() { 63 case ap.ActivityFollow: 64 if err := f.undoFollow(ctx, receivingAccount, undo, t); err != nil { 65 return err 66 } 67 case ap.ActivityLike: 68 if err := f.undoLike(ctx, receivingAccount, undo, t); err != nil { 69 return err 70 } 71 case ap.ActivityAnnounce: 72 // todo: undo boost / reblog / announce 73 case ap.ActivityBlock: 74 if err := f.undoBlock(ctx, receivingAccount, undo, t); err != nil { 75 return err 76 } 77 } 78 } 79 80 return nil 81 } 82 83 func (f *federatingDB) undoFollow( 84 ctx context.Context, 85 receivingAccount *gtsmodel.Account, 86 undo vocab.ActivityStreamsUndo, 87 t vocab.Type, 88 ) error { 89 Follow, ok := t.(vocab.ActivityStreamsFollow) 90 if !ok { 91 return errors.New("undoFollow: couldn't parse vocab.Type into vocab.ActivityStreamsFollow") 92 } 93 94 // Make sure the undo actor owns the target. 95 if !sameActor(undo.GetActivityStreamsActor(), Follow.GetActivityStreamsActor()) { 96 // Ignore this Activity. 97 return nil 98 } 99 100 follow, err := f.typeConverter.ASFollowToFollow(ctx, Follow) 101 if err != nil { 102 return fmt.Errorf("undoFollow: error converting ActivityStreams Follow to follow: %w", err) 103 } 104 105 // Ensure addressee is follow target. 106 if follow.TargetAccountID != receivingAccount.ID { 107 // Ignore this Activity. 108 return nil 109 } 110 111 // Delete any existing follow with this URI. 112 if err := f.state.DB.DeleteFollowByURI(ctx, follow.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { 113 return fmt.Errorf("undoFollow: db error removing follow: %w", err) 114 } 115 116 // Delete any existing follow request with this URI. 117 if err := f.state.DB.DeleteFollowRequestByURI(ctx, follow.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { 118 return fmt.Errorf("undoFollow: db error removing follow request: %w", err) 119 } 120 121 log.Debug(ctx, "Follow undone") 122 return nil 123 } 124 125 func (f *federatingDB) undoLike( 126 ctx context.Context, 127 receivingAccount *gtsmodel.Account, 128 undo vocab.ActivityStreamsUndo, 129 t vocab.Type, 130 ) error { 131 Like, ok := t.(vocab.ActivityStreamsLike) 132 if !ok { 133 return errors.New("undoLike: couldn't parse vocab.Type into vocab.ActivityStreamsLike") 134 } 135 136 // Make sure the undo actor owns the target. 137 if !sameActor(undo.GetActivityStreamsActor(), Like.GetActivityStreamsActor()) { 138 // Ignore this Activity. 139 return nil 140 } 141 142 fave, err := f.typeConverter.ASLikeToFave(ctx, Like) 143 if err != nil { 144 return fmt.Errorf("undoLike: error converting ActivityStreams Like to fave: %w", err) 145 } 146 147 // Ensure addressee is fave target. 148 if fave.TargetAccountID != receivingAccount.ID { 149 // Ignore this Activity. 150 return nil 151 } 152 153 // Ignore URI on Likes, since we often get multiple Likes 154 // with the same target and account ID, but differing URIs. 155 // Instead, we'll select using account and target status. 156 // Regardless of the URI, we can read an Undo Like to mean 157 // "I don't want to fave this post anymore". 158 fave, err = f.state.DB.GetStatusFave(gtscontext.SetBarebones(ctx), fave.AccountID, fave.StatusID) 159 if err != nil { 160 if errors.Is(err, db.ErrNoEntries) { 161 // We didn't have a like/fave 162 // for this combo anyway, ignore. 163 return nil 164 } 165 // Real error. 166 return fmt.Errorf("undoLike: db error getting fave from %s targeting %s: %w", fave.AccountID, fave.StatusID, err) 167 } 168 169 // Delete the status fave. 170 if err := f.state.DB.DeleteStatusFaveByID(ctx, fave.ID); err != nil { 171 return fmt.Errorf("undoLike: db error deleting fave %s: %w", fave.ID, err) 172 } 173 174 log.Debug(ctx, "Like undone") 175 return nil 176 } 177 178 func (f *federatingDB) undoBlock( 179 ctx context.Context, 180 receivingAccount *gtsmodel.Account, 181 undo vocab.ActivityStreamsUndo, 182 t vocab.Type, 183 ) error { 184 Block, ok := t.(vocab.ActivityStreamsBlock) 185 if !ok { 186 return errors.New("undoBlock: couldn't parse vocab.Type into vocab.ActivityStreamsBlock") 187 } 188 189 // Make sure the undo actor owns the target. 190 if !sameActor(undo.GetActivityStreamsActor(), Block.GetActivityStreamsActor()) { 191 // Ignore this Activity. 192 return nil 193 } 194 195 block, err := f.typeConverter.ASBlockToBlock(ctx, Block) 196 if err != nil { 197 return fmt.Errorf("undoBlock: error converting ActivityStreams Block to block: %w", err) 198 } 199 200 // Ensure addressee is block target. 201 if block.TargetAccountID != receivingAccount.ID { 202 // Ignore this Activity. 203 return nil 204 } 205 206 // Delete any existing BLOCK 207 if err := f.state.DB.DeleteBlockByURI(ctx, block.URI); err != nil && !errors.Is(err, db.ErrNoEntries) { 208 return fmt.Errorf("undoBlock: db error removing block: %w", err) 209 } 210 211 log.Debug(ctx, "Block undone") 212 return nil 213 }