From fdba03f81b3936b8cff9807975e8287c8dbe9b49 Mon Sep 17 00:00:00 2001 From: dgw Date: Wed, 8 Nov 2023 16:03:56 -0600 Subject: [PATCH 1/7] irc: add `make_identifier_memory` helper to `AbstractBot` This is a convenience method for people who just want default behavior matching what Sopel itself does internally. The common use cases for `SopelIdentifierMemory` require that its casemapping behave the same as what Sopel's using, and this simplifies getting there. --- sopel/irc/__init__.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/sopel/irc/__init__.py b/sopel/irc/__init__.py index 81fcd2451..b1b8dafb6 100644 --- a/sopel/irc/__init__.py +++ b/sopel/irc/__init__.py @@ -45,7 +45,7 @@ from sopel import tools, trigger from sopel.lifecycle import deprecated -from sopel.tools import identifiers +from sopel.tools import identifiers, memories from .backends import AsyncioBackend, UninitializedBackend from .capabilities import Capabilities from .isupport import ISupport @@ -242,6 +242,34 @@ def make_identifier(self, name: str) -> identifiers.Identifier: chantypes=chantypes, ) + def make_identifier_memory(self) -> memories.SopelIdentifierMemory: + """Instantiate a SopelIdentifierMemory using the bot's context. + + This is a shortcut for :class:`~.memories.SopelIdentifierMemory`\'s most + common use case, which requires remembering to pass the ``bot``\'s own + :meth:`make_identifier` method so the ``SopelIdentifierMemory`` will + cast its keys to :class:`~.tools.identifiers.Identifier`\\s that are + compatible with what the bot tracks internally and sends with + :class:`~.trigger.Trigger`\\s when a plugin callable runs. + + Calling this method is equivalent to the following:: + + from sopel.tools import memories + + memories.SopelIdentifierMemory( + identifier_factory=bot.make_identifier, + ) + + .. seealso:: + + The :mod:`.tools.memories` module describes how to use + :class:`~.tools.memories.SopelIdentifierMemory` and its siblings. + + """ + return memories.SopelIdentifierMemory( + identifier_factory=self.make_identifier, + ) + def safe_text_length(self, recipient: str) -> int: """Estimate a safe text length for an IRC message. From ed0cbcaad9fc7930f568bcf36633d93785ce4a37 Mon Sep 17 00:00:00 2001 From: dgw Date: Wed, 8 Nov 2023 16:06:14 -0600 Subject: [PATCH 2/7] bot: use new `make_identifier_memory()` helper for channel/user tracking --- sopel/bot.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sopel/bot.py b/sopel/bot.py index f7747aa98..9fb57cbae 100644 --- a/sopel/bot.py +++ b/sopel/bot.py @@ -75,9 +75,7 @@ def __init__(self, config, daemon=False): self.modeparser = modes.ModeParser() """A mode parser used to parse ``MODE`` messages and modestrings.""" - self.channels = tools.SopelIdentifierMemory( - identifier_factory=self.make_identifier, - ) + self.channels = self.make_identifier_memory() """A map of the channels that Sopel is in. The keys are :class:`~sopel.tools.identifiers.Identifier`\\s of the @@ -85,9 +83,7 @@ def __init__(self, config, daemon=False): which contain the users in the channel and their permissions. """ - self.users = tools.SopelIdentifierMemory( - identifier_factory=self.make_identifier, - ) + self.users = self.make_identifier_memory() """A map of the users that Sopel is aware of. The keys are :class:`~sopel.tools.identifiers.Identifier`\\s of the From d504c206009eac71d47e777c2752d7007111b0b1 Mon Sep 17 00:00:00 2001 From: dgw Date: Wed, 8 Nov 2023 16:06:51 -0600 Subject: [PATCH 3/7] find: use new `bot.make_identifier_memory()` helper --- sopel/builtins/find.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sopel/builtins/find.py b/sopel/builtins/find.py index 5e0b38620..db6528252 100644 --- a/sopel/builtins/find.py +++ b/sopel/builtins/find.py @@ -18,14 +18,11 @@ from sopel import plugin from sopel.formatting import bold -from sopel.tools import SopelIdentifierMemory def setup(bot): if 'find_lines' not in bot.memory: - bot.memory['find_lines'] = SopelIdentifierMemory( - identifier_factory=bot.make_identifier, - ) + bot.memory['find_lines'] = bot.make_identifier_memory() def shutdown(bot): @@ -49,9 +46,7 @@ def collectlines(bot, trigger): # Add a log for the channel and nick, if there isn't already one if trigger.sender not in bot.memory['find_lines']: - bot.memory['find_lines'][trigger.sender] = SopelIdentifierMemory( - identifier_factory=bot.make_identifier, - ) + bot.memory['find_lines'][trigger.sender] = bot.make_identifier_memory() if trigger.nick not in bot.memory['find_lines'][trigger.sender]: bot.memory['find_lines'][trigger.sender][trigger.nick] = deque(maxlen=10) From acc0bf0b5d03f814d34124bf8be30cd366d4ab6d Mon Sep 17 00:00:00 2001 From: dgw Date: Wed, 8 Nov 2023 16:07:26 -0600 Subject: [PATCH 4/7] translate: use new `bot.make_identifier_memory()` helper --- sopel/builtins/translate.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sopel/builtins/translate.py b/sopel/builtins/translate.py index 98fde9fbf..207d53c8e 100644 --- a/sopel/builtins/translate.py +++ b/sopel/builtins/translate.py @@ -15,7 +15,7 @@ import requests -from sopel import plugin, tools +from sopel import plugin from sopel.tools import web @@ -25,9 +25,7 @@ def setup(bot): if 'mangle_lines' not in bot.memory: - bot.memory['mangle_lines'] = tools.SopelIdentifierMemory( - identifier_factory=bot.make_identifier, - ) + bot.memory['mangle_lines'] = bot.make_identifier_memory() def shutdown(bot): From 31e1c1e69d8c02bc61449dc1a8c08841ea0ab94c Mon Sep 17 00:00:00 2001 From: dgw Date: Wed, 8 Nov 2023 16:07:44 -0600 Subject: [PATCH 5/7] url: use new `bot.make_identifier_memory()` helper --- sopel/builtins/url.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sopel/builtins/url.py b/sopel/builtins/url.py index fc121b8c4..7383e0211 100644 --- a/sopel/builtins/url.py +++ b/sopel/builtins/url.py @@ -133,9 +133,7 @@ def setup(bot: Sopel): # Ensure last_seen_url is in memory if 'last_seen_url' not in bot.memory: - bot.memory['last_seen_url'] = tools.SopelIdentifierMemory( - identifier_factory=bot.make_identifier, - ) + bot.memory['last_seen_url'] = bot.make_identifier_memory() # Initialize shortened_urls as a dict if it doesn't exist. if 'shortened_urls' not in bot.memory: From 46977b61cd00c9112e12e6927fb31d1ab2215319 Mon Sep 17 00:00:00 2001 From: dgw Date: Wed, 8 Nov 2023 16:39:47 -0600 Subject: [PATCH 6/7] irc: `versionadded` annotations for `AbstractBot.make_identifier*()` --- sopel/irc/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sopel/irc/__init__.py b/sopel/irc/__init__.py index b1b8dafb6..e2a3b1fa8 100644 --- a/sopel/irc/__init__.py +++ b/sopel/irc/__init__.py @@ -227,7 +227,10 @@ def hostmask(self) -> Optional[str]: # Utility def make_identifier(self, name: str) -> identifiers.Identifier: - """Instantiate an Identifier using the bot's context.""" + """Instantiate an Identifier using the bot's context. + + .. versionadded:: 8.0 + """ casemapping = { 'ascii': identifiers.ascii_lower, 'rfc1459': identifiers.rfc1459_lower, @@ -260,6 +263,8 @@ def make_identifier_memory(self) -> memories.SopelIdentifierMemory: identifier_factory=bot.make_identifier, ) + .. versionadded:: 8.0 + .. seealso:: The :mod:`.tools.memories` module describes how to use From 89b8d49c53381759a11b7a490454c1f8b3676009 Mon Sep 17 00:00:00 2001 From: dgw Date: Sat, 11 Nov 2023 13:37:38 -0600 Subject: [PATCH 7/7] test: verify `bot.make_identifier_memory()` behavior w/CASEMAPPING set --- test/test_coretasks.py | 30 ++++++++++++++++++++++++++++++ test/test_irc.py | 15 +++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/test/test_coretasks.py b/test/test_coretasks.py index c01174e1b..110ceb0d7 100644 --- a/test/test_coretasks.py +++ b/test/test_coretasks.py @@ -340,6 +340,36 @@ def test_handle_isupport_casemapping(mockbot): assert mockbot.nick.lower() == 'test[a]' +def test_handle_isupport_casemapping_identifiermemory(mockbot): + # create a nick that needs casemapping + rfc1459 = 'Test[a]' + + # create `SopelIdentifierMemory` w/bot's helper method and add the nick + memory = mockbot.make_identifier_memory() + memory[rfc1459] = rfc1459 + + # check default behavior (`rfc1459` casemapping) + assert memory['test{a}'] == rfc1459 + assert memory['Test[a]'] == rfc1459 + + # now the bot "connects" to a server using `CASEMAPPING=ascii` + mockbot.on_message( + ':irc.example.com 005 Sopel ' + 'CHANTYPES=# EXCEPTS INVEX CHANMODES=eIbq,k,flj,CFLMPQScgimnprstz ' + 'CHANLIMIT=#:120 PREFIX=(ov)@+ MAXLIST=bqeI:100 MODES=4 ' + 'NETWORK=example STATUSMSG=@+ CALLERID=g CASEMAPPING=ascii ' + ':are supported by this server') + + # CASEMAPPING token change doesn't affect previously existing Identifiers... + assert memory['Test{a}'] == rfc1459 + # ...so we have to create a new nick that will casemap differently now + ascii = 'Test[b]' + memory[ascii] = ascii + assert len(memory) == 2 + assert memory['test[b]'] == ascii + assert 'test{b}' not in memory + + def test_handle_isupport_chantypes(mockbot): # check default behavior (chantypes allows #, &, +, and !) assert not mockbot.make_identifier('#channel').is_nick() diff --git a/test/test_irc.py b/test/test_irc.py index 1ea83f32a..e15620a61 100644 --- a/test/test_irc.py +++ b/test/test_irc.py @@ -29,6 +29,21 @@ def bot(tmpconfig, botfactory): return botfactory(tmpconfig) +def test_make_identifier(bot): + nick = bot.make_identifier('Test[a]') + assert 'test{a}' == nick + + +def test_make_identifier_memory(bot): + memory = bot.make_identifier_memory() + memory['Test[a]'] = True + assert memory['test{a}'] is True + + memory['test{a}'] = False + assert len(memory) == 1 + assert memory['Test[a]'] is False + + def prefix_length(bot): # ':', nick, '!', '~', ident/username, '@', maximum hostname length, <0x20> return 1 + len(bot.nick) + 1 + 1 + len(bot.user) + 1 + 63 + 1