Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Allow using username as receiver #101

Merged
merged 11 commits into from
Mar 9, 2025
28 changes: 28 additions & 0 deletions signalbot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ def _resolve_receiver(self, receiver: str) -> str:
if self._is_valid_uuid(receiver):
return receiver

if self._is_username(receiver):
return receiver

if self._is_group_id(receiver):
return receiver

Expand Down Expand Up @@ -306,6 +309,31 @@ def _is_valid_uuid(self, receiver_uuid: str):
except ValueError:
return False

def _is_username(self, receiver_username: str) -> bool:
"""
Check if username has correct format, as described in https://support.signal.org/hc/en-us/articles/6712070553754-Phone-Number-Privacy-and-Usernames#username_req
Additionally, cannot have more than 9 digits and the digits cannot be 00.
"""
split_username = receiver_username.split(".")
if len(split_username) == 2:
characters = split_username[0]
digits = split_username[1]
if len(characters) < 3 or len(characters) > 32:
return False
if not re.match(r"^[A-Za-z\d_]+$", characters):
return False
if len(digits) < 2 or len(digits) > 9:
return False
try:
digits = int(digits)
if digits == 0:
return False
return True
except ValueError:
return False
else:
return False

def _is_group_id(self, group_id: str) -> bool:
"""Check if group_id has the right format, e.g.

Expand Down
33 changes: 33 additions & 0 deletions tests/test_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,39 @@ def test_listen_valid_invalid_phone_number(self):
self.assertSetEqual(self.signal_bot.user_chats, expected_user_chats)


class TestUsernameValidation(BotTestCase):
def test_valid_username(self):
valid_usernames = [
"Usr.99",
"UserName.99",
"username.999999999",
"UserName99.99",
"_Use_rName99_.99",
"username.999999999",
"usernameeeeeeeeeeeeeeeeeeeeeeeee.999999999",
]
for valid_username in valid_usernames:
self.assertTrue(self.signal_bot._is_username(valid_username))

def test_invalid_username(self):
invalid_usernames = [
"Us.99",
"Usr.9",
".UserName99",
".UserName.99",
"UserName99",
"UserName99.",
"username.9999999999",
"user@name.999",
"UserName99.0",
"UserName99.00",
"UserName99.000000000",
".usernameeeeeeeeeeeeeeeeeeeeeeeeee.99",
]
for invalid_username in invalid_usernames:
self.assertFalse(self.signal_bot._is_username(invalid_username))


class TestRegisterCommand(BotTestCase):
def test_register_one_command(self):
self.signal_bot.register(DummyCommand())
Expand Down