Skip to content

Commit

Permalink
bot, coretasks: add bot.modeparser
Browse files Browse the repository at this point in the history
Instead of creating an instance of `ModeParser` at each MODE event, the
bot has a `modeparser` attribute with appropriate default values for its
chanmodes, privileges, and param types.

For that to work, when the bot received new ISUPPORT parameters, it
updates the chanmodes and privileges of the mode parser if necessary.

This required to adapt the list of default modes, as `a` and `q` are
more commonly used as privilege rather than modes.

Tests have been updated to manually update the modeparser. This may
require new methods on the test IRC server to streamline an ISUPPORT
configuration, because right now it's a bit too manual.
  • Loading branch information
Exirel committed Jul 14, 2021
1 parent 31dcab2 commit 178e392
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 19 deletions.
4 changes: 4 additions & 0 deletions sopel/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from sopel import irc, logger, plugins, tools
from sopel.db import SopelDB
from sopel.irc import modes
import sopel.loader
from sopel.module import NOLIMIT
from sopel.plugins import jobs as plugin_jobs, rules as plugin_rules
Expand Down Expand Up @@ -78,6 +79,9 @@ def __init__(self, config, daemon=False):
For servers that do not support IRCv3, this will be an empty set.
"""

self.modeparser = modes.ModeParser()
"""A mode parser used to parse ``MODE`` messages and modestrings."""

self.channels = tools.SopelIdentifierMemory()
"""A map of the channels that Sopel is in.
Expand Down
21 changes: 10 additions & 11 deletions sopel/coretasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

from sopel import loader, plugin
from sopel.config import ConfigurationError
from sopel.irc import isupport, modes as ircmodes
from sopel.irc import isupport
from sopel.irc.utils import CapReq, MyInfo
from sopel.tools import events, Identifier, SopelMemory, target, web

Expand Down Expand Up @@ -342,6 +342,13 @@ def handle_isupport(bot, trigger):

bot._isupport = bot._isupport.apply(**parameters)

# update bot's mode parser
if 'CHANMODES' in bot.isupport:
bot.modeparser.chanmodes = bot.isupport.CHANMODES

if 'PREFIX' in bot.isupport:
bot.modeparser.privileges = set(bot.isupport.PREFIX.keys())

# was BOT mode support status updated?
if not botmode_support and 'BOT' in bot.isupport:
# yes it was! set our mode unless the config overrides it
Expand Down Expand Up @@ -541,15 +548,7 @@ def _parse_modes(bot, args, clear=False):
LOGGER.debug(
"The server sent a possibly malformed MODE message: %r", args)

privileges = MODE_PREFIXES
if 'PREFIX' in bot.isupport:
privileges = set(bot.isupport.PREFIX.keys())

modemessage = ircmodes.ModeParser(
bot.isupport.CHANMODES,
privileges=privileges,
)
modeinfo = modemessage.parse_modestring(args[1], tuple(args[2:]))
modeinfo = bot.modeparser.parse_modestring(args[1], tuple(args[2:]))

# set or update channel's modes
modes = {} if clear else copy.deepcopy(channel.modes)
Expand Down Expand Up @@ -608,7 +607,7 @@ def _parse_modes(bot, args, clear=False):
LOGGER.warning(
"Too many arguments received for MODE: args=%r chanmodes=%r",
args,
modemessage.chanmodes,
bot.modeparser.chanmodes,
)

LOGGER.info("Updated mode for channel: %s", channel.name)
Expand Down
22 changes: 17 additions & 5 deletions sopel/irc/modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,28 +120,40 @@ class ModeParser:
'A': tuple('beI'),
'B': tuple('k'),
'C': tuple('l'),
'D': tuple('Oaimnqpsrt'),
'D': tuple('Oimnpsrt'),
}
"""Default CHANMODES per :rfc:`2811`."""
"""Default CHANMODES per :rfc:`2811`.
.. note::
Mode ``a`` has been removed from the default list, as it appears
to be a relic of the past and is more commonly used as a privilege.
Mode ``q`` has been removed too, as it is communly used as a privilege.
If a server is unhappy with these defaults, they should advertise
``CHANMODES`` and ``PREFIX`` properly.
"""

