Skip to content

Commit

Permalink
when a Bluesky user blocks the ap.brid.gy bot, delete their bridged f…
Browse files Browse the repository at this point in the history
…ediverse profile

for #783
  • Loading branch information
snarfed committed Jun 14, 2024
1 parent f21bc2c commit 81319a7
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 129 deletions.
41 changes: 14 additions & 27 deletions protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,8 +912,8 @@ def receive(from_cls, obj, authed_as=None, internal=False):
logger.info("Ignoring block, target isn't one of our protocol domains")
return 'OK', 200

proto.delete_user_copy(from_user)
from_user.disable_protocol(proto)
proto.maybe_delete_copy(from_user)
return 'OK', 200

elif obj.type == 'post':
Expand All @@ -932,8 +932,8 @@ def receive(from_cls, obj, authed_as=None, internal=False):
from_user.enable_protocol(proto)
proto.bot_follow(from_user)
elif content == 'no':
proto.delete_user_copy(from_user)
from_user.disable_protocol(proto)
proto.maybe_delete_copy(from_user)
return 'OK', 200

# fetch actor if necessary
Expand Down Expand Up @@ -1112,43 +1112,23 @@ def bot_follow(bot_cls, user):
user=bot.key.urlsafe())

@classmethod
def maybe_delete_copy(copy_cls, user):
def delete_user_copy(copy_cls, user):
"""Deletes a user's copy actor in a given protocol.
...if ``copy_cls`` 's :attr:`Protocol.HAS_COPIES` is True. Otherwise,
does nothing.
TODO: this should eventually go through receive for protocols that need
to deliver to all followers' targets, eg AP.
Args:
user (User)
"""
if not copy_cls.HAS_COPIES:
return

copy_user_id = user.get_copy(copy_cls)
if not copy_user_id:
logger.warning(f"Tried to delete {user.key} copy for {copy_cls.LABEL}, which doesn't exist!")
return

now = util.now().isoformat()
delete_id = f'{user.key.id()}#delete-copy-{copy_cls.LABEL}-{now}'
delete = Object(id=delete_id, source_protocol=user.LABEL, our_as1={
'id': delete_id,
'objectType': 'activity',
'verb': 'delete',
'actor': user.key.id(),
'object': copy_user_id,
'object': user.key.id(),
})

target_uri = copy_cls.target_for(Object(id=copy_user_id))
delete.undelivered = [Target(protocol=copy_cls.LABEL, uri=target_uri)]
delete.put()

common.create_task(queue='send', obj=delete.key.urlsafe(),
url=target_uri, protocol=copy_cls.LABEL,
user=user.key.urlsafe())
user.deliver(delete, from_user=user, to_proto=copy_cls)

@classmethod
def handle_bare_object(cls, obj, authed_as=None):
Expand Down Expand Up @@ -1219,20 +1199,25 @@ def handle_bare_object(cls, obj, authed_as=None):
error(f'{obj.key.id()} is unchanged, nothing to do', status=204)

@classmethod
def deliver(from_cls, obj, from_user):
def deliver(from_cls, obj, from_user, to_proto=None):
"""Delivers an activity to its external recipients.
Args:
obj (models.Object): activity to deliver
from_user (models.User): user (actor) this activity is from
to_proto (protocol.Protocol): optional; if provided, only deliver to
targets on this protocol
"""
if to_proto:
logger.info(f'Only delivering to {to_proto.LABEL}')

# find delivery targets. maps Target to Object or None
targets = from_cls.targets(obj, from_user=from_user)

if not targets:
obj.status = 'ignored'
obj.put()
error(r'No targets, nothing to do ¯\_(ツ)_/¯', status=204)
return r'No targets, nothing to do ¯\_(ツ)_/¯', 204

# sort targets so order is deterministic for tests, debugging, etc
sorted_targets = sorted(targets.items(), key=lambda t: t[0].uri)
Expand All @@ -1248,6 +1233,8 @@ def deliver(from_cls, obj, from_user):
# enqueue send task for each targets
user = from_user.key.urlsafe()
for i, (target, orig_obj) in enumerate(sorted_targets):
if to_proto and target.protocol != to_proto.LABEL:
continue
orig_obj = orig_obj.key.urlsafe() if orig_obj else ''
common.create_task(queue='send', obj=obj.key.urlsafe(),
url=target.uri, protocol=target.protocol,
Expand Down
2 changes: 1 addition & 1 deletion templates/docs.html
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ <h3 id="about">About</h3>
<li id="opt-out" class="question">How do I opt out and remove my site or account?</li>
<li class="answer">
<p>If you're on the fediverse or Bluesky, and you've opted in but now want to opt out, block the Bridgy Fed bot user for the network you want to opt out of. For example, on the fediverse, block <code>@[email protected]</code>. On Bluesky, block <a href="https://bsky.app/profile/ap.brid.gy">@ap.brid.gy</a>.</p>
<p>Once you've done this, Bridgy Fed will delete your bridged profile in that network, and it will no longer bridge any of your posts or interactions there.</p>
<p><em>Warning: right now, this can't be undone! If you opt in by following a bot user for a given network, and then opt out by blocking it, you can't re-enable the bridge by unblocking and re-following it. <a href="https://github.com/snarfed/bridgy-fed/issues/783">Hopefully this will be possible in the future.</a></em></p>
<p>Once you've done this, Bridgy Fed will no longer bridge any or your posts or interactions in to that network. It also deletes your bridged account in Bluesky, but not (yet) your bridged account in the fediverse. <a href="https://github.com/snarfed/bridgy-fed/issues/783">We're working on it!</a></p>
<p>If you're on the web, feel free to <a href="mailto:[email protected]">email me</a>, or you can put the text <code>#nobridge</code> in the <a href="#web-profile">profile on your home page</a> and then <a href="#update-profile">update your profile</a> on <a href="#user-page">your user page</a>.</p>
</li>

Expand Down
14 changes: 7 additions & 7 deletions tests/test_activitypub.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,13 +914,13 @@ def test_follow_bot_user_enables_protocol(self, _, mock_get, __):
mock_get.return_value = self.as2_resp(ACTOR)

id = 'https://inst/follow'
with self.assertRaises(NoContent):
ActivityPub.receive(Object(id=id, as2={
'type': 'Follow',
'id': id,
'actor': 'https://mas.to/users/swentel',
'object': 'https://eefake.brid.gy/eefake.brid.gy',
}), authed_as='https://mas.to/users/swentel')
_, code = ActivityPub.receive(Object(id=id, as2={
'type': 'Follow',
'id': id,
'actor': 'https://mas.to/users/swentel',
'object': 'https://eefake.brid.gy/eefake.brid.gy',
}), authed_as='https://mas.to/users/swentel')
self.assertEqual(204, code)

self.assertEqual(['https://mas.to/users/swentel'],
ExplicitEnableFake.created_for)
Expand Down
Loading

0 comments on commit 81319a7

Please sign in to comment.