when someone follows a bot user to opt in, have the bot user follow them back

fixes #999. this makes sure we start receiving posts from AP users immediately, instead of waiting until a Bluesky opted in user follows them.
in-reply-to-bridged
Ryan Barrett 2024-05-07 10:54:18 -07:00
rodzic 9765227aa0
commit 4da00192b0
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
5 zmienionych plików z 66 dodań i 6 usunięć

Wyświetl plik

@ -411,7 +411,7 @@ class Protocol:
Returns:
str:
"""
return f'{cls.ABBREV}{SUPERDOMAIN}'
return f'{cls.ABBREV}{common.SUPERDOMAIN}'
@classmethod
def create_for(cls, user):
@ -818,6 +818,7 @@ class Protocol:
logger.info(f'got DM to {to_cc}: {content}')
if content in ('yes', 'ok'):
from_user.enable_protocol(proto)
proto.bot_follow(from_user)
elif content == 'no':
from_user.disable_protocol(proto)
return 'OK', 200
@ -850,6 +851,7 @@ class Protocol:
# follow of one of our protocol bot users; enable that protocol.
# foll through so that we send an accept.
from_user.enable_protocol(proto)
proto.bot_follow(from_user)
from_cls.handle_follow(obj)
@ -965,6 +967,32 @@ class Protocol:
)
accept.put()
@classmethod
def bot_follow(bot_cls, user):
"""Follow a user from a protocol bot user.
...so that the protocol starts sending us their activities, if it needs
a follow for that (eg ActivityPub).
Args:
user (User)
"""
from web import Web
bot = Web.get_by_id(bot_cls.bot_user_id())
now = util.now().isoformat()
logger.info(f'Following {user.key.id()} back from bot user {bot.key.id()}')
follow_back_id = f'https://{bot.key.id()}/#follow-back-{user.key.id()}-{now}'
follow_back = Object(id=follow_back_id, source_protocol='web', our_as1={
'objectType': 'activity',
'verb': 'follow',
'id': follow_back_id,
'actor': bot.key.id(),
'object': user.key.id(),
})
user.send(follow_back, user.target_for(user.obj), from_user=bot)
@classmethod
def handle_bare_object(cls, obj):
"""If obj is a bare object, wraps it in a create or update activity.

Wyświetl plik

@ -872,6 +872,9 @@ class ActivityPubTest(TestCase):
self.assertIsNone(Object.get_by_id(not_public['object']['id']))
def test_follow_bot_user_enables_protocol(self, _, mock_get, __):
# bot user
self.make_user('eefake.brid.gy', cls=Web)
user = self.make_user('https://mas.to/users/swentel', cls=ActivityPub,
obj_as2=ACTOR)
self.assertFalse(user.is_enabled(ExplicitEnableFake))
@ -894,6 +897,9 @@ class ActivityPubTest(TestCase):
self.assertTrue(user.is_enabled(ExplicitEnableFake))
def test_inbox_dm_yes_to_bot_user_enables_protocol(self, *mocks):
# bot user
self.make_user('eefake.brid.gy', cls=Web)
user = self.make_user(ACTOR['id'], cls=ActivityPub)
self.assertFalse(user.is_enabled(ExplicitEnableFake))

Wyświetl plik

@ -406,8 +406,19 @@ class IntegrationTests(TestCase):
self.assertEqual(['app.bsky.actor.profile'], list(records.keys()))
self.assertEqual(['self'], list(records['app.bsky.actor.profile'].keys()))
# bot user follows back
args, kwargs = mock_post.call_args_list[1]
self.assert_equals(('http://inst/inbox',), args)
self.assert_equals({
'type': 'Follow',
'id': 'https://bsky.brid.gy/r/https://bsky.brid.gy/#follow-back-https://inst/alice-2022-01-02T03:04:05+00:00',
'actor': 'https://bsky.brid.gy/bsky.brid.gy',
'object': 'https://inst/alice',
}, json_loads(kwargs['data']), ignore=['to', '@context'])
# accept user's follow
args, kwargs = mock_post.call_args_list[2]
self.assert_equals(('http://inst/inbox',), args)
self.assert_equals({
'type': 'Accept',
'id': 'http://localhost/r/bsky.brid.gy/followers#accept-http://inst/follow',

Wyświetl plik

@ -1855,6 +1855,9 @@ class ProtocolReceiveTest(TestCase):
}, obj.key.get().our_as1)
def test_follow_and_block_protocol_user_sets_enabled_protocols(self):
# bot user
self.make_user('fa.brid.gy', cls=Web)
follow = {
'objectType': 'activity',
'verb': 'follow',
@ -1886,9 +1889,13 @@ class ProtocolReceiveTest(TestCase):
self.assertEqual(['fake'], user.enabled_protocols)
self.assertEqual(['eefake:user'], Fake.created_for)
self.assertTrue(user.is_enabled(Fake))
self.assertEqual([('fa.brid.gy/followers#accept-eefake:follow',
'eefake:user:target')],
ExplicitEnableFake.sent)
follow_back_id = 'https://fa.brid.gy/#follow-back-eefake:user-2022-01-02T03:04:05+00:00'
self.assertEqual([
# fa.brid.gy follows back
(follow_back_id, 'eefake:user:target'),
('fa.brid.gy/followers#accept-eefake:follow', 'eefake:user:target'),
], ExplicitEnableFake.sent)
# another follow should be a noop
follow['id'] += '2'
@ -1908,6 +1915,9 @@ class ProtocolReceiveTest(TestCase):
self.assertFalse(user.is_enabled(Fake))
def test_follow_bot_user_refreshes_profile(self):
# bot user
self.make_user('fa.brid.gy', cls=Web)
# store profile that's opted out
user = self.make_user('eefake:user', cls=ExplicitEnableFake, obj_as1={
'id': 'eefake:user',
@ -1936,6 +1946,9 @@ class ProtocolReceiveTest(TestCase):
self.assertEqual(['eefake:user'], ExplicitEnableFake.fetched)
def test_dm_no_yes_sets_enabled_protocols(self):
# bot user
self.make_user('fa.brid.gy', cls=Web)
dm = {
'objectType': 'note',
'id': 'eefake:dm',

Wyświetl plik

@ -94,6 +94,7 @@ class Fake(User, protocol.Protocol):
def create_for(cls, user):
assert not user.get_copy(cls)
id = user.key.id()
logger.info(f'{cls.__name__}.create_for {id}')
cls.created_for.append(id)
add(user.copies, Target(uri=ids.translate_user_id(id=id, from_=user, to=cls),
protocol=cls.LABEL))
@ -123,7 +124,7 @@ class Fake(User, protocol.Protocol):
@classmethod
def send(cls, obj, url, from_user=None, orig_obj=None):
logger.info(f'{cls.__name__}.send {url}')
logger.info(f'{cls.__name__}.send {url} {obj.as1}')
cls.sent.append((obj.key.id(), url))
return True
@ -146,7 +147,8 @@ class Fake(User, protocol.Protocol):
@classmethod
def target_for(cls, obj, shared=False):
assert obj.source_protocol in (cls.LABEL, cls.ABBREV, 'ui', None)
assert obj.source_protocol in (cls.LABEL, cls.ABBREV, 'ui', None), \
obj.source_protocol
return 'shared:target' if shared else f'{obj.key.id()}:target'
@classmethod