def __init__(
self,
chanmodes: Dict[str, Tuple[str, ...]] = CHANMODES,
type_params: Dict[str, ParamRequired] = DEFAULT_MODETYPE_PARAM_CONFIG,
privileges: Set[str] = PRIVILEGES,
) -> None:
self.chanmodes: Dict[str, Tuple[str, ...]] = chanmodes
self.chanmodes: Dict[str, Tuple[str, ...]] = dict(chanmodes)
"""Map of mode types (``str``) to their lists of modes (``tuple``).
This map should come from ``ISUPPORT``, usually through
:attr:`bot.isupport.CHANMODES <sopel.irc.isupport.ISupport.CHANMODES>`.
"""
self.type_params = type_params
self.type_params = dict(type_params)
"""Map of mode types (``str``) with their param requirements.
This map defaults to :data:`DEFAULT_MODETYPE_PARAM_CONFIG`.
"""
self.privileges = privileges
self.privileges = set(privileges)
"""Set of valid user privileges.
This set should come from ``ISUPPORT``, usually through
Expand Down
4 changes: 1 addition & 3 deletions test/irc/test_irc_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,13 @@ def test_modemessage_get_mode_info_custom_privileges():
def test_modemessage_parse_modestring_default():
modeparser = ModeParser()
result = modeparser.parse_modestring(
'+Oaimn-qpsrt+lk-beI' + '+Z',
'+Oimn-psrt+lk-beI' + '+Z',
tuple('abcdef'))
assert result.modes == (
('D', 'O', ADDED, None),
('D', 'a', ADDED, None),
('D', 'i', ADDED, None),
('D', 'm', ADDED, None),
('D', 'n', ADDED, None),
('D', 'q', REMOVED, None),
('D', 'p', REMOVED, None),
('D', 's', REMOVED, None),
('D', 'r', REMOVED, None),
Expand Down
7 changes: 7 additions & 0 deletions test/test_coretasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def test_bot_mixed_mode_removal(mockbot, ircfactory):
"""
irc = ircfactory(mockbot)
irc.bot._isupport = isupport.ISupport(chanmodes=("b", "", "", "m", tuple()))
irc.bot.modeparser.chanmodes = irc.bot.isupport.CHANMODES
irc.channel_joined('#test', ['Uvoice', 'Uop'])

irc.mode_set('#test', '+qao', ['Uvoice', 'Uvoice', 'Uvoice'])
Expand All @@ -88,6 +89,7 @@ def test_bot_mixed_mode_types(mockbot, ircfactory):
"""
irc = ircfactory(mockbot)
irc.bot._isupport = isupport.ISupport(chanmodes=("be", "", "", "mn", tuple()))
irc.bot.modeparser.chanmodes = irc.bot.isupport.CHANMODES
irc.channel_joined('#test', [
'Uvoice', 'Uop', 'Uadmin', 'Uvoice2', 'Uop2', 'Uadmin2'])
irc.mode_set('#test', '+amovn', ['Uadmin', 'Uop', 'Uvoice'])
Expand All @@ -111,6 +113,7 @@ def test_bot_unknown_mode(mockbot, ircfactory):
"""Ensure modes not in PREFIX or CHANMODES trigger a WHO."""
irc = ircfactory(mockbot)
irc.bot._isupport = isupport.ISupport(chanmodes=("b", "", "", "mnt", tuple()))
irc.bot.modeparser.chanmodes = irc.bot.isupport.CHANMODES
irc.channel_joined("#test", ["Alex", "Bob", "Cheryl"])
irc.mode_set("#test", "+te", ["Alex"])

Expand All @@ -124,6 +127,7 @@ def test_bot_unknown_priv_mode(mockbot, ircfactory):
"""Ensure modes in `mapping` but not PREFIX are treated as unknown."""
irc = ircfactory(mockbot)
irc.bot._isupport = isupport.ISupport(prefix={"o": "@", "v": "+"})
irc.bot.modeparser.privileges = set(irc.bot.isupport.PREFIX.keys())
irc.channel_joined("#test", ["Alex", "Bob", "Cheryl"])
irc.mode_set("#test", "+oh", ["Alex", "Bob"])

Expand All @@ -137,6 +141,7 @@ def test_bot_extra_mode_args(mockbot, ircfactory, caplog):
"""Test warning on extraneous MODE args."""
irc = ircfactory(mockbot)
irc.bot._isupport = isupport.ISupport(chanmodes=("b", "k", "l", "mnt", tuple()))
irc.bot.modeparser.chanmodes = irc.bot.isupport.CHANMODES
irc.channel_joined("#test", ["Alex", "Bob", "Cheryl"])

mode_msg = ":Sopel!bot@bot MODE #test +m nonsense"
Expand All @@ -159,6 +164,7 @@ def test_handle_rpl_channelmodeis(mockbot, ircfactory):
])
irc = ircfactory(mockbot)
irc.bot._isupport = isupport.ISupport(chanmodes=("b", "k", "l", "mnt", tuple()))
irc.bot.modeparser.chanmodes = irc.bot.isupport.CHANMODES
irc.channel_joined("#test", ["Alex", "Bob", "Cheryl"])
mockbot.on_message(rpl_channelmodeis)

Expand All @@ -172,6 +178,7 @@ def test_handle_rpl_channelmodeis_clear(mockbot, ircfactory):
"""Test RPL_CHANNELMODEIS events clearing previous modes"""
irc = ircfactory(mockbot)
irc.bot._isupport = isupport.ISupport(chanmodes=("b", "k", "l", "mnt", tuple()))
irc.bot.modeparser.chanmodes = irc.bot.isupport.CHANMODES
irc.channel_joined("#test", ["Alex", "Bob", "Cheryl"])

rpl_base = ":mercury.libera.chat 324 TestName #test {modes}"
Expand Down

0 comments on commit 178e392

Please sign in to comment.