2019-02-10 14:18:33 +00:00
|
|
|
import datetime, typing
|
2019-06-06 08:29:17 +00:00
|
|
|
from src import IRCObject, utils
|
2019-02-10 18:43:04 +00:00
|
|
|
|
2019-02-10 22:52:23 +00:00
|
|
|
# this should be 510 (RFC1459, 512 with \r\n) but a server BitBot uses is broken
|
2019-02-10 20:54:12 +00:00
|
|
|
LINE_CUTOFF = 470
|
2019-02-10 14:18:33 +00:00
|
|
|
|
2019-02-23 21:33:04 +00:00
|
|
|
class IRCArgs(object):
|
|
|
|
def __init__(self, args: typing.List[str]):
|
|
|
|
self._args = args
|
|
|
|
|
|
|
|
def get(self, index: int) -> typing.Optional[str]:
|
2019-03-08 23:35:52 +00:00
|
|
|
if index < 0:
|
|
|
|
if len(self._args) > (abs(index)-1):
|
|
|
|
return self._args[index]
|
|
|
|
elif len(self._args) > index:
|
2019-02-23 21:33:04 +00:00
|
|
|
return self._args[index]
|
|
|
|
return None
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "IRCArgs(%s)" % self._args
|
|
|
|
def __len__(self) -> int:
|
|
|
|
return len(self._args)
|
2019-02-23 21:39:46 +00:00
|
|
|
def __getitem__(self, index: int) -> str:
|
2019-02-23 21:33:04 +00:00
|
|
|
return self._args[index]
|
2019-02-23 21:39:46 +00:00
|
|
|
def __setitem__(self, index: int, value: str):
|
|
|
|
self._args[index] = value
|
2019-02-23 21:33:04 +00:00
|
|
|
|
|
|
|
class Hostmask(object):
|
|
|
|
def __init__(self, nickname: str, username: str, hostname: str,
|
|
|
|
hostmask: str):
|
|
|
|
self.nickname = nickname
|
|
|
|
self.username = username
|
|
|
|
self.hostname = hostname
|
|
|
|
self.hostmask = hostmask
|
|
|
|
def __repr__(self):
|
|
|
|
return "Hostmask(%s)" % self.__str__()
|
|
|
|
def __str__(self):
|
|
|
|
return self.hostmask
|
|
|
|
|
|
|
|
class ParsedLine(object):
|
|
|
|
def __init__(self, command: str, args: typing.List[str],
|
2019-06-03 12:30:39 +00:00
|
|
|
source: Hostmask=None,
|
2019-05-30 14:30:15 +00:00
|
|
|
tags: typing.Dict[str, str]=None):
|
2019-02-23 21:33:04 +00:00
|
|
|
self.command = command
|
|
|
|
self._args = args
|
|
|
|
self.args = IRCArgs(args)
|
2019-06-03 12:30:39 +00:00
|
|
|
self.source = source
|
2019-02-23 21:33:04 +00:00
|
|
|
self.tags = {} if tags == None else tags
|
2019-06-10 09:11:31 +00:00
|
|
|
self._valid = True
|
2019-06-10 09:45:02 +00:00
|
|
|
self._assured = False
|
2019-02-23 21:33:04 +00:00
|
|
|
|
2019-06-01 21:28:37 +00:00
|
|
|
def __repr__(self):
|
|
|
|
return "ParsedLine(%s)" % self.__str__()
|
|
|
|
def __str__(self):
|
|
|
|
return self.format()
|
|
|
|
|
2019-06-10 09:11:31 +00:00
|
|
|
def valid(self) -> bool:
|
|
|
|
return self._valid
|
|
|
|
def invalidate(self):
|
|
|
|
self._valid = False
|
|
|
|
|
2019-06-10 09:45:02 +00:00
|
|
|
def assured(self) -> bool:
|
|
|
|
return self._assured
|
|
|
|
def assure(self):
|
|
|
|
self._assured = True
|
|
|
|
|
2019-02-23 21:33:04 +00:00
|
|
|
def _tag_str(self, tags: typing.Dict[str, str]) -> str:
|
2019-02-27 22:14:53 +00:00
|
|
|
tag_pieces = []
|
2019-02-23 21:33:04 +00:00
|
|
|
for tag, value in tags.items():
|
|
|
|
if value:
|
2019-06-06 08:29:17 +00:00
|
|
|
value_escaped = utils.irc.message_tag_escape(value)
|
|
|
|
tag_pieces.append("%s=%s" % (tag, value_escaped))
|
2019-02-27 22:14:53 +00:00
|
|
|
else:
|
|
|
|
tag_pieces.append(tag)
|
|
|
|
|
|
|
|
if tag_pieces:
|
2019-06-06 16:19:27 +00:00
|
|
|
return "@%s" % ";".join(tag_pieces)
|
2019-02-27 22:14:53 +00:00
|
|
|
return ""
|
2019-02-23 21:33:04 +00:00
|
|
|
|
|
|
|
def format(self) -> str:
|
2019-02-27 22:14:53 +00:00
|
|
|
pieces = []
|
2019-02-23 21:33:04 +00:00
|
|
|
if self.tags:
|
2019-02-27 22:14:53 +00:00
|
|
|
pieces.append(self._tag_str(self.tags))
|
2019-02-23 21:33:04 +00:00
|
|
|
|
2019-06-03 12:30:39 +00:00
|
|
|
if self.source:
|
|
|
|
pieces.append(str(self.source))
|
2019-02-10 18:43:04 +00:00
|
|
|
|
2019-02-27 22:14:53 +00:00
|
|
|
pieces.append(self.command.upper())
|
2019-02-10 18:43:04 +00:00
|
|
|
|
2019-02-23 21:33:04 +00:00
|
|
|
if self.args:
|
2019-02-27 22:14:53 +00:00
|
|
|
for i, arg in enumerate(self._args):
|
2019-03-08 23:23:09 +00:00
|
|
|
if arg and i == len(self._args)-1 and (
|
|
|
|
" " in arg or arg[0] == ":"):
|
2019-02-27 22:14:53 +00:00
|
|
|
pieces.append(":%s" % arg)
|
|
|
|
else:
|
|
|
|
pieces.append(arg)
|
2019-02-23 21:33:04 +00:00
|
|
|
|
2019-02-27 22:14:53 +00:00
|
|
|
return " ".join(pieces).split("\n")[0].strip("\r")
|
2019-02-23 21:33:04 +00:00
|
|
|
|
2019-02-24 08:24:58 +00:00
|
|
|
class SentLine(IRCObject.Object):
|
2019-06-04 16:51:20 +00:00
|
|
|
def __init__(self, events: "EventManager.EventHook",
|
|
|
|
send_time: datetime.datetime, hostmask: str, line: ParsedLine):
|
|
|
|
self.events = events
|
2019-02-23 21:33:04 +00:00
|
|
|
self.send_time = send_time
|
|
|
|
self._hostmask = hostmask
|
|
|
|
self.parsed_line = line
|
|
|
|
|
|
|
|
self.truncate_marker: typing.Optional[str] = None
|
2019-02-10 14:18:33 +00:00
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
2019-02-24 08:24:58 +00:00
|
|
|
return "IRCLine.SentLine(%s)" % self.__str__()
|
2019-02-10 14:18:33 +00:00
|
|
|
def __str__(self) -> str:
|
2019-02-10 23:57:38 +00:00
|
|
|
return self.decoded_data()
|
2019-02-10 14:18:33 +00:00
|
|
|
|
2019-02-10 23:52:25 +00:00
|
|
|
def _char_limit(self) -> int:
|
2019-02-23 21:33:04 +00:00
|
|
|
return LINE_CUTOFF-len(":%s " % self._hostmask)
|
2019-02-10 20:49:59 +00:00
|
|
|
|
2019-02-23 21:33:04 +00:00
|
|
|
def _encode_truncate(self) -> typing.Tuple[bytes, str]:
|
|
|
|
line = self.parsed_line.format()
|
|
|
|
byte_max = self._char_limit()
|
|
|
|
encoded = b""
|
|
|
|
truncated = ""
|
|
|
|
truncate_marker = b""
|
|
|
|
if not self.truncate_marker == None:
|
|
|
|
truncate_marker = typing.cast(str, self.truncate_marker
|
|
|
|
).encode("utf8")
|
|
|
|
|
|
|
|
for i, character in enumerate(line):
|
|
|
|
encoded_character = character.encode("utf8")
|
|
|
|
new_len = len(encoded + encoded_character)
|
|
|
|
if truncate_marker and (byte_max-new_len) < len(truncate_marker):
|
|
|
|
encoded += truncate_marker
|
|
|
|
truncated = line[i:]
|
|
|
|
break
|
|
|
|
elif new_len > byte_max:
|
|
|
|
truncated = line[i:]
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
encoded += encoded_character
|
|
|
|
return (encoded, truncated)
|
|
|
|
|
2019-06-04 13:28:35 +00:00
|
|
|
def _for_wire(self) -> bytes:
|
2019-02-23 21:33:04 +00:00
|
|
|
return self._encode_truncate()[0]
|
2019-06-04 13:28:35 +00:00
|
|
|
def for_wire(self) -> bytes:
|
|
|
|
return b"%s\r\n" % self._for_wire()
|
|
|
|
|
2019-02-10 23:52:25 +00:00
|
|
|
def decoded_data(self) -> str:
|
2019-06-04 13:28:35 +00:00
|
|
|
return self._for_wire().decode("utf8")
|
2019-02-10 20:49:59 +00:00
|
|
|
def truncated(self) -> str:
|
2019-02-23 21:33:04 +00:00
|
|
|
return self._encode_truncate()[1]
|