示例#1
0
def test_param_construct():
    msg = Message(None, None, "PRIVMSG", "#channel", "Message thing")
    assert str(msg) == "PRIVMSG #channel :Message thing"

    msg = Message(None, None, "PRIVMSG", ["#channel", "Message thing"])
    assert str(msg) == "PRIVMSG #channel :Message thing"

    msg = Message(None, None, "PRIVMSG", ["#channel", ":Message thing"])
    assert str(msg) == "PRIVMSG #channel ::Message thing"

    msg = Message(None, None, "PRIVMSG", [""])
    assert str(msg) == "PRIVMSG :"
示例#2
0
def test_line():
    assert Message.parse("COMMAND") == "COMMAND"
    assert Message.parse("command") == "COMMAND"

    msg1 = Message.parse("PRIVMSG")

    assert msg1.command == "PRIVMSG"

    msg2 = Message.parse(
        "@test=data;test1=more\sdata :nick!user@host COMMAND arg1 arg2 :trailing text"
    )

    assert msg2.prefix.host == "host"
def test_check_send_key(mock_db):
    conn = make_conn()
    db = mock_db.session()
    chan_key_db.table.create(mock_db.engine)
    msg = Message(None, None, "JOIN", ["#foo,#bar", "bing"])
    assert chan_key_db.check_send_key(conn, msg, db) is msg
    assert conn.get_channel_key("#foo") == "bing"

    msg = Message(None, None, "PRIVMSG", ["#foo,#bar", "bing"])
    assert chan_key_db.check_send_key(conn, msg, db) is msg

    msg = Message(None, None, "JOIN", ["#foo,#bar"])
    assert chan_key_db.check_send_key(conn, msg, db) is msg
    assert conn.get_channel_key("#foo") == "bing"
示例#4
0
 def transcode_off(self, data: bytes) -> None:
     self._buff += data
     while b'\r\n' in self._buff:
         raw_line, self._buff = self._buff.split(b'\r\n', 1)
         message = Message.parse(raw_line)
         for trigger, func in self.handlers.values():
             if trigger in (message.command, '*'):
                 self.loop.create_task(func(self, message))
示例#5
0
def test_trail():
    """Ensure this parser does not have the same issue as https://github.com/hexchat/hexchat/issues/2271"""
    text = "COMMAND thing thing :thing"
    parsed = Message.parse(text)

    assert "COMMAND" == parsed.command

    for i in range(3):
        assert "thing" == parsed.parameters[i]
示例#6
0
 def data_received(self, data: bytes) -> None:
     """Called by the event loop when data has been read from the socket"""
     self._buff += data
     while b'\r\n' in self._buff:
         raw_line, self._buff = self._buff.split(b'\r\n', 1)
         message = Message.parse(raw_line)
         for trigger, func in self.handlers.values():
             if trigger in (message.command, '*'):
                 self.loop.create_task(func(self, message))
示例#7
0
 def cmd(self, command, *params):
     """
     Sends a raw IRC command of type <command> with params <params>
     :param command: The IRC command to send
     :param params: The params to the IRC command
     :type command: str
     :type params: (str)
     """
     params = list(map(str, params))  # turn the tuple of parameters into a list
     self.send(str(Message(None, None, command, params)))
示例#8
0
    def prepare_threaded(self):
        super().prepare_threaded()

        if "parsed_line" in self.hook.required_args:
            try:
                self.parsed_line = Message.parse(self.line)
            except Exception:
                logger.exception("Unable to parse line requested by hook %s",
                                 self.hook)
                self.parsed_line = None
示例#9
0
def test_names_handling():
    from plugins.core.server_info import handle_prefixes, handle_chan_modes
    from plugins.core.chan_track import on_join, on_part, on_kick, on_quit, on_names

    handlers = {
        "JOIN": on_join,
        "PART": on_part,
        "QUIT": on_quit,
        "KICK": on_kick,
        "353": on_names,
        "366": on_names,
    }

    chan_pos = {
        "JOIN": 0,
        "PART": 0,
        "KICK": 0,
        "353": 2,
        "366": 1,
    }

    bot = MagicMock()
    bot.loop = asyncio.get_event_loop()

    conn = MockConn(bot)
    serv_info = conn.memory["server_info"]
    handle_prefixes("(YohvV)!@%+-", serv_info)
    handle_chan_modes("IXZbegw,k,FHJLWdfjlx,ABCDKMNOPQRSTcimnprstuz",
                      serv_info)

    for line in NAMES_MOCK_TRAFFIC:
        line = Message.parse(line)
        data = {
            "nick": line.prefix.nick,
            "user": line.prefix.ident,
            "host": line.prefix.host,
            "conn": conn,
            "irc_paramlist": line.parameters,
            "irc_command": line.command,
            "chan": None,
            "target": None,
        }

        if line.command in chan_pos:
            data["chan"] = line.parameters[chan_pos[line.command]]

        if line.command == "KICK":
            data["target"] = line.parameters[1]

        call_with_args(handlers[line.command], data)
