diff --git a/doc/manual/match.md b/doc/manual/match.md index 5a3f660..e948089 100644 --- a/doc/manual/match.md +++ b/doc/manual/match.md @@ -70,6 +70,17 @@ async def example(room, message): #Respond to help command ``` +It is also possible to match by mention of the bot's username, matrix ID, etc. +In the next example, we can use the prefix or mention the bot to show its help message. + +```python +bot.listener.on_message_event +async def help(room, message): + match = botlib.MessageMatch(room, message, bot, "!") + if match.command("help") and (match.prefix() or match.mention()): + #Respond to help command +``` + A list of methods for the Match class is shown below. [Methods from the Match class](#match-methods) can also be used with the MessageMatch class. #### List of Methods: @@ -78,5 +89,6 @@ A list of methods for the Match class is shown below. [Methods from the Match cl | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MessageMatch.command()` or `MessageMatch.command(command)` | The "command" is the beginning of messages that are intended to be commands, but after the prefix; e.g. "help". Returns the command if the command argument is empty. Returns True if the command argument is equivalent to the command. | | `MessageMatch.prefix()` | Returns True if the message begins with the prefix specified during the initialization of the instance of the MessageMatch class. Returns True if no prefix was specified during the initialization. | +| `MessageMatch.mention()` | Returns True if the message begins with the bot's display name, disambiguated display name, matrix ID, or pill (HTML link to the bot via matrix.to) if formatted_body is present. | | `MessageMatch.args()` | Returns a list of strings; each string is part of the message separated by a space, with the exception of the part of the message before the first space (the prefix and command). Returns an empty list if it is a single-word command. | -| `MessageMatch.contains(string)` | Returns True if the message contains the value specified in the string argument. | \ No newline at end of file +| `MessageMatch.contains(string)` | Returns True if the message contains the value specified in the string argument. | diff --git a/simplematrixbotlib/bot.py b/simplematrixbotlib/bot.py index 969e04e..20177c9 100644 --- a/simplematrixbotlib/bot.py +++ b/simplematrixbotlib/bot.py @@ -39,7 +39,7 @@ async def main(self): resp = await self.async_client.sync(timeout=65536, - full_state=False) #Ignore prior messages + full_state=True) #Ignore prior messages if isinstance(resp, SyncResponse): print(f"Connected to {self.async_client.homeserver} as {self.async_client.user_id} ({self.async_client.device_id})") diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index 8d98516..bc39539 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -1,3 +1,5 @@ +from typing import Union, Optional + class Match: """ Class with methods to filter events @@ -83,7 +85,27 @@ def __init__(self, room, event, bot, prefix="") -> None: super().__init__(room, event, bot) self._prefix = prefix - def command(self, command=None): + """Forms of identification""" + self._own_user_id = room.own_user_id + self._own_nio_user = self.room.users[self._own_user_id] + self._own_disambiguated_name = self._own_nio_user.disambiguated_name + self._own_display_name = self._own_nio_user.display_name + self._own_display_name_colon = f"{self._own_display_name}:" + self._own_pill = f"" + + self.mention() # Set self._mention_id_length + self._body_without_prefix = self.event.body[len(self._prefix):] + self._body_without_mention = self.event.body[self._mention_id_length:] + + if self.mention(): + body = self._body_without_mention + elif self.prefix(): + body = self._body_without_prefix + else: + body = self.event.body + self._split_body = body.split() + + def command(self, command: Optional[str] = None) -> Union[bool, str]: """ Parameters ---------- @@ -99,18 +121,19 @@ def command(self, command=None): Returns the string after the prefix and before the first space if no arg is passed to this method. """ - if self._prefix == self.event.body[0:len(self._prefix)]: - body_without_prefix = self.event.body[len(self._prefix):] - else: - body_without_prefix = self.event.body - - if not body_without_prefix: - return [] + if not (self._body_without_prefix and self._body_without_mention): + """Body is empty after removing prefix or mention""" + if command is None: + return "" + elif not command: + return True + else: + return False - if command: - return body_without_prefix.split()[0] == command + if command is not None: + return self._split_body[0] == command else: - return body_without_prefix.split()[0] + return self._split_body[0] def prefix(self): """ @@ -121,7 +144,24 @@ def prefix(self): Returns True if the message begins with the prefix, and False otherwise. If there is no prefix specified during the creation of this MessageMatch object, then return True. """ - return self.event.body.startswith(self._prefix) + return self.event.body[self._mention_id_length:].startswith(self._prefix) + + def mention(self): + """ + + Returns + ------- + boolean + Returns True if the message begins with the bot's username, MXID, or pill targeting the MXID, and False otherwise. + """ + + for id in [self._own_disambiguated_name, self._own_display_name, self._own_user_id, self._own_display_name_colon]: + if self.event.body.startswith(id): + self._mention_id_length = len(id)+1 + return True + self._mention_id_length = 0 + + return False def args(self): """ @@ -129,10 +169,10 @@ def args(self): Returns ------- list - Returns a list of strings that are the "words" of the message, except for the first "word", which would be the command. + Returns a list of strings that are the "words" of the message, except for the first "word", which would be the prefix/mention + command. """ - return self.event.body.split()[1:] + return self._split_body[1:] def contains(self, string): """ diff --git a/tests/match/test_messagematch.py b/tests/match/test_messagematch.py index 162d19f..9367dbc 100644 --- a/tests/match/test_messagematch.py +++ b/tests/match/test_messagematch.py @@ -2,6 +2,18 @@ from unittest import mock mock_room = mock.MagicMock() +mock_room.own_user_id = "@bot:matrix.org" +mock_user = mock.MagicMock() +mock_user.display_name = "bot" +mock_user.disambiguated_name = f"{mock_user.display_name} ({mock_room.own_user_id})" +mock_room.users = {mock_room.own_user_id: mock_user} + +mock_room2 = mock.MagicMock() +mock_room2.own_user_id = "@bot:matrix.org" +mock_user2 = mock.MagicMock() +mock_user2.display_name = "bot" +mock_user2.disambiguated_name = f"{mock_user2.display_name} ({mock_room2.own_user_id})" +mock_room2.users = {mock_room2.own_user_id: mock_user2} mock_event = mock.MagicMock() mock_event.body = "p!help example" @@ -9,15 +21,49 @@ mock_event2 = mock.MagicMock() mock_event2.body = "p!help" +mock_event3 = mock.MagicMock() +mock_event3.body = "bot help" +mock_event3.formatted_body = None +mock_event4 = mock.MagicMock() +mock_event4.body = "bot: help" +mock_event4.formatted_body = None +mock_event5 = mock.MagicMock() +mock_event5.body = f"{mock_room2.own_user_id} help" +mock_event5.formatted_body = None +mock_event6 = mock.MagicMock() +mock_event6.body = f"bot ({mock_room2.own_user_id}) help" +mock_event6.formatted_body = None +mock_event7 = mock.MagicMock() +mock_event7.body = "something else" +mock_event7.formatted_body = "bot help" +mock_event8 = mock.MagicMock() +mock_event8.body = "bottom help" +mock_event8.formatted_body = None + +mock_event9 = mock.MagicMock() +mock_event9.body = "p!" + mock_bot = mock.MagicMock() +mock_bot.creds.username = "bot" +mock_bot.creds.homeserver = "https://matrix.org" prefix = "p!" prefix2 = "!!" -match = MessageMatch(mock_room, mock_event, mock_bot, prefix) -match2 = MessageMatch(mock_room, mock_event, mock_bot) -match3 = MessageMatch(mock_room, mock_event, mock_bot, prefix2) -match4 = MessageMatch(mock_room, mock_event2, mock_bot, prefix) +match = MessageMatch(mock_room, mock_event, mock_bot, prefix) # prefix match +match2 = MessageMatch(mock_room, mock_event, mock_bot) # no prefix given +match3 = MessageMatch(mock_room, mock_event, mock_bot, prefix2) # wrong prefix given +match4 = MessageMatch(mock_room, mock_event2, mock_bot, prefix) # no arguments given + +match5 = MessageMatch(mock_room2, mock_event3, mock_bot, prefix) # mention with display name +match6 = MessageMatch(mock_room2, mock_event4, mock_bot, prefix) # mention with colon +match7 = MessageMatch(mock_room2, mock_event5, mock_bot, prefix) # mention with user id +match8 = MessageMatch(mock_room2, mock_event6, mock_bot, prefix) # mention with disambiguated name +match9 = MessageMatch(mock_room2, mock_event7, mock_bot, prefix) # mention with pill +match10 = MessageMatch(mock_room2, mock_event8, mock_bot, prefix) # mention someone else +match11 = MessageMatch(mock_room2, mock_event3, mock_bot) # mention without prefix + +match12 = MessageMatch(mock_room, mock_event9, mock_bot, prefix) # prefix match with empty command def test_init(): assert issubclass(MessageMatch, Match) @@ -30,11 +76,38 @@ def test_command(): assert match2.command() == "p!help" assert match2.command("p!help") == True + assert match12.command() == "" + assert match12.command("") == True + +def test_mention(): + assert match5.command() == "help" + assert match5.mention() == True + + assert match6.command() == "help" + assert match6.mention() == True + + assert match7.command() == "help" + assert match7.mention() == True + + assert match8.command() == "help" + assert match8.mention() == True + + assert match9.command() == "help" + assert match9.mention() == True + + assert match10.command() == "bottom" + assert match10.mention() == False + + assert match11.command() == "help" + assert match11.mention() == True + def test_prefix(): assert match.prefix() == True assert match3.prefix() == False - #assert match2.prefix() == True + assert match2.prefix() == True + + assert match12.prefix() == True def test_args(): assert match.args() == ["example"]