From 33776bad9855c4337557d3badfe3af74e9c9af1a Mon Sep 17 00:00:00 2001 From: HarHarLinks Date: Wed, 17 Nov 2021 16:55:50 +0100 Subject: [PATCH 01/17] add bot trigger: mention --- simplematrixbotlib/bot.py | 2 +- simplematrixbotlib/match.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) 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..36f245b 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -82,6 +82,10 @@ def __init__(self, room, event, bot, prefix="") -> None: """ super().__init__(room, event, bot) self._prefix = prefix + bot_user = self.room.users[self.room.own_user_id] + self._display_name = bot_user.display_name + self._disambiguated_name = bot_user.disambiguated_name + self._pill = f'' def command(self, command=None): """ @@ -101,6 +105,21 @@ def command(self, command=None): if self._prefix == self.event.body[0:len(self._prefix)]: body_without_prefix = self.event.body[len(self._prefix):] + elif self.mention(): + # the order is important here! + # note: we assume that body and formatted_body, if present, match as a workaraound of cleaning html + if self.event.body.startswith(self._disambiguated_name): + body_without_prefix = self.event.body[len(self._disambiguated_name):] + elif self.event.body.startswith(self._display_name): + body_without_prefix = self.event.body[len(self._display_name):] + elif self.event.body.startswith(self.room.own_user_id): + body_without_prefix = self.event.body[len(self.room.own_user_id):] + elif self.event.formatted_body.startswith(self._pill): + name = self.event.formatted_body[len(self._pill):self.event.formatted_body.index('')] + body_without_prefix = body_without_prefix = self.event.body[len(name):] + # mentioning may include a : (colon) as inserted by Element when clicking on a user + if body_without_prefix.startswith(':'): + body_without_prefix = body_without_prefix[1:] else: body_without_prefix = self.event.body @@ -123,6 +142,22 @@ def prefix(self): return self.event.body.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 i in [self._display_name, self._disambiguated_name, self.room.own_user_id, self._pill]: + body = self.event.formatted_body or self.event.body + if body.startswith(i): + return True + + return False + def args(self): """ From d6a3a342a7fe90d366c4db7d6559d1f36f0c7f2c Mon Sep 17 00:00:00 2001 From: HarHarLinks Date: Wed, 17 Nov 2021 18:46:43 +0100 Subject: [PATCH 02/17] fix api.args() for mentions --- simplematrixbotlib/match.py | 55 ++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index 36f245b..4807cce 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -86,6 +86,7 @@ def __init__(self, room, event, bot, prefix="") -> None: self._display_name = bot_user.display_name self._disambiguated_name = bot_user.disambiguated_name self._pill = f'' + self._body_without_prefix = None def command(self, command=None): """ @@ -103,33 +104,37 @@ 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):] - elif self.mention(): - # the order is important here! - # note: we assume that body and formatted_body, if present, match as a workaraound of cleaning html - if self.event.body.startswith(self._disambiguated_name): - body_without_prefix = self.event.body[len(self._disambiguated_name):] - elif self.event.body.startswith(self._display_name): - body_without_prefix = self.event.body[len(self._display_name):] - elif self.event.body.startswith(self.room.own_user_id): - body_without_prefix = self.event.body[len(self.room.own_user_id):] - elif self.event.formatted_body.startswith(self._pill): - name = self.event.formatted_body[len(self._pill):self.event.formatted_body.index('')] - body_without_prefix = body_without_prefix = self.event.body[len(name):] - # mentioning may include a : (colon) as inserted by Element when clicking on a user - if body_without_prefix.startswith(':'): - body_without_prefix = body_without_prefix[1:] - else: - body_without_prefix = self.event.body - - if not body_without_prefix: - return [] + # we cache this part + if not self._body_without_prefix: + if self._prefix == self.event.body[0:len(self._prefix)]: + self._body_without_prefix = self.event.body[len(self._prefix):] + elif self.mention(): + # the order is important here! + # note: we assume that body and formatted_body, if present, match as a workaraound of cleaning html + if self.event.body.startswith(self._disambiguated_name): + self._body_without_prefix = self.event.body[len(self._disambiguated_name):] + elif self.event.body.startswith(self._display_name): + self._body_without_prefix = self.event.body[len(self._display_name):] + elif self.event.body.startswith(self.room.own_user_id): + self._body_without_prefix = self.event.body[len(self.room.own_user_id):] + elif self.event.formatted_body.startswith(self._pill): + name = self.event.formatted_body[len(self._pill):self.event.formatted_body.index('')] + self._body_without_prefix = self.event.body[len(name):] + # mentioning may include a : (colon) as inserted by Element when clicking on a user + if self._body_without_prefix.startswith(':'): + self._body_without_prefix = self._body_without_prefix[1:] + # trim leading whitespace after the mention + self._body_without_prefix = self._body_without_prefix.strip() + else: + self._body_without_prefix = self.event.body + + if not self._body_without_prefix: + return [] if command: - return body_without_prefix.split()[0] == command + return self._body_without_prefix.split()[0] == command else: - return body_without_prefix.split()[0] + return self._body_without_prefix.split()[0] def prefix(self): """ @@ -167,7 +172,7 @@ def args(self): Returns a list of strings that are the "words" of the message, except for the first "word", which would be the command. """ - return self.event.body.split()[1:] + return self._body_without_prefix.split()[1:] def contains(self, string): """ From e73f24b727d60b557c20ecd76abf56127dd802fd Mon Sep 17 00:00:00 2001 From: krazykirby99999 Date: Wed, 24 Nov 2021 18:49:23 -0600 Subject: [PATCH 03/17] Fix messagemath.mention. "body" is now a tuple instead of a list --- simplematrixbotlib/match.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index 4807cce..6a44de1 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -156,11 +156,11 @@ def mention(self): Returns True if the message begins with the bot's username, MXID, or pill targeting the MXID, and False otherwise. """ - for i in [self._display_name, self._disambiguated_name, self.room.own_user_id, self._pill]: - body = self.event.formatted_body or self.event.body - if body.startswith(i): - return True - + for body in (self.event.formatted_body, self.event.body): + for id in [self._display_name, self._disambiguated_name, self.room.own_user_id, self._pill]: + if body.startswith(id): + return True + return False def args(self): From 2d63cbe02498c7ecd763adcfe4d6141d01ed48b1 Mon Sep 17 00:00:00 2001 From: krazykirby99999 Date: Wed, 24 Nov 2021 19:20:45 -0600 Subject: [PATCH 04/17] Refactor messagematch.command --- simplematrixbotlib/match.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index 6a44de1..9be79ff 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -111,26 +111,26 @@ def command(self, command=None): elif self.mention(): # the order is important here! # note: we assume that body and formatted_body, if present, match as a workaraound of cleaning html - if self.event.body.startswith(self._disambiguated_name): - self._body_without_prefix = self.event.body[len(self._disambiguated_name):] - elif self.event.body.startswith(self._display_name): - self._body_without_prefix = self.event.body[len(self._display_name):] - elif self.event.body.startswith(self.room.own_user_id): - self._body_without_prefix = self.event.body[len(self.room.own_user_id):] - elif self.event.formatted_body.startswith(self._pill): - name = self.event.formatted_body[len(self._pill):self.event.formatted_body.index('')] + id_matched = False + for id in (self._disambiguated_name, self._display_name, self.room.own_user_id): + if self.event.body.startswith(id) and not id_matched: + self._body_without_prefix = self.event.body[len(id):] + id_matched = True + + if self.event.formatted_body.startswith(self._pill) and not id_matched: + name = self.event.formatted_body[len():self.event.formatted_body.index('')] self._body_without_prefix = self.event.body[len(name):] + # mentioning may include a : (colon) as inserted by Element when clicking on a user if self._body_without_prefix.startswith(':'): self._body_without_prefix = self._body_without_prefix[1:] + # trim leading whitespace after the mention self._body_without_prefix = self._body_without_prefix.strip() + else: self._body_without_prefix = self.event.body - if not self._body_without_prefix: - return [] - if command: return self._body_without_prefix.split()[0] == command else: From 57147ec3c11dd456e2beec9e96d3e0e04d80ba98 Mon Sep 17 00:00:00 2001 From: HarHarLinks Date: Thu, 25 Nov 2021 17:52:03 +0100 Subject: [PATCH 05/17] =?UTF-8?q?docs:=20=F0=9F=93=9D=20Add=20match.mentio?= =?UTF-8?q?n()=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/manual/match.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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. | From 9a2e92442629a2026a633eff37895eceadffc0d9 Mon Sep 17 00:00:00 2001 From: HarHarLinks Date: Thu, 25 Nov 2021 19:28:00 +0100 Subject: [PATCH 06/17] refactor MessageMatch.mention() --- simplematrixbotlib/match.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index 9be79ff..bfe0d3b 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -156,12 +156,18 @@ def mention(self): Returns True if the message begins with the bot's username, MXID, or pill targeting the MXID, and False otherwise. """ - for body in (self.event.formatted_body, self.event.body): - for id in [self._display_name, self._disambiguated_name, self.room.own_user_id, self._pill]: - if body.startswith(id): + for id in [self._display_name, self._disambiguated_name, self.room.own_user_id]: + body = self.event.body + if body.startswith(id): + body = body[len(id):] + # the match needs to end here, otherwise someone else is mentioned + # this isn't perfect but probably the best effort + if body[0] in [' ', ':']: return True - - return False + + # pills on the other hand are a clearer case thanks to HTML tags which include delimiters + body = self.event.formatted_body + return False if body is None else body.startswith(self._pill) def args(self): """ From 809186ecc97b578fbc0de857bb8b7d08cae126a9 Mon Sep 17 00:00:00 2001 From: HarHarLinks Date: Fri, 26 Nov 2021 02:05:15 +0100 Subject: [PATCH 07/17] refactor command() into mention() --- simplematrixbotlib/match.py | 42 +++++++++++++------------------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index bfe0d3b..5b5c863 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -105,30 +105,10 @@ def command(self, command=None): """ # we cache this part - if not self._body_without_prefix: + if self._body_without_prefix is None: if self._prefix == self.event.body[0:len(self._prefix)]: self._body_without_prefix = self.event.body[len(self._prefix):] - elif self.mention(): - # the order is important here! - # note: we assume that body and formatted_body, if present, match as a workaraound of cleaning html - id_matched = False - for id in (self._disambiguated_name, self._display_name, self.room.own_user_id): - if self.event.body.startswith(id) and not id_matched: - self._body_without_prefix = self.event.body[len(id):] - id_matched = True - - if self.event.formatted_body.startswith(self._pill) and not id_matched: - name = self.event.formatted_body[len():self.event.formatted_body.index('')] - self._body_without_prefix = self.event.body[len(name):] - - # mentioning may include a : (colon) as inserted by Element when clicking on a user - if self._body_without_prefix.startswith(':'): - self._body_without_prefix = self._body_without_prefix[1:] - - # trim leading whitespace after the mention - self._body_without_prefix = self._body_without_prefix.strip() - - else: + elif not self.mention(): # if mention() is True then it also sets the _body_without_prefix self._body_without_prefix = self.event.body if command: @@ -156,18 +136,26 @@ def mention(self): Returns True if the message begins with the bot's username, MXID, or pill targeting the MXID, and False otherwise. """ - for id in [self._display_name, self._disambiguated_name, self.room.own_user_id]: - body = self.event.body + body = self.event.body + for id in [self._disambiguated_name, self._display_name, self.room.own_user_id]: if body.startswith(id): - body = body[len(id):] + body_ = body[len(id):] # the match needs to end here, otherwise someone else is mentioned # this isn't perfect but probably the best effort - if body[0] in [' ', ':']: + if body_[0] in [' ', ':']: + self._body_without_prefix = body_[1:].strip() return True # pills on the other hand are a clearer case thanks to HTML tags which include delimiters body = self.event.formatted_body - return False if body is None else body.startswith(self._pill) + if body is not None and body.startswith(self._pill): + # remove the first half of the pill + body = body[len(self._pill):] + # find pill end + trailing delimiter + maybe whitespace + self._body_without_prefix = body[body.index('')+5:].strip() + return True + + return False def args(self): """ From c2414d3bcee1bb1b41a3db9b8150f5c626dae8eb Mon Sep 17 00:00:00 2001 From: HarHarLinks Date: Fri, 26 Nov 2021 02:06:41 +0100 Subject: [PATCH 08/17] add tests for mention() missing: mention when no prefix is set --- tests/match/test_messagematch.py | 60 +++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/tests/match/test_messagematch.py b/tests/match/test_messagematch.py index 162d19f..cbefe84 100644 --- a/tests/match/test_messagematch.py +++ b/tests/match/test_messagematch.py @@ -3,21 +3,54 @@ mock_room = mock.MagicMock() +mock_room2 = mock.MagicMock() +mock_room2.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_room2.own_user_id})" +mock_room2.users = {mock_room2.own_user_id: mock_user} + mock_event = mock.MagicMock() mock_event.body = "p!help example" 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_bot = mock.MagicMock() 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 def test_init(): assert issubclass(MessageMatch, Match) @@ -30,6 +63,25 @@ def test_command(): assert match2.command() == "p!help" assert match2.command("p!help") == 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 + def test_prefix(): assert match.prefix() == True assert match3.prefix() == False From 25c8ce8854b6c0bcec17575769422c4a3fa9c48e Mon Sep 17 00:00:00 2001 From: HarHarLinks Date: Fri, 26 Nov 2021 02:10:35 +0100 Subject: [PATCH 09/17] add comments to mention related class privates --- simplematrixbotlib/match.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index 5b5c863..a6dd9ea 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -83,8 +83,12 @@ def __init__(self, room, event, bot, prefix="") -> None: super().__init__(room, event, bot) self._prefix = prefix bot_user = self.room.users[self.room.own_user_id] - self._display_name = bot_user.display_name + # in case another user uses the same display name, we need to regard the disambiguation if present self._disambiguated_name = bot_user.disambiguated_name + # still we allow to mention without disambiguation if typing manually for now + self._display_name = bot_user.display_name + # element generates a "pill" from formatted (HTML) links to matrix.to/#/@matrix:id + # this isn't really specced but still the status quo self._pill = f'' self._body_without_prefix = None From 45599afc7f92963ba99cc90dc27c07915e0e85b8 Mon Sep 17 00:00:00 2001 From: HarHarLinks Date: Fri, 26 Nov 2021 02:14:16 +0100 Subject: [PATCH 10/17] fix None exception in args happens when calling args before command --- simplematrixbotlib/match.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index a6dd9ea..ed2fdef 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -170,6 +170,8 @@ def args(self): Returns a list of strings that are the "words" of the message, except for the first "word", which would be the command. """ + if self._body_without_prefix is None: + self.command() # calculate _body_without_prefix return self._body_without_prefix.split()[1:] def contains(self, string): From 3a5d621843d0df4e4004f0828f224d33ab05142c Mon Sep 17 00:00:00 2001 From: HarHarLinks Date: Fri, 26 Nov 2021 15:39:36 +0100 Subject: [PATCH 11/17] correctly strip mention from command even when no prefix is given --- simplematrixbotlib/match.py | 9 +++++---- tests/match/test_messagematch.py | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index ed2fdef..11313a7 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -110,10 +110,11 @@ def command(self, command=None): # we cache this part if self._body_without_prefix is None: - if self._prefix == self.event.body[0:len(self._prefix)]: - self._body_without_prefix = self.event.body[len(self._prefix):] - elif not self.mention(): # if mention() is True then it also sets the _body_without_prefix - self._body_without_prefix = self.event.body + if not self.mention(): + if self._prefix == self.event.body[0:len(self._prefix)]: + self._body_without_prefix = self.event.body[len(self._prefix):] + else: + self._body_without_prefix = self.event.body if command: return self._body_without_prefix.split()[0] == command diff --git a/tests/match/test_messagematch.py b/tests/match/test_messagematch.py index cbefe84..5263abe 100644 --- a/tests/match/test_messagematch.py +++ b/tests/match/test_messagematch.py @@ -51,6 +51,7 @@ 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 def test_init(): assert issubclass(MessageMatch, Match) @@ -82,6 +83,9 @@ def test_mention(): 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 From 6b8a0d95c06de25dccbd5806827f3b7dccb63c70 Mon Sep 17 00:00:00 2001 From: HarHarLinks Date: Fri, 26 Nov 2021 15:48:57 +0100 Subject: [PATCH 12/17] fix command() list index out of range --- simplematrixbotlib/match.py | 10 +++++++--- tests/match/test_messagematch.py | 10 ++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index 11313a7..b5886b6 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -116,10 +116,14 @@ def command(self, command=None): else: self._body_without_prefix = self.event.body - if command: - return self._body_without_prefix.split()[0] == command + split_body = self._body_without_prefix.split() + if len(split_body) < 1: + split_body = [""] + + if command is not None: + return split_body[0] == command else: - return self._body_without_prefix.split()[0] + return split_body[0] def prefix(self): """ diff --git a/tests/match/test_messagematch.py b/tests/match/test_messagematch.py index 5263abe..e98ffc8 100644 --- a/tests/match/test_messagematch.py +++ b/tests/match/test_messagematch.py @@ -35,6 +35,9 @@ mock_event8.body = "bottom help" mock_event8.formatted_body = None +mock_event9 = mock.MagicMock() +mock_event9.body = "p!" + mock_bot = mock.MagicMock() prefix = "p!" @@ -53,6 +56,8 @@ 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) assert match._prefix == prefix @@ -64,6 +69,9 @@ 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 @@ -92,6 +100,8 @@ def test_prefix(): #assert match2.prefix() == True + assert match12.prefix() == True + def test_args(): assert match.args() == ["example"] From 2e380d530a16109a20496b8ff9a4b020356374bf Mon Sep 17 00:00:00 2001 From: krazykirby99999 Date: Sun, 28 Nov 2021 13:20:42 -0600 Subject: [PATCH 13/17] refactor: :recycle: Refactor MessageMatch class Refactor MessageMatch class to use _body_without_mention and _split_body. No longer checks formatted_body for mention. --- simplematrixbotlib/match.py | 86 ++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 49 deletions(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index b5886b6..d2e8dfd 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -82,17 +82,27 @@ def __init__(self, room, event, bot, prefix="") -> None: """ super().__init__(room, event, bot) self._prefix = prefix - bot_user = self.room.users[self.room.own_user_id] - # in case another user uses the same display name, we need to regard the disambiguation if present - self._disambiguated_name = bot_user.disambiguated_name - # still we allow to mention without disambiguation if typing manually for now - self._display_name = bot_user.display_name - # element generates a "pill" from formatted (HTML) links to matrix.to/#/@matrix:id - # this isn't really specced but still the status quo - self._pill = f'' - self._body_without_prefix = None - - def command(self, command=None): + + """Forms of identification""" + self._own_user_id = f"@{self._bot.creds.username}:{self._bot.creds.homeserver.replace("https://","").replace("http://","")}" + self._own_nio_user = self.room.users[own_user_id] + self._own_disambiguated_name = own_nio_user.disambiguated_name + self._own_display_name = own_nio_user.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[len(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="") -> Union[bool, str]: """ Parameters ---------- @@ -108,22 +118,16 @@ def command(self, command=None): Returns the string after the prefix and before the first space if no arg is passed to this method. """ - # we cache this part - if self._body_without_prefix is None: - if not self.mention(): - if self._prefix == self.event.body[0:len(self._prefix)]: - self._body_without_prefix = self.event.body[len(self._prefix):] - else: - self._body_without_prefix = self.event.body - - split_body = self._body_without_prefix.split() - if len(split_body) < 1: - split_body = [""] + if not (self._body_without_prefix and self._body_without_mention): + if command: + return False + else: + return "" - if command is not None: - return split_body[0] == command + if command: + return self._split_body[0] == command else: - return split_body[0] + return self._split_body[0] def prefix(self): """ @@ -134,7 +138,7 @@ 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): """ @@ -145,25 +149,11 @@ def mention(self): Returns True if the message begins with the bot's username, MXID, or pill targeting the MXID, and False otherwise. """ - body = self.event.body - for id in [self._disambiguated_name, self._display_name, self.room.own_user_id]: - if body.startswith(id): - body_ = body[len(id):] - # the match needs to end here, otherwise someone else is mentioned - # this isn't perfect but probably the best effort - if body_[0] in [' ', ':']: - self._body_without_prefix = body_[1:].strip() - return True - - # pills on the other hand are a clearer case thanks to HTML tags which include delimiters - body = self.event.formatted_body - if body is not None and body.startswith(self._pill): - # remove the first half of the pill - body = body[len(self._pill):] - # find pill end + trailing delimiter + maybe whitespace - self._body_without_prefix = body[body.index('')+5:].strip() - return True - + for id in [self._own_disambiguated_name, self._own_display_name, self._own_user_id]: + if self.event.body.startswith(id): + self._mention_id_length = len(id) + return True + return False def args(self): @@ -172,12 +162,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. """ - if self._body_without_prefix is None: - self.command() # calculate _body_without_prefix - return self._body_without_prefix.split()[1:] + return self._split_body[1:] def contains(self, string): """ From deecc3614bc68506767178f600dcf12043574f5c Mon Sep 17 00:00:00 2001 From: krazykirby99999 Date: Sun, 28 Nov 2021 16:18:10 -0600 Subject: [PATCH 14/17] Fix errors introduced by commit 2e380d53 --- simplematrixbotlib/match.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index d2e8dfd..33d214d 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 @@ -84,15 +86,16 @@ def __init__(self, room, event, bot, prefix="") -> None: self._prefix = prefix """Forms of identification""" - self._own_user_id = f"@{self._bot.creds.username}:{self._bot.creds.homeserver.replace("https://","").replace("http://","")}" - self._own_nio_user = self.room.users[own_user_id] - self._own_disambiguated_name = own_nio_user.disambiguated_name - self._own_display_name = own_nio_user.display_name - self._own_pill = f"" + 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[len(self._mention_id_length):] + self._body_without_mention = self.event.body[self._mention_id_length:] if self.mention(): body = self._body_without_mention @@ -102,7 +105,7 @@ def __init__(self, room, event, bot, prefix="") -> None: body = self.event.body self._split_body = body.split() - def command(self, command="") -> Union[bool, str]: + def command(self, command: Optional[str] = None) -> Union[bool, str]: """ Parameters ---------- @@ -119,12 +122,15 @@ def command(self, command="") -> Union[bool, str]: """ if not (self._body_without_prefix and self._body_without_mention): - if command: + """Body is empty after removing prefix or mention""" + if command is None: + return "" + elif command: return False else: - return "" + return True - if command: + if command is not None: return self._split_body[0] == command else: return self._split_body[0] @@ -149,11 +155,12 @@ def mention(self): 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]: + 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) + self._mention_id_length = len(id)+1 return True - + self._mention_id_length = 0 + return False def args(self): From cbd843d7a9f800f03ba1de136f53879297f09941 Mon Sep 17 00:00:00 2001 From: krazykirby99999 Date: Sun, 28 Nov 2021 16:20:33 -0600 Subject: [PATCH 15/17] test: :white_check_mark: Fix messagematch tests Fix messagematch tests. match9 and match10 in test_mention are disabled, but will need to be enabled to test switching between body and formatted_body --- tests/match/test_messagematch.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/match/test_messagematch.py b/tests/match/test_messagematch.py index e98ffc8..6aba8ed 100644 --- a/tests/match/test_messagematch.py +++ b/tests/match/test_messagematch.py @@ -2,13 +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_user = mock.MagicMock() -mock_user.display_name = "bot" -mock_user.disambiguated_name = f"{mock_user.display_name} ({mock_room2.own_user_id})" -mock_room2.users = {mock_room2.own_user_id: mock_user} +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" @@ -39,6 +44,8 @@ mock_event9.body = "p!" mock_bot = mock.MagicMock() +mock_bot.creds.username = "bot" +mock_bot.creds.homeserver = "https://matrix.org" prefix = "p!" prefix2 = "!!" @@ -75,7 +82,7 @@ def test_command(): def test_mention(): assert match5.command() == "help" assert match5.mention() == True - + assert match6.command() == "help" assert match6.mention() == True @@ -85,11 +92,11 @@ def test_mention(): assert match8.command() == "help" assert match8.mention() == True - assert match9.command() == "help" - assert match9.mention() == True +# assert match9.command() == "help" +# assert match9.mention() == True - assert match10.command() == "bottom" - assert match10.mention() == False +# assert match10.command() == "bottom" +# assert match10.mention() == False assert match11.command() == "help" assert match11.mention() == True From 708eb2bf555cfafbb39ea6536167c134f796fa7f Mon Sep 17 00:00:00 2001 From: krazykirby99999 Date: Mon, 29 Nov 2021 18:19:37 -0600 Subject: [PATCH 16/17] Enable failing tests --- tests/match/test_messagematch.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/match/test_messagematch.py b/tests/match/test_messagematch.py index 6aba8ed..9367dbc 100644 --- a/tests/match/test_messagematch.py +++ b/tests/match/test_messagematch.py @@ -92,11 +92,11 @@ def test_mention(): assert match8.command() == "help" assert match8.mention() == True -# assert match9.command() == "help" -# assert match9.mention() == True + assert match9.command() == "help" + assert match9.mention() == True -# assert match10.command() == "bottom" -# assert match10.mention() == False + assert match10.command() == "bottom" + assert match10.mention() == False assert match11.command() == "help" assert match11.mention() == True @@ -105,7 +105,7 @@ def test_prefix(): assert match.prefix() == True assert match3.prefix() == False - #assert match2.prefix() == True + assert match2.prefix() == True assert match12.prefix() == True From a2bde24b93253b19a9ce13ba108c389c5ed8b18f Mon Sep 17 00:00:00 2001 From: krazykirby99999 Date: Tue, 28 Dec 2021 14:35:35 -0600 Subject: [PATCH 17/17] refactor: :recycle: Change order of if statements to improve readability in MessageMatch class --- simplematrixbotlib/match.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simplematrixbotlib/match.py b/simplematrixbotlib/match.py index 33d214d..bc39539 100644 --- a/simplematrixbotlib/match.py +++ b/simplematrixbotlib/match.py @@ -125,10 +125,10 @@ def command(self, command: Optional[str] = None) -> Union[bool, str]: """Body is empty after removing prefix or mention""" if command is None: return "" - elif command: - return False - else: + elif not command: return True + else: + return False if command is not None: return self._split_body[0] == command