示例#10
0
def test_names_handling():
    from plugins.core.server_info import handle_prefixes, handle_chan_modes
    from plugins.chan_track import on_join, on_part, on_kick, on_quit, on_names

    handlers = {
        'JOIN': on_join,
        'PART': on_part,
        'QUIT': on_quit,
        'KICK': on_kick,
        '353': on_names,
        '366': on_names,
    }

    chan_pos = {
        'JOIN': 0,
        'PART': 0,
        'KICK': 0,
        '353': 2,
        '366': 1,
    }

    bot = MagicMock()
    bot.loop = asyncio.get_event_loop()

    conn = MockConn(bot)
    serv_info = conn.memory['server_info']
    handle_prefixes('(YohvV)!@%+-', serv_info)
    handle_chan_modes('IXZbegw,k,FHJLWdfjlx,ABCDKMNOPQRSTcimnprstuz',
                      serv_info)

    for line in NAMES_MOCK_TRAFFIC:
        line = Message.parse(line)
        data = {
            'nick': line.prefix.nick,
            'user': line.prefix.ident,
            'host': line.prefix.host,
            'conn': conn,
            'irc_paramlist': line.parameters,
            'irc_command': line.command,
            'chan': None,
            'target': None,
        }

        if line.command in chan_pos:
            data['chan'] = line.parameters[chan_pos[line.command]]

        if line.command == 'KICK':
            data['target'] = line.parameters[1]

        call_with_args(handlers[line.command], data)
示例#11
0
def test_msg_join(data):
    """
    Ensure that message building passes all tests from
    the irc-parser-tests library.
    """
    atoms = data["atoms"]
    msg = Message(
        atoms.pop("tags", None),
        atoms.pop("source", None),
        atoms.pop("verb", None),
        atoms.pop("params", []),
    )

    assert not atoms, "Not all atoms were handled"

    matches = data["matches"]
    assert str(msg) in matches
示例#12
0
def test_msg_join(data):
    atoms = data['atoms']
    msg = Message(
        atoms.pop('tags', None),
        atoms.pop('source', None),
        atoms.pop('verb', None),
        atoms.pop('params', []),
    )

    assert not atoms, "Not all atoms were handled"

    matches = data['matches']
    if len(matches) > 1:
        assert any(str(msg) == match for match in data['matches'])
    else:
        # With single matches, make it easier to debug
        assert str(msg) == matches[0]
示例#13
0
def test_msg_split(data):
    msg = Message.parse(data['input'])
    atoms = data['atoms'].copy()

    # We store tags a bit differently than the test data expects, convert the format
    if msg.tags is not None:
        tags_dict = {name: tag.value for name, tag in msg.tags.items()}
    else:
        tags_dict = None

    assert tags_dict == atoms.pop('tags', None)

    prefix = None if msg.prefix is None else str(msg.prefix)
    assert prefix == atoms.pop('source', None)

    # Commands are case-insensitive
    assert String(msg.command, ASCII) == atoms.pop('verb', None)

    assert list(msg.parameters) == atoms.pop('params', [])

    # Make sure we handled everything
    assert not atoms
示例#14
0
def test_msg_split(data):
    """Test splitting a message against the irc-parser-tests data"""
    msg = Message.parse(data["input"])
    atoms = data["atoms"].copy()

    # We store tags a bit differently than the test data expects, convert the format
    if msg.tags is not None:
        tags_dict = {name: tag.value for name, tag in msg.tags.items()}
    else:
        tags_dict = None

    assert tags_dict == atoms.pop("tags", None)

    prefix = None if msg.prefix is None else str(msg.prefix)
    assert prefix == atoms.pop("source", None)

    # Commands are case-insensitive
    assert String(msg.command, ASCII) == atoms.pop("verb", None)

    assert list(msg.parameters) == atoms.pop("params", [])

    # Make sure we handled everything
    assert not atoms
