irctest/irctest/irc_utils/message_parser.py

77 lines
2.1 KiB
Python
Raw Permalink Normal View History

import dataclasses
2021-02-22 18:04:23 +00:00
import re
from typing import Any, Dict, List, Optional
2020-09-13 10:38:15 +00:00
from .junkdrawer import MultipleReplacer
# http://ircv3.net/specs/core/message-tags-3.2.html#escaping-values
TAG_ESCAPE = [
2021-02-22 18:02:13 +00:00
("\\", "\\\\"), # \ -> \\
(" ", r"\s"),
(";", r"\:"),
("\r", r"\r"),
("\n", r"\n"),
]
unescape_tag_value = MultipleReplacer(dict(map(lambda x: (x[1], x[0]), TAG_ESCAPE)))
# TODO: validate host
tag_key_validator = re.compile(r"^\+?(\S+/)?[a-zA-Z0-9-]+$")
2021-02-22 18:02:13 +00:00
def parse_tags(s: str) -> Dict[str, Optional[str]]:
tags: Dict[str, Optional[str]] = {}
2021-02-22 18:02:13 +00:00
for tag in s.split(";"):
if "=" not in tag:
tags[tag] = None
else:
2021-02-22 18:02:13 +00:00
(key, value) = tag.split("=", 1)
assert tag_key_validator.match(key), "Invalid tag key: {}".format(key)
tags[key] = unescape_tag_value(value)
return tags
2021-02-22 18:02:13 +00:00
@dataclasses.dataclass(frozen=True)
class HistoryMessage:
time: Any
msgid: Optional[str]
target: str
text: str
@dataclasses.dataclass(frozen=True)
class Message:
tags: Dict[str, Optional[str]]
prefix: Optional[str]
command: str
params: List[str]
def to_history_message(self) -> HistoryMessage:
return HistoryMessage(
time=self.tags.get("time"),
msgid=self.tags.get("msgid"),
target=self.params[0],
text=self.params[1],
)
2021-02-22 18:02:13 +00:00
def parse_message(s: str) -> Message:
"""Parse a message according to
http://tools.ietf.org/html/rfc1459#section-2.3.1
and
http://ircv3.net/specs/core/message-tags-3.2.html"""
2021-02-22 18:02:13 +00:00
s = s.rstrip("\r\n")
if s.startswith("@"):
(tags_str, s) = s.split(" ", 1)
tags = parse_tags(tags_str[1:])
else:
2020-01-26 01:59:55 +00:00
tags = {}
2021-02-22 18:02:13 +00:00
if " :" in s:
(other_tokens, trailing_param) = s.split(" :", 1)
tokens = list(filter(bool, other_tokens.split(" "))) + [trailing_param]
else:
2021-02-22 18:02:13 +00:00
tokens = list(filter(bool, s.split(" ")))
prefix = prefix = tokens.pop(0)[1:] if tokens[0].startswith(":") else None
command = tokens.pop(0)
params = tokens
return Message(tags=tags, prefix=prefix, command=command, params=params)