Skip to content

Commit

Permalink
Merge pull request #2552 from sopel-irc/bot-identifiermemory-helper
Browse files Browse the repository at this point in the history
irc, bot, builtins: add & use `AbstractBot.make_identifier_memory()` helper
  • Loading branch information
dgw authored Nov 17, 2023
2 parents 72493dc + 89b8d49 commit 9103422
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 22 deletions.
8 changes: 2 additions & 6 deletions sopel/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,15 @@ 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
channel names, and map to :class:`~sopel.tools.target.Channel` objects
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
Expand Down
9 changes: 2 additions & 7 deletions sopel/builtins/find.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)

Expand Down
6 changes: 2 additions & 4 deletions sopel/builtins/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import requests

from sopel import plugin, tools
from sopel import plugin
from sopel.tools import web


Expand All @@ -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):
Expand Down
4 changes: 1 addition & 3 deletions sopel/builtins/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
37 changes: 35 additions & 2 deletions sopel/irc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -242,6 +245,36 @@ 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,
)
.. versionadded:: 8.0
.. 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.
Expand Down
30 changes: 30 additions & 0 deletions test/test_coretasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
15 changes: 15 additions & 0 deletions test/test_irc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9103422

Please sign in to comment.