示例#15
0
class TestMessage:
    def test_parse_bytes(self):
        line = Message.parse(b"COMMAND some params :and stuff")
        assert line.command == 'COMMAND'
        assert line.parameters == ['some', 'params', 'and stuff']

    @pytest.mark.parametrize('obj,text', [
        (Message(None, None, None), ''),
        (Message(None, None, None, None), ''),
        (Message(None, None, None, []), ''),
        (Message(None, None, 'COMMAND'), 'COMMAND'),
        (Message(['a=b'], None, 'COMMAND'), '@a=b COMMAND'),
        (Message([MessageTag('a', 'b')], None, 'COMMAND'), '@a=b COMMAND'),
        (Message({'a': 'b'}, None, 'COMMAND'), '@a=b COMMAND'),
        (Message({'a': 'b'}, 'nick', 'COMMAND'), '@a=b :nick COMMAND'),
        (Message(None, ('nick', ), 'COMMAND'), ':nick COMMAND'),
        (Message(None, ('nick', 'user'), 'COMMAND'), ':nick!user COMMAND'),
        (Message(None, ('nick', 'user', 'host'),
                 'COMMAND'), ':nick!user@host COMMAND'),
        (Message({'a': 'b'}, 'nick', 'COMMAND', 'a',
                 'b'), '@a=b :nick COMMAND a b'),
    ])
    def test_str(self, obj, text):
        assert str(obj) == text

    @pytest.mark.parametrize('tags,prefix,command,params', [
        (None, None, None, None),
        ('some tag', None, 'COMMAND', ['param', '']),
    ])
    def test_eq(self, tags, prefix, command, params):
        assert Message(tags, prefix, command,
                       params) == Message(tags, prefix, command, params)

    @pytest.mark.parametrize('tags,prefix,command,params', [
        (None, None, None, None),
        ('some tag', None, 'COMMAND', ['param', '']),
    ])
    def test_ne(self, tags, prefix, command, params):
        assert not (Message(tags, prefix, command, params) != Message(
            tags, prefix, command, params))

    @pytest.mark.parametrize('obj,other', [
        (Message(None, None, None), 0),
        (Message(None, None, None), None),
        (Message(None, None, None), ()),
    ])
    def test_no_cmp(self, obj, other):
        assert obj != other
        assert other != obj

        assert not (obj == other)
        assert not (other == obj)

    @pytest.mark.parametrize('obj', [
        Message(None, None, 'COMMAND'),
    ])
    def test_bool(self, obj):
        assert obj

    @pytest.mark.parametrize('obj', [
        Message(None, None, None),
        Message(None, None, ''),
        Message(None, '', ''),
        Message('', '', ''),
        Message([], [], '', []),
        Message({}, [], '', []),
        Message(TagList(), Prefix(), '', ParamList()),
    ])
    def test_bool_false(self, obj):
        assert not obj
示例#16
0
def test_has_trail(text, has_trail):
    """Ensure that a message with trailing arguments is recorded as having a tril"""
    msg = Message.parse(text)
    assert msg.parameters.has_trail == has_trail
示例#17
0
 def test_ne(self, tags, prefix, command, params):
     """Test not-equals"""
     b = Message(tags, prefix, command, params) != Message(
         tags, prefix, command, params
     )
     assert not b
示例#18
0
 def test_eq(self, tags, prefix, command, params):
     assert Message(tags, prefix, command,
                    params) == Message(tags, prefix, command, params)
示例#19
0
 def test_parse_bytes(self):
     """Test parsing bytes"""
     line = Message.parse(b"COMMAND some params :and stuff")
     assert line.command == "COMMAND"
     assert line.parameters == ["some", "params", "and stuff"]
示例#20
0
 def test_parse_bytes(self):
     line = Message.parse(b"COMMAND some params :and stuff")
     assert line.command == 'COMMAND'
     assert line.parameters == ['some', 'params', 'and stuff']
