Example #1
0
    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)
Example #2
0
    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)
Example #3
0
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'
Example #4
0
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
Example #5
0
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'
Example #6
0
    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)
Example #7
0
    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)
Example #8
0
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': (
            [],
            [],
        )
    }
Example #9
0
    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
Example #10
0
 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))
Example #11
0
 def __call__(self, mockbot, raw, pattern=None):
     return trigger.Trigger(mockbot.settings,
                            trigger.PreTrigger(mockbot.nick, raw),
                            re.match(pattern or r'.*', raw))
Example #12
0
def line(sopel, raw):
    return trigger.Trigger(sopel.settings, trigger.PreTrigger(sopel.nick, raw),
                           re.match('.*', raw))