kopia lustrzana https://github.com/snarfed/bridgy-fed
basic anti-spam: add new Protocol.REQUIRES_AVATAR/NAME constants
...and start returning `blocked` from User.status for themin-reply-to-bridged
rodzic
a7c099fc08
commit
de0af66979
|
@ -132,8 +132,9 @@ class ActivityPub(User, Protocol):
|
|||
if status:
|
||||
return status
|
||||
|
||||
return util.domain_or_parent_in(util.domain_from_link(self.key.id()),
|
||||
WEB_OPT_OUT_DOMAINS)
|
||||
if util.domain_or_parent_in(util.domain_from_link(self.key.id()),
|
||||
WEB_OPT_OUT_DOMAINS):
|
||||
return 'opt-out'
|
||||
|
||||
|
||||
@classmethod
|
||||
|
|
22
models.py
22
models.py
|
@ -341,13 +341,17 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
|
|||
|
||||
@ndb.ComputedProperty
|
||||
def status(self):
|
||||
"""Whether this user has explicitly opted out of Bridgy Fed.
|
||||
"""Whether this user is blocked or opted out.
|
||||
|
||||
Optional. Current possible values:
|
||||
* ``opt-out``
|
||||
|
||||
Currently just looks for ``#nobridge`` or ``#nobot`` in the profile
|
||||
description/bio.
|
||||
* ``opt-out``: if ``#nobridge`` or ``#nobot`` is in the profile
|
||||
description/bio, or if the user or domain has manually opted out.
|
||||
Some protocols also have protocol-specific opt out logic, eg Bluesky
|
||||
accounts that have disabled logged out view.
|
||||
* ``blocked``: if the user fails our validation checks, eg
|
||||
``REQUIRES_NAME`` or ``REQUIRES_AVATAR`` if either of those are
|
||||
``True` for this protocol.
|
||||
|
||||
Duplicates ``util.is_opt_out`` in Bridgy!
|
||||
|
||||
|
@ -359,6 +363,10 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
|
|||
if not self.obj or not self.obj.as1:
|
||||
return None
|
||||
|
||||
if ((self.REQUIRES_AVATAR and not self.obj.as1.get('image')) or
|
||||
(self.REQUIRES_NAME and not self.obj.as1.get('displayName'))):
|
||||
return 'blocked'
|
||||
|
||||
if not as1.is_public(self.obj.as1, unlisted=False):
|
||||
return 'opt-out'
|
||||
|
||||
|
@ -368,15 +376,13 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
|
|||
if tag in text:
|
||||
return 'opt-out'
|
||||
|
||||
return None
|
||||
|
||||
def is_enabled(self, to_proto):
|
||||
"""Returns True if this user can be bridged to a given protocol.
|
||||
|
||||
Reasons this might return False:
|
||||
* We haven't turned on bridging these two protocols yet.
|
||||
* The user is opted out.
|
||||
* The user is on a domain that's opted out.
|
||||
* The user is opted out or blocked.
|
||||
* The user is on a domain that's opted out or blocked.
|
||||
* The from protocol requires opt in, and the user hasn't opted in.
|
||||
|
||||
Args:
|
||||
|
|
12
protocol.py
12
protocol.py
|
@ -83,6 +83,12 @@ class Protocol:
|
|||
HAS_COPIES (bool): whether this protocol is push and needs us to
|
||||
proactively create "copy" users and objects, as opposed to pulling
|
||||
converted objects on demand
|
||||
REQUIRES_AVATAR (bool): whether accounts on this protocol are required
|
||||
to have a profile picture. If they don't, their ``User.status`` will be
|
||||
``blocked``.
|
||||
REQUIRES_NAME (bool): whether accounts on this protocol are required to
|
||||
have a profile name that's different than their handle or id. If they
|
||||
don't, their ``User.status`` will be ``blocked``.
|
||||
DEFAULT_ENABLED_PROTOCOLS (list of str): labels of other protocols that
|
||||
are automatically enabled for this protocol to bridge into
|
||||
"""
|
||||
|
@ -93,6 +99,8 @@ class Protocol:
|
|||
CONTENT_TYPE = None
|
||||
HAS_FOLLOW_ACCEPTS = False
|
||||
HAS_COPIES = False
|
||||
REQUIRES_AVATAR = False
|
||||
REQUIRES_NAME = False
|
||||
DEFAULT_ENABLED_PROTOCOLS = ()
|
||||
|
||||
def __init__(self):
|
||||
|
@ -237,7 +245,7 @@ class Protocol:
|
|||
# load user so that we follow use_instead
|
||||
existing = cls.get_by_id(id, allow_opt_out=True)
|
||||
if existing:
|
||||
if existing.status == 'opt-out':
|
||||
if existing.status is not None:
|
||||
return None
|
||||
return existing.key
|
||||
|
||||
|
@ -359,7 +367,7 @@ class Protocol:
|
|||
for proto in candidates:
|
||||
user = proto.query(proto.handle == handle).get()
|
||||
if user:
|
||||
if user.status == 'opt-out':
|
||||
if user.status is not None:
|
||||
return (None, None)
|
||||
logger.info(f' user {user.key} owns handle {handle}')
|
||||
return (proto, user.key.id())
|
||||
|
|
|
@ -281,6 +281,24 @@ class UserTest(TestCase):
|
|||
user = User(manual_opt_out=True)
|
||||
self.assertEqual('opt-out', user.status)
|
||||
|
||||
@patch.object(Fake, 'REQUIRES_AVATAR', True)
|
||||
def test_requires_avatar(self):
|
||||
user = self.make_user(id='fake:user', cls=Fake,
|
||||
obj_as1={'displayName': 'Alice'})
|
||||
self.assertEqual('blocked', user.status)
|
||||
|
||||
user.obj.our_as1['image'] = 'http://pic'
|
||||
self.assertIsNone(user.status)
|
||||
|
||||
@patch.object(Fake, 'REQUIRES_NAME', True)
|
||||
def test_requires_avatar(self):
|
||||
user = self.make_user(id='fake:user', cls=Fake,
|
||||
obj_as1={'image': 'http://pic'})
|
||||
self.assertEqual('blocked', user.status)
|
||||
|
||||
user.obj.our_as1['displayName'] = 'Alice'
|
||||
self.assertIsNone(user.status)
|
||||
|
||||
def test_get_copy(self):
|
||||
user = Fake(id='x')
|
||||
self.assertEqual('x', user.get_copy(Fake))
|
||||
|
|
Ładowanie…
Reference in New Issue