示例#21
0
    def data_received(self, data):
        self._input_buffer += data

        while b"\r\n" in self._input_buffer:
            line_data, self._input_buffer = self._input_buffer.split(b"\r\n", 1)
            line = decode(line_data)

            try:
                message = Message.parse(line)
            except Exception:
                logger.exception(
                    "[%s] Error occurred while parsing IRC line '%s' from %s",
                    self.conn.name, line, self.conn.describe_server()
                )
                continue

            command = message.command
            command_params = message.parameters

            # Reply to pings immediately

            if command == "PING":
                self.conn.send("PONG " + command_params[-1], log=False)

            # Parse the command and params

            # Content
            if command_params.has_trail:
                content_raw = command_params[-1]
                content = irc_clean(content_raw)
            else:
                content_raw = None
                content = None

            # Event type
            event_type = irc_command_to_event_type.get(
                command, EventType.other
            )

            # Target (for KICK, INVITE)
            if event_type is EventType.kick:
                target = command_params[1]
            elif command in ("INVITE", "MODE"):
                target = command_params[0]
            else:
                # TODO: Find more commands which give a target
                target = None

            # Parse for CTCP
            if event_type is EventType.message and content_raw.startswith("\x01"):
                possible_ctcp = content_raw[1:]
                if content_raw.endswith('\x01'):
                    possible_ctcp = possible_ctcp[:-1]

                if '\x01' in possible_ctcp:
                    logger.debug(
                        "[%s] Invalid CTCP message received, "
                        "treating it as a mornal message",
                        self.conn.name
                    )
                    ctcp_text = None
                else:
                    ctcp_text = possible_ctcp
                    ctcp_text_split = ctcp_text.split(None, 1)
                    if ctcp_text_split[0] == "ACTION":
                        # this is a CTCP ACTION, set event_type and content accordingly
                        event_type = EventType.action
                        content = irc_clean(ctcp_text_split[1])
                    else:
                        # this shouldn't be considered a regular message
                        event_type = EventType.other
            else:
                ctcp_text = None

            # Channel
            channel = None
            if command_params:
                if command in ["NOTICE", "PRIVMSG", "KICK", "JOIN", "PART", "MODE"]:
                    channel = command_params[0]
                elif command == "INVITE":
                    channel = command_params[1]
                elif len(command_params) > 2 or not (command_params.has_trail and len(command_params) == 1):
                    channel = command_params[0]

            prefix = message.prefix

            if prefix is None:
                nick = None
                user = None
                host = None
                mask = None
            else:
                nick = prefix.nick
                user = prefix.user
                host = prefix.host
                mask = prefix.mask

            if channel:
                # TODO Migrate plugins to accept the original case of the channel
                channel = channel.lower()

                channel = channel.split()[0]  # Just in case there is more data

                if channel == self.conn.nick.lower():
                    channel = nick.lower()

            # Set up parsed message
            # TODO: Do we really want to send the raw `prefix` and `command_params` here?
            event = Event(
                bot=self.bot, conn=self.conn, event_type=event_type, content_raw=content_raw, content=content,
                target=target, channel=channel, nick=nick, user=user, host=host, mask=mask, irc_raw=line,
                irc_prefix=mask, irc_command=command, irc_paramlist=command_params, irc_ctcp_text=ctcp_text
            )

            # handle the message, async
            async_util.wrap_future(self.bot.process(event), loop=self.loop)
示例#22
0
def test_has_trail(text, has_trail):
    msg = Message.parse(text)
    assert msg.parameters.has_trail == has_trail
示例#23
0
 def test_eq(self, tags, prefix, command, params):
     """Test equals"""
     assert Message(tags, prefix, command, params) == Message(
         tags, prefix, command, params
     )
示例#24
0
 def test_ne(self, tags, prefix, command, params):
     assert not (Message(tags, prefix, command, params) != Message(
         tags, prefix, command, params))
示例#25
0
    def parse_line(self, line: str) -> Event:
        message = Message.parse(line)
        command = message.command
        command_params = message.parameters

        # Reply to pings immediately
        if command == "PING":
            self.conn.send("PONG " + command_params[-1], log=False)

        # Parse the command and params
        # Content
        content_raw = _get_param(message, content_params)
        if content_raw is not None:
            content = irc_clean(content_raw)
        else:
            content = None

        # Event type
        event_type = irc_command_to_event_type.get(command, EventType.other)
        target = _get_param(message, target_params)

        # Parse for CTCP
        if event_type is EventType.message and content_raw.startswith("\x01"):
            possible_ctcp = content_raw[1:]
            if content_raw.endswith("\x01"):
                possible_ctcp = possible_ctcp[:-1]

            if "\x01" in possible_ctcp:
                logger.debug(
                    "[%s] Invalid CTCP message received, "
                    "treating it as a mornal message",
                    self.conn.name,
                )
                ctcp_text = None
            else:
                ctcp_text = possible_ctcp
                ctcp_text_split = ctcp_text.split(None, 1)
                if ctcp_text_split[0] == "ACTION":
                    # this is a CTCP ACTION, set event_type and content accordingly
                    event_type = EventType.action
                    content = irc_clean(ctcp_text_split[1])
                else:
                    # this shouldn't be considered a regular message
                    event_type = EventType.other
        else:
            ctcp_text = None

        # Channel
        channel = _get_param(message, chan_params)

        prefix = message.prefix
        if prefix is None:
            nick = None
            user = None
            host = None
            mask = None
        else:
            nick = prefix.nick
            user = prefix.user
            host = prefix.host
            mask = prefix.mask

        if channel:
            # TODO Migrate plugins to accept the original case of the channel
            channel = channel.lower()

            channel = channel.split()[0]  # Just in case there is more data

            # Channel for a PM is the sending user
            if channel == self.conn.nick.lower():
                channel = nick.lower()
        else:
            # If the channel isn't set, it's the sending user/server
            channel = nick.lower() if nick else nick

        # Set up parsed message
        # TODO: Do we really want to send the raw `prefix` and `command_params` here?
        event = Event(
            bot=self.bot,
            conn=self.conn,
            event_type=event_type,
            content_raw=content_raw,
            content=content,
            target=target,
            channel=channel,
            nick=nick,
            user=user,
            host=host,
            mask=mask,
            irc_raw=line,
            irc_prefix=mask,
            irc_command=command,
            irc_paramlist=command_params,
            irc_ctcp_text=ctcp_text,
            irc_tags=message.tags,
        )
        return event
示例#26
0
class TestMessage:
    """Test parsing an entire IRC message"""

    def test_parse_bytes(self):
        """Test parsing bytes"""
        line = Message.parse(b"COMMAND some params :and stuff")
        assert line.command == "COMMAND"
        assert line.parameters == ["some", "params", "and stuff"]

    @pytest.mark.parametrize(
        "obj,text",
        [
            (Message(None, None, None), ""),
            (Message(None, None, None, None), ""),
            (Message(None, None, None, []), ""),
            (Message(None, None, "COMMAND"), "COMMAND"),
            (Message(["a=b"], None, "COMMAND"), "@a=b COMMAND"),
            (Message([MessageTag("a", "b")], None, "COMMAND"), "@a=b COMMAND"),
            (Message({"a": "b"}, None, "COMMAND"), "@a=b COMMAND"),
            (Message({"a": "b"}, "nick", "COMMAND"), "@a=b :nick COMMAND"),
            (Message(None, ("nick",), "COMMAND"), ":nick COMMAND"),
            (Message(None, ("nick", "user"), "COMMAND"), ":nick!user COMMAND"),
            (
                Message(None, ("nick", "user", "host"), "COMMAND"),
                ":nick!user@host COMMAND",
            ),
            (
                Message({"a": "b"}, "nick", "COMMAND", "a", "b"),
                "@a=b :nick COMMAND a b",
            ),
        ],
    )
    def test_str(self, obj, text):
        """Test string conversion"""
        assert str(obj) == text

    @pytest.mark.parametrize(
        "tags,prefix,command,params",
        [
            (None, None, None, None),
            ("some tag", None, "COMMAND", ["param", ""]),
        ],
    )
    def test_eq(self, tags, prefix, command, params):
        """Test equals"""
        assert Message(tags, prefix, command, params) == Message(
            tags, prefix, command, params
        )

    @pytest.mark.parametrize(
        "tags,prefix,command,params",
        [
            (None, None, None, None),
            ("some tag", None, "COMMAND", ["param", ""]),
        ],
    )
    def test_ne(self, tags, prefix, command, params):
        """Test not-equals"""
        b = Message(tags, prefix, command, params) != Message(
            tags, prefix, command, params
        )
        assert not b

    @pytest.mark.parametrize(
        "obj,other",
        [
            (Message(None, None, None), 0),
            (Message(None, None, None), None),
            (Message(None, None, None), ()),
        ],
    )
    def test_no_cmp(self, obj, other):
        """Test Message.__ne__"""
        assert obj != other
        assert other != obj

        assert not obj == other
        assert not other == obj

    @pytest.mark.parametrize(
        "obj",
        [
            Message(None, None, "COMMAND"),
        ],
    )
    def test_bool(self, obj):
        """Test the cases where bool(Message) should return True"""
        assert obj

    @pytest.mark.parametrize(
        "obj",
        [
            Message(None, None, None),
            Message(None, None, ""),
            Message(None, "", ""),
            Message("", "", ""),
            Message([], [], "", []),
            Message({}, [], "", []),
            Message(TagList(), Prefix(), "", ParamList()),
        ],
    )
    def test_bool_false(self, obj):
        """Test all the cases where bool(Message) should return False"""
        assert not obj