def on_message(self, message: str) -> None: """Handle an incoming IRC message. :param str message: the received raw IRC message """ if self.backend is None: raise RuntimeError(ERR_BACKEND_NOT_INITIALIZED) self.last_raw_line = message pretrigger = trigger.PreTrigger( self.nick, message, url_schemes=self.settings.core.auto_url_schemes, identifier_factory=self.make_identifier, ) if all(cap not in self.enabled_capabilities for cap in ['account-tag', 'extended-join']): pretrigger.tags.pop('account', None) if pretrigger.event == 'PING': self.backend.send_pong(pretrigger.args[-1]) elif pretrigger.event == 'ERROR': LOGGER.error("ERROR received from server: %s", pretrigger.args[-1]) self.backend.on_irc_error(pretrigger) self.dispatch(pretrigger)
def on_message_sent(self, raw): """Handle any message sent through the connection. :param str raw: raw text message sent through the connection When a message is sent through the IRC connection, the bot will log the raw message. If necessary, it will also simulate the `echo-message`_ feature of IRCv3. .. _echo-message: https://ircv3.net/irc/#echo-message """ # Log raw message self.log_raw(raw, '>>') # Simulate echo-message no_echo = 'echo-message' not in self.enabled_capabilities echoed = ['PRIVMSG', 'NOTICE'] if no_echo and any(raw.upper().startswith(cmd) for cmd in echoed): # Use the hostmask we think the IRC server is using for us, # or something reasonable if that's not available host = 'localhost' if self.settings.core.bind_host: host = self.settings.core.bind_host else: try: host = self.hostmask except KeyError: pass # we tried, and that's good enough pretrigger = trigger.PreTrigger( self.nick, ":{0}!{1}@{2} {3}".format(self.nick, self.user, host, raw), url_schemes=self.settings.core.auto_url_schemes, ) self.dispatch(pretrigger)
def test_register_urls(tmpconfig): sopel = bot.Sopel(tmpconfig) @module.url(r'https://(\S+)/(.+)?') @plugin.label('handle_urls_https') def url_callback_https(bot, trigger, match): pass @module.url(r'http://(\S+)/(.+)?') @plugin.label('handle_urls_http') def url_callback_http(bot, trigger, match): pass # prepare callables to be registered callables = [ url_callback_https, url_callback_http, ] # clean callables and set plugin name by hand # since the loader and plugin handlers are excluded here for handler in callables: loader.clean_callable(handler, tmpconfig) handler.plugin_name = 'testplugin' # register callables sopel.register_urls(callables) # trigger URL callback "handle_urls_https" line = ':[email protected] PRIVMSG #sopel :https://example.com/test' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 1 assert matches[0][0].get_rule_label() == 'handle_urls_https' # trigger URL callback "handle_urls_https" line = ':[email protected] PRIVMSG #sopel :http://example.com/test' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 1 assert matches[0][0].get_rule_label() == 'handle_urls_http'
def test_url_triggers_rules_and_auto_title(mockbot): line = ':[email protected] PRIVMSG #sopel :https://not.example.com/test' pretrigger = trigger.PreTrigger(mockbot.nick, line) results = mockbot.rules.get_triggered_rules(mockbot, pretrigger) assert len(results) == 1, 'Only one should match' result = results[0] assert isinstance(result[0], plugins.rules.Rule) assert result[0].get_rule_label() == 'title_auto' line = ':[email protected] PRIVMSG #sopel :https://example.com/test' pretrigger = trigger.PreTrigger(mockbot.nick, line) results = mockbot.rules.get_triggered_rules(mockbot, pretrigger) assert len(results) == 2, ( 'Two rules should match: title_auto and handle_urls_https') labels = sorted(result[0].get_rule_label() for result in results) expected = ['handle_urls_https', 'title_auto'] assert labels == expected
def test_call_rule_rate_limited_user(mockbot): items = [] # setup def testrule(bot, trigger): bot.say('hi') items.append(1) return "Return Value" rule_hello = rules.Rule( [re.compile(r'(hi|hello|hey|sup)')], plugin='testplugin', label='testrule', handler=testrule, rate_limit=100, threaded=False, ) # trigger line = ':[email protected] PRIVMSG #channel :hello' pretrigger = trigger.PreTrigger(mockbot.nick, line) # match matches = list(rule_hello.match(mockbot, pretrigger)) match = matches[0] # trigger and wrapper rule_trigger = trigger.Trigger(mockbot.settings, pretrigger, match, account=None) wrapper = bot.SopelWrapper(mockbot, rule_trigger) # call rule mockbot.call_rule(rule_hello, wrapper, rule_trigger) # assert the rule has been executed assert mockbot.backend.message_sent == rawlist('PRIVMSG #channel :hi') assert items == [1] # assert the rule is now rate limited assert rule_hello.is_rate_limited(Identifier('Test')) assert not rule_hello.is_channel_rate_limited('#channel') assert not rule_hello.is_global_rate_limited() # call rule again mockbot.call_rule(rule_hello, wrapper, rule_trigger) # assert no new message assert mockbot.backend.message_sent == rawlist( 'PRIVMSG #channel :hi'), 'There must not be any new message sent' assert items == [1], 'There must not be any new item'
def __call__( self, mockbot: bot.Sopel, raw: str, pattern: Optional[str] = None, ) -> trigger.Trigger: match = re.match(pattern or r'.*', raw) if match is None: raise ValueError( 'Cannot create a Trigger without a matching pattern') url_schemes = mockbot.settings.core.auto_url_schemes pretrigger = trigger.PreTrigger( mockbot.nick, raw, url_schemes=url_schemes, identifier_factory=mockbot.make_identifier, ) return trigger.Trigger(mockbot.settings, pretrigger, match)
def on_message(self, message): """Handle an incoming IRC message. :param str message: the received raw IRC message """ self.last_raw_line = message pretrigger = trigger.PreTrigger( self.nick, message, url_schemes=self.settings.core.auto_url_schemes, ) if all(cap not in self.enabled_capabilities for cap in ['account-tag', 'extended-join']): pretrigger.tags.pop('account', None) if pretrigger.event == 'PING': self.backend.send_pong(pretrigger.args[-1]) elif pretrigger.event == 'ERROR': LOGGER.error("ERROR received from server: %s", pretrigger.args[-1]) self.backend.on_irc_error(pretrigger) self.dispatch(pretrigger)
def test_register_callables(tmpconfig): sopel = bot.Sopel(tmpconfig) @module.rule(r'(hi|hello|hey|sup)') def rule_hello(bot, trigger): pass @plugin.find(r'(hi|hello|hey|sup)') def rule_find_hello(bot, trigger): pass @plugin.search(r'(hi|hello|hey|sup)') def rule_search_hello(bot, trigger): pass @module.commands('do') @module.example('.do nothing') def command_do(bot, trigger): """The do command does nothing.""" pass @module.commands('main sub') @module.example('.main sub') def command_main_sub(bot, trigger): """A command with subcommand sub.""" pass @module.commands('main other') @module.example('.main other') def command_main_other(bot, trigger): """A command with subcommand other.""" pass @module.nickname_commands('info') @module.example('$nickname: info about this') def nick_command_info(bot, trigger): """Ask Sopel to get some info about nothing.""" pass @module.action_commands('tell') def action_command_tell(bot, trigger): pass @module.commands('mixed') @module.rule('mixing') def mixed_rule_command(bot, trigger): pass @module.event('JOIN') @plugin.label('handle_join_event') def on_join(bot, trigger): pass # prepare callables to be registered callables = [ rule_hello, rule_find_hello, rule_search_hello, command_do, command_main_sub, command_main_other, nick_command_info, action_command_tell, mixed_rule_command, on_join, ] # clean callables and set plugin name by hand # since the loader and plugin handlers are excluded here for handler in callables: loader.clean_callable(handler, tmpconfig) handler.plugin_name = 'testplugin' # register callables sopel.register_callables(callables) # trigger rule "hello" line = ':[email protected] PRIVMSG #sopel :hello' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 3 assert matches[0][0].get_rule_label() == 'rule_hello' assert matches[1][0].get_rule_label() == 'rule_find_hello' assert matches[2][0].get_rule_label() == 'rule_search_hello' # trigger command "do" line = ':[email protected] PRIVMSG #sopel :.do' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 1 assert matches[0][0].get_rule_label() == 'do' # trigger command with subcommand "main-sub" line = ':[email protected] PRIVMSG #sopel :.main sub' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 1 assert matches[0][0].get_rule_label() == 'main-sub' # trigger command with the other subcommand "main-other" line = ':[email protected] PRIVMSG #sopel :.main other' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 1 assert matches[0][0].get_rule_label() == 'main-other' # trigger nick command "info" line = ':[email protected] PRIVMSG #sopel :TestBot: info' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 1 assert matches[0][0].get_rule_label() == 'info' # trigger action command "tell" line = ':[email protected] PRIVMSG #sopel :\x01ACTION tell\x01' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 1 assert matches[0][0].get_rule_label() == 'tell' # trigger rules with event line = ':[email protected] JOIN #Sopel' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 1 assert matches[0][0].get_rule_label() == 'handle_join_event' # trigger command "mixed" line = ':[email protected] PRIVMSG #sopel :.mixed' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 1 assert matches[0][0].get_rule_label() == 'mixed' # trigger rule "mixed_rule_command" line = ':[email protected] PRIVMSG #sopel :mixing' pretrigger = trigger.PreTrigger(sopel.nick, line) matches = sopel.rules.get_triggered_rules(sopel, pretrigger) assert len(matches) == 1 assert matches[0][0].get_rule_label() == 'mixed_rule_command' # check documentation assert sopel.command_groups == { 'testplugin': ['do', 'info', 'main other', 'main sub', 'mixed'], } assert sopel.doc == { 'do': ( ['The do command does nothing.'], ['.do nothing'], ), 'info': ( ['Ask Sopel to get some info about nothing.'], ['TestBot: info about this'], ), 'main sub': ( ['A command with subcommand sub.'], ['.main sub'], ), 'main other': ( ['A command with subcommand other.'], ['.main other'], ), 'mixed': ( [], [], ) }
def test(configfactory, botfactory, ircfactory): test_config = TEST_CONFIG.format( name='NickName', admin=admin, owner=owner, ) settings = configfactory('default.cfg', test_config) url_schemes = settings.core.auto_url_schemes mockbot = botfactory(settings) server = ircfactory(mockbot) server.channel_joined('#Sopel') if not hasattr(tested_func, 'commands'): raise AssertionError('Function is not a command.') loader.clean_callable(tested_func, settings) test_rule = plugins.rules.Command.from_callable(settings, tested_func) parse_results = list(test_rule.parse(msg)) assert parse_results, "Example did not match any command." match = parse_results[0] sender = mockbot.nick if privmsg else "#channel" hostmask = "%s!%s@%s" % (mockbot.nick, "UserName", "example.com") # TODO enable message tags full_message = ':{} PRIVMSG {} :{}'.format(hostmask, sender, msg) pretrigger = trigger.PreTrigger( mockbot.nick, full_message, url_schemes=url_schemes) test_trigger = trigger.Trigger(mockbot.settings, pretrigger, match) pattern = re.compile(r'^%s: ' % re.escape(mockbot.nick)) # setup module module = sys.modules[tested_func.__module__] if hasattr(module, 'setup'): module.setup(mockbot) def isnt_ignored(value): """Return True if value doesn't match any re in ignore list.""" return not any( re.match(ignored_line, value) for ignored_line in ignore) expected_output_count = 0 for _i in range(repeat): expected_output_count += len(results) wrapper = bot.SopelWrapper(mockbot, test_trigger) tested_func(wrapper, test_trigger) output_triggers = ( trigger.PreTrigger( mockbot.nick, message.decode('utf-8'), url_schemes=url_schemes, ) for message in wrapper.backend.message_sent ) output_texts = ( # subtract "Sopel: " when necessary pattern.sub('', output_trigger.args[-1]) for output_trigger in output_triggers ) outputs = [text for text in output_texts if isnt_ignored(text)] # output length assert len(outputs) == expected_output_count # output content for expected, output in zip(results, outputs): if use_regexp: message = ( "Output does not match the regex:\n" "Pattern: %s\n" "Output: %s" ) % (expected, output) if not re.match(expected, output): raise AssertionError(message) else: assert expected == output
def __call__(self, mockbot, raw, pattern=None): url_schemes = mockbot.settings.core.auto_url_schemes return trigger.Trigger( mockbot.settings, trigger.PreTrigger(mockbot.nick, raw, url_schemes=url_schemes), re.match(pattern or r'.*', raw))
def __call__(self, mockbot, raw, pattern=None): return trigger.Trigger(mockbot.settings, trigger.PreTrigger(mockbot.nick, raw), re.match(pattern or r'.*', raw))
def line(sopel, raw): return trigger.Trigger(sopel.settings, trigger.PreTrigger(sopel.nick, raw), re.match('.*', raw))