2019-02-10 14:09:27 +00:00
|
|
|
import collections, datetime, socket, ssl, sys, time, typing
|
|
|
|
from src import EventManager, IRCBot, IRCChannel, IRCChannels, IRCLine
|
|
|
|
from src import IRCObject, IRCUser, utils
|
2016-03-29 11:56:58 +00:00
|
|
|
|
2018-08-28 16:30:14 +00:00
|
|
|
THROTTLE_LINES = 4
|
2018-08-29 07:38:43 +00:00
|
|
|
THROTTLE_SECONDS = 1
|
2019-02-10 14:09:27 +00:00
|
|
|
UNTHROTTLED_MAX_LINES = 10
|
|
|
|
|
2018-08-29 13:33:27 +00:00
|
|
|
READ_TIMEOUT_SECONDS = 120
|
|
|
|
PING_INTERVAL_SECONDS = 30
|
2019-02-10 14:09:27 +00:00
|
|
|
|
2019-02-10 14:08:07 +00:00
|
|
|
LINE_CUTOFF = 450
|
2018-08-29 13:33:27 +00:00
|
|
|
|
2018-10-01 12:48:55 +00:00
|
|
|
class Server(IRCObject.Object):
|
2018-10-30 14:58:48 +00:00
|
|
|
def __init__(self,
|
|
|
|
bot: "IRCBot.Bot",
|
|
|
|
events: EventManager.EventHook,
|
2018-11-05 18:23:02 +00:00
|
|
|
id: int,
|
2018-11-05 18:30:14 +00:00
|
|
|
alias: typing.Optional[str],
|
2018-11-05 18:23:02 +00:00
|
|
|
connection_params: utils.irc.IRCConnectionParameters):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.connected = False
|
|
|
|
self.bot = bot
|
2018-08-31 11:55:52 +00:00
|
|
|
self.events = events
|
2016-03-29 11:56:58 +00:00
|
|
|
self.id = id
|
2018-11-05 18:30:14 +00:00
|
|
|
self.alias = alias
|
2018-11-05 18:23:02 +00:00
|
|
|
self.connection_params = connection_params
|
2018-10-31 15:12:46 +00:00
|
|
|
self.name = None # type: typing.Optional[str]
|
2018-09-03 21:17:01 +00:00
|
|
|
|
2018-11-27 12:16:36 +00:00
|
|
|
self.nickname = None # type: typing.Optional[str]
|
|
|
|
self.username = None # type: typing.Optional[str]
|
|
|
|
self.realname = None # type: typing.Optional[str]
|
|
|
|
self.hostname = None # type: typing.Optional[str]
|
|
|
|
|
2018-10-31 15:12:46 +00:00
|
|
|
self._capability_queue = set([]) # type: typing.Set[str]
|
|
|
|
self._capabilities_waiting = set([]) # type: typing.Set[str]
|
|
|
|
self.capabilities = set([]) # type: typing.Set[str]
|
2019-02-05 19:20:02 +00:00
|
|
|
self.requested_capabilities = [] # type: typing.List[str]
|
2018-10-31 15:12:46 +00:00
|
|
|
self.server_capabilities = {} # type: typing.Dict[str, str]
|
|
|
|
self.batches = {} # type: typing.Dict[str, utils.irc.IRCLine]
|
2018-11-09 10:43:05 +00:00
|
|
|
self.cap_started = False
|
2018-08-29 07:38:43 +00:00
|
|
|
|
2016-03-29 11:56:58 +00:00
|
|
|
self.write_buffer = b""
|
2019-02-10 14:09:27 +00:00
|
|
|
self.queued_lines = [] # type: typing.List[IRCLine.Line]
|
|
|
|
self.buffered_lines = [] # type: typing.List[IRCLine.Line]
|
2018-11-19 10:12:52 +00:00
|
|
|
self._write_throttling = False
|
2016-03-29 11:56:58 +00:00
|
|
|
self.read_buffer = b""
|
2018-10-31 15:12:46 +00:00
|
|
|
self.recent_sends = [] # type: typing.List[float]
|
2018-11-05 12:27:30 +00:00
|
|
|
self.cached_fileno = None # type: typing.Optional[int]
|
2018-11-08 13:31:05 +00:00
|
|
|
self.bytes_written = 0
|
|
|
|
self.bytes_read = 0
|
2018-08-29 07:38:43 +00:00
|
|
|
|
2018-10-31 15:12:46 +00:00
|
|
|
self.users = {} # type: typing.Dict[str, IRCUser.User]
|
|
|
|
self.new_users = set([]) #type: typing.Set[IRCUser.User]
|
2018-11-11 14:53:16 +00:00
|
|
|
self.channels = IRCChannels.Channels(self, self.bot, self.events)
|
2018-10-31 15:12:46 +00:00
|
|
|
self.own_modes = {} # type: typing.Dict[str, typing.Optional[str]]
|
2018-11-08 12:34:30 +00:00
|
|
|
|
|
|
|
self.isupport = {} # type: typing.Dict[str, typing.Optional[str]]
|
2019-02-09 18:52:59 +00:00
|
|
|
|
2018-10-03 15:44:00 +00:00
|
|
|
self.prefix_symbols = collections.OrderedDict(
|
|
|
|
(("@", "o"), ("+", "v")))
|
|
|
|
self.prefix_modes = collections.OrderedDict(
|
|
|
|
(("o", "@"), ("v", "+")))
|
2019-02-09 18:52:59 +00:00
|
|
|
|
|
|
|
self.channel_list_modes = ["b"] # type: typing.List[str]
|
|
|
|
self.channel_paramatered_modes = ["k"] # type: typing.List[str]
|
|
|
|
self.channel_setting_modes = ["l"] # type: typing.List[str]
|
|
|
|
self.channel_modes = [
|
|
|
|
"n", "i", "m", "t", "p", "s"
|
|
|
|
] # type: typing.List[str]
|
|
|
|
|
2018-09-17 21:08:54 +00:00
|
|
|
self.channel_types = ["#"]
|
2018-09-11 07:52:12 +00:00
|
|
|
self.case_mapping = "rfc1459"
|
2018-08-29 07:38:43 +00:00
|
|
|
|
2018-11-13 14:59:47 +00:00
|
|
|
self.motd_lines = [] # type: typing.List[str]
|
|
|
|
self.motd_done = False
|
|
|
|
|
2018-08-29 13:33:27 +00:00
|
|
|
self.last_read = time.monotonic()
|
2018-10-31 15:12:46 +00:00
|
|
|
self.last_send = None # type: typing.Optional[float]
|
2018-08-29 07:38:43 +00:00
|
|
|
|
2018-10-31 15:12:46 +00:00
|
|
|
self.attempted_join = {} # type: typing.Dict[str, typing.Optional[str]]
|
2016-04-10 16:30:44 +00:00
|
|
|
self.ping_sent = False
|
2018-07-22 20:46:22 +00:00
|
|
|
|
2018-09-19 12:25:12 +00:00
|
|
|
self.events.on("timer.rejoin").hook(self.try_rejoin)
|
2018-08-28 14:14:41 +00:00
|
|
|
|
2018-11-21 20:13:56 +00:00
|
|
|
def __repr__(self) -> str:
|
2018-08-28 15:05:43 +00:00
|
|
|
return "IRCServer.Server(%s)" % self.__str__()
|
2018-11-21 20:13:56 +00:00
|
|
|
def __str__(self) -> str:
|
2018-09-19 00:13:54 +00:00
|
|
|
if self.alias:
|
|
|
|
return self.alias
|
2018-11-05 18:23:02 +00:00
|
|
|
return "%s:%s%s" % (self.connection_params.hostname,
|
2018-11-05 20:18:06 +00:00
|
|
|
"+" if self.connection_params.tls else "",
|
|
|
|
self.connection_params.port)
|
2018-11-21 20:13:56 +00:00
|
|
|
def fileno(self) -> int:
|
2018-11-05 12:27:30 +00:00
|
|
|
return self.cached_fileno or self.socket.fileno()
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-09-07 15:34:51 +00:00
|
|
|
def tls_wrap(self):
|
2018-10-05 22:01:12 +00:00
|
|
|
client_certificate = self.bot.config.get("tls-certificate", None)
|
|
|
|
client_key = self.bot.config.get("tls-key", None)
|
2019-02-10 12:36:52 +00:00
|
|
|
verify = self.get_setting("ssl-verify", True)
|
2018-09-17 09:55:39 +00:00
|
|
|
|
2019-02-06 18:11:19 +00:00
|
|
|
server_hostname = None
|
|
|
|
if not utils.is_ip(self.connection_params.hostname):
|
|
|
|
server_hostname = self.connection_params.hostname
|
|
|
|
|
2019-02-10 12:36:52 +00:00
|
|
|
self.socket = utils.security.ssl_wrap(self.socket,
|
|
|
|
cert=client_certificate, key=client_key,
|
|
|
|
verify=verify, hostname=server_hostname)
|
2018-09-07 15:34:51 +00:00
|
|
|
|
2016-03-29 11:56:58 +00:00
|
|
|
def connect(self):
|
2018-11-05 18:23:02 +00:00
|
|
|
ipv4 = self.connection_params.ipv4
|
|
|
|
family = socket.AF_INET if ipv4 else socket.AF_INET6
|
2018-11-05 11:53:33 +00:00
|
|
|
self.socket = socket.socket(family, socket.SOCK_STREAM)
|
2018-11-05 18:23:02 +00:00
|
|
|
|
2018-11-05 11:53:33 +00:00
|
|
|
self.socket.settimeout(5.0)
|
2018-11-05 18:23:02 +00:00
|
|
|
|
|
|
|
if self.connection_params.bindhost:
|
2018-11-05 18:30:14 +00:00
|
|
|
self.socket.bind((self.connection_params.bindhost, 0))
|
2018-11-05 18:23:02 +00:00
|
|
|
if self.connection_params.tls:
|
2018-11-05 11:53:33 +00:00
|
|
|
self.tls_wrap()
|
|
|
|
|
2018-11-05 18:23:02 +00:00
|
|
|
self.socket.connect((self.connection_params.hostname,
|
|
|
|
self.connection_params.port))
|
2018-11-05 20:33:30 +00:00
|
|
|
self.cached_fileno = self.socket.fileno()
|
2017-07-12 09:00:27 +00:00
|
|
|
|
2018-11-05 18:30:14 +00:00
|
|
|
if self.connection_params.password:
|
2018-11-05 18:23:02 +00:00
|
|
|
self.send_pass(self.connection_params.password)
|
2018-07-15 22:56:06 +00:00
|
|
|
|
2019-02-07 17:41:10 +00:00
|
|
|
self.send_capibility_ls()
|
|
|
|
|
2018-11-05 20:18:22 +00:00
|
|
|
nickname = self.connection_params.nickname
|
2018-11-05 20:08:55 +00:00
|
|
|
username = self.connection_params.username or nickname
|
|
|
|
realname = self.connection_params.realname or nickname
|
|
|
|
|
|
|
|
self.send_user(username, realname)
|
|
|
|
self.send_nick(nickname)
|
2016-03-29 11:56:58 +00:00
|
|
|
self.connected = True
|
|
|
|
def disconnect(self):
|
|
|
|
self.connected = False
|
|
|
|
try:
|
|
|
|
self.socket.shutdown(socket.SHUT_RDWR)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
self.socket.close()
|
|
|
|
except:
|
|
|
|
pass
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def set_setting(self, setting: str, value: typing.Any):
|
2018-08-05 21:41:38 +00:00
|
|
|
self.bot.database.server_settings.set(self.id, setting,
|
2016-03-29 11:56:58 +00:00
|
|
|
value)
|
2018-11-21 20:13:56 +00:00
|
|
|
def get_setting(self, setting: str, default: typing.Any=None
|
|
|
|
) -> typing.Any:
|
2018-08-05 21:41:38 +00:00
|
|
|
return self.bot.database.server_settings.get(self.id,
|
2016-03-29 11:56:58 +00:00
|
|
|
setting, default)
|
2018-11-21 20:13:56 +00:00
|
|
|
def find_settings(self, pattern: str, default: typing.Any=[]
|
|
|
|
) -> typing.List[typing.Any]:
|
2018-08-05 21:41:38 +00:00
|
|
|
return self.bot.database.server_settings.find(self.id,
|
2016-03-29 11:56:58 +00:00
|
|
|
pattern, default)
|
2018-11-21 20:13:56 +00:00
|
|
|
def find_settings_prefix(self, prefix: str, default: typing.Any=[]
|
|
|
|
) -> typing.List[typing.Any]:
|
2018-08-05 21:41:38 +00:00
|
|
|
return self.bot.database.server_settings.find_prefix(
|
2018-08-03 12:43:45 +00:00
|
|
|
self.id, prefix, default)
|
2018-10-30 14:58:48 +00:00
|
|
|
def del_setting(self, setting: str):
|
2018-08-05 21:41:38 +00:00
|
|
|
self.bot.database.server_settings.delete(self.id, setting)
|
2018-10-14 13:30:19 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def get_user_setting(self, nickname: str, setting: str,
|
2018-11-21 20:13:56 +00:00
|
|
|
default: typing.Any=None) -> typing.Any:
|
2018-10-14 13:30:19 +00:00
|
|
|
user_id = self.get_user_id(nickname)
|
2018-10-18 16:16:23 +00:00
|
|
|
return self.bot.database.user_settings.get(user_id, setting, default)
|
2018-10-30 14:58:48 +00:00
|
|
|
def set_user_setting(self, nickname: str, setting: str, value: typing.Any):
|
2018-10-14 13:30:19 +00:00
|
|
|
user_id = self.get_user_id(nickname)
|
|
|
|
self.bot.database.user_settings.set(user_id, setting, value)
|
|
|
|
|
2018-11-21 20:13:56 +00:00
|
|
|
def get_all_user_settings(self, setting: str, default: typing.Any=[]
|
|
|
|
) -> typing.List[typing.Any]:
|
2018-08-05 21:41:38 +00:00
|
|
|
return self.bot.database.user_settings.find_all_by_setting(
|
|
|
|
self.id, setting, default)
|
2018-10-30 14:58:48 +00:00
|
|
|
def find_all_user_channel_settings(self, setting: str,
|
2018-11-21 20:13:56 +00:00
|
|
|
default: typing.Any=[]) -> typing.List[typing.Any]:
|
2018-08-31 14:34:56 +00:00
|
|
|
return self.bot.database.user_channel_settings.find_all_by_setting(
|
|
|
|
self.id, setting, default)
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def set_own_nickname(self, nickname: str):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.nickname = nickname
|
2019-01-24 17:12:50 +00:00
|
|
|
self.nickname_lower = self.irc_lower(nickname)
|
2018-11-21 20:05:21 +00:00
|
|
|
def is_own_nickname(self, nickname: str) -> bool:
|
2018-12-02 09:56:57 +00:00
|
|
|
if self.nickname == None:
|
|
|
|
return False
|
2019-01-24 17:12:50 +00:00
|
|
|
return self.irc_equals(nickname, typing.cast(str, self.nickname))
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def add_own_mode(self, mode: str, arg: str=None):
|
2018-07-15 12:30:27 +00:00
|
|
|
self.own_modes[mode] = arg
|
2018-10-30 14:58:48 +00:00
|
|
|
def remove_own_mode(self, mode: str):
|
2018-07-15 12:30:27 +00:00
|
|
|
del self.own_modes[mode]
|
2018-10-30 14:58:48 +00:00
|
|
|
def change_own_mode(self, remove: bool, mode: str, arg: str=None):
|
2018-08-30 10:12:48 +00:00
|
|
|
if remove:
|
2018-08-30 10:17:00 +00:00
|
|
|
self.remove_own_mode(mode)
|
2018-08-30 10:12:48 +00:00
|
|
|
else:
|
|
|
|
self.add_own_mode(mode, arg)
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-11-21 20:05:21 +00:00
|
|
|
def has_user(self, nickname: str) -> bool:
|
2019-01-24 17:12:50 +00:00
|
|
|
return self.irc_lower(nickname) in self.users
|
2018-11-21 20:18:12 +00:00
|
|
|
def get_user(self, nickname: str, create: bool=True
|
|
|
|
) -> typing.Optional[IRCUser.User]:
|
2018-10-03 10:32:31 +00:00
|
|
|
if not self.has_user(nickname) and create:
|
2018-08-18 23:19:53 +00:00
|
|
|
user_id = self.get_user_id(nickname)
|
|
|
|
new_user = IRCUser.User(nickname, user_id, self, self.bot)
|
2018-09-19 12:25:12 +00:00
|
|
|
self.events.on("new.user").call(user=new_user, server=self)
|
2018-08-04 11:10:10 +00:00
|
|
|
self.users[new_user.nickname_lower] = new_user
|
2016-03-29 11:56:58 +00:00
|
|
|
self.new_users.add(new_user)
|
2019-01-24 17:12:50 +00:00
|
|
|
return self.users.get(self.irc_lower(nickname),
|
2018-10-30 14:58:48 +00:00
|
|
|
None)
|
2018-11-21 20:05:21 +00:00
|
|
|
def get_user_id(self, nickname: str) -> int:
|
2019-02-06 11:25:20 +00:00
|
|
|
nickname_lower = self.irc_lower(nickname)
|
|
|
|
self.bot.database.users.add(self.id, nickname_lower)
|
|
|
|
return self.bot.database.users.get_id(self.id, nickname_lower)
|
2018-10-30 14:58:48 +00:00
|
|
|
def remove_user(self, user: IRCUser.User):
|
2018-08-04 11:10:10 +00:00
|
|
|
del self.users[user.nickname_lower]
|
2016-03-29 11:56:58 +00:00
|
|
|
for channel in user.channels:
|
|
|
|
channel.remove_user(user)
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-11-21 20:02:36 +00:00
|
|
|
def get_target(self, name: str
|
2018-11-21 20:13:56 +00:00
|
|
|
) -> typing.Optional[
|
|
|
|
typing.Union[IRCChannel.Channel, IRCUser.User]]:
|
2018-11-21 20:02:36 +00:00
|
|
|
if name[0] in self.channel_types:
|
|
|
|
if name in self.channels:
|
|
|
|
return self.channels.get(name)
|
|
|
|
else:
|
|
|
|
return self.get_user(name)
|
|
|
|
return None
|
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def change_user_nickname(self, old_nickname: str, new_nickname: str):
|
2019-01-24 17:12:50 +00:00
|
|
|
user = self.users.pop(self.irc_lower(old_nickname))
|
2018-09-05 11:58:12 +00:00
|
|
|
user._id = self.get_user_id(new_nickname)
|
2019-01-24 17:12:50 +00:00
|
|
|
self.users[self.irc_lower(new_nickname)] = user
|
|
|
|
|
2019-01-24 17:15:02 +00:00
|
|
|
def irc_lower(self, s: str) -> str:
|
2019-01-24 17:12:50 +00:00
|
|
|
return utils.irc.lower(self.case_mapping, s)
|
2019-01-24 17:13:51 +00:00
|
|
|
def irc_equals(self, s1: str, s2: str) -> bool:
|
2019-01-24 17:12:50 +00:00
|
|
|
return utils.irc.equals(self.case_mapping, s1, s2)
|
2018-11-04 14:48:55 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def parse_data(self, line: str):
|
2017-12-26 10:32:36 +00:00
|
|
|
if not line:
|
|
|
|
return
|
2018-12-02 09:41:21 +00:00
|
|
|
|
|
|
|
self.bot.log.debug("%s (raw recv) | %s", [str(self), line])
|
2018-11-14 14:41:28 +00:00
|
|
|
self.events.on("raw.received").call_unsafe(server=self, line=line)
|
2017-07-16 20:18:58 +00:00
|
|
|
self.check_users()
|
2016-03-29 11:56:58 +00:00
|
|
|
def check_users(self):
|
|
|
|
for user in self.new_users:
|
|
|
|
if not len(user.channels):
|
|
|
|
self.remove_user(user)
|
|
|
|
self.new_users.clear()
|
2018-11-21 20:13:56 +00:00
|
|
|
def read(self) -> typing.Optional[typing.List[str]]:
|
2017-11-01 13:56:44 +00:00
|
|
|
data = b""
|
|
|
|
try:
|
2018-10-06 14:37:05 +00:00
|
|
|
data = self.socket.recv(4096)
|
2018-10-26 10:30:16 +00:00
|
|
|
except (ConnectionResetError, socket.timeout, OSError):
|
2017-11-01 13:56:44 +00:00
|
|
|
self.disconnect()
|
2018-10-06 14:37:05 +00:00
|
|
|
return None
|
|
|
|
if not data:
|
|
|
|
self.disconnect()
|
|
|
|
return None
|
2018-11-08 13:31:05 +00:00
|
|
|
self.bytes_read += len(data)
|
2018-10-06 14:37:05 +00:00
|
|
|
data = self.read_buffer+data
|
2016-03-29 11:56:58 +00:00
|
|
|
self.read_buffer = b""
|
2018-10-06 14:37:05 +00:00
|
|
|
|
2016-03-29 11:56:58 +00:00
|
|
|
data_lines = [line.strip(b"\r") for line in data.split(b"\n")]
|
|
|
|
if data_lines[-1]:
|
|
|
|
self.read_buffer = data_lines[-1]
|
2018-10-11 17:07:11 +00:00
|
|
|
self.bot.log.trace("recevied and buffered non-complete line: %s",
|
|
|
|
[data_lines[-1]])
|
2018-10-11 16:56:19 +00:00
|
|
|
|
2016-03-29 11:56:58 +00:00
|
|
|
data_lines.pop(-1)
|
|
|
|
decoded_lines = []
|
2018-10-06 14:37:05 +00:00
|
|
|
|
2016-03-29 11:56:58 +00:00
|
|
|
for line in data_lines:
|
2018-10-17 15:54:33 +00:00
|
|
|
encoding = self.get_setting("encoding", "utf8")
|
2016-03-29 11:56:58 +00:00
|
|
|
try:
|
2018-11-21 20:16:07 +00:00
|
|
|
decoded_line = line.decode(encoding)
|
2016-03-29 11:56:58 +00:00
|
|
|
except:
|
2018-10-17 16:04:56 +00:00
|
|
|
self.bot.log.trace("can't decode line with '%s', falling back",
|
2018-10-17 15:54:33 +00:00
|
|
|
[encoding])
|
2016-03-29 11:56:58 +00:00
|
|
|
try:
|
2018-11-21 20:16:07 +00:00
|
|
|
decoded_line = line.decode(self.get_setting(
|
2018-08-05 10:42:25 +00:00
|
|
|
"fallback-encoding", "latin-1"))
|
2016-03-29 11:56:58 +00:00
|
|
|
except:
|
|
|
|
continue
|
2018-11-21 20:16:07 +00:00
|
|
|
decoded_lines.append(decoded_line)
|
2018-10-06 14:37:05 +00:00
|
|
|
|
2018-08-28 13:36:16 +00:00
|
|
|
self.last_read = time.monotonic()
|
2016-04-10 16:30:44 +00:00
|
|
|
self.ping_sent = False
|
2016-03-29 11:56:58 +00:00
|
|
|
return decoded_lines
|
2018-08-29 13:33:27 +00:00
|
|
|
|
2018-11-21 20:13:56 +00:00
|
|
|
def until_next_ping(self) -> typing.Optional[float]:
|
2018-09-11 17:25:27 +00:00
|
|
|
if self.ping_sent:
|
|
|
|
return None
|
2018-08-29 13:33:27 +00:00
|
|
|
return max(0, (self.last_read+PING_INTERVAL_SECONDS
|
|
|
|
)-time.monotonic())
|
2018-11-21 20:13:56 +00:00
|
|
|
def ping_due(self) -> bool:
|
2018-08-29 13:33:27 +00:00
|
|
|
return self.until_next_ping() == 0
|
|
|
|
|
2018-11-21 20:17:02 +00:00
|
|
|
def until_read_timeout(self) -> float:
|
2018-08-29 13:33:27 +00:00
|
|
|
return max(0, (self.last_read+READ_TIMEOUT_SECONDS
|
|
|
|
)-time.monotonic())
|
2018-11-21 20:13:56 +00:00
|
|
|
def read_timed_out(self) -> bool:
|
2018-08-29 13:33:27 +00:00
|
|
|
return self.until_read_timeout == 0
|
|
|
|
|
2018-12-02 09:41:21 +00:00
|
|
|
def send(self, line: str):
|
2019-01-26 11:05:25 +00:00
|
|
|
results = self.events.on("preprocess.send").call_unsafe(
|
2018-12-02 09:48:06 +00:00
|
|
|
server=self, line=line)
|
2019-01-26 11:05:25 +00:00
|
|
|
for result in results:
|
|
|
|
if result:
|
|
|
|
line = result
|
|
|
|
break
|
2018-09-30 19:12:11 +00:00
|
|
|
|
|
|
|
encoded = line.split("\n")[0].strip("\r").encode("utf8")
|
2019-02-10 14:08:07 +00:00
|
|
|
if len(encoded) > LINE_CUTOFF:
|
|
|
|
encoded = encoded[:LINE_CUTOFF]
|
2019-02-10 14:09:27 +00:00
|
|
|
|
|
|
|
encoded = b"%s\r\n" % encoded
|
|
|
|
line_obj = IRCLine.Line(datetime.datetime.utcnow(), encoded)
|
|
|
|
self.queued_lines.append(line_obj)
|
|
|
|
|
2018-12-02 09:41:21 +00:00
|
|
|
self.bot.log.debug("%s (raw send) | %s", [str(self), line])
|
2018-10-03 15:44:00 +00:00
|
|
|
|
2016-03-29 11:56:58 +00:00
|
|
|
def _send(self):
|
2018-08-29 07:38:43 +00:00
|
|
|
if not len(self.write_buffer):
|
2019-02-10 14:09:27 +00:00
|
|
|
throttle_space = self.throttle_space()
|
|
|
|
to_buffer = self.queued_lines[:throttle_space]
|
|
|
|
self.queued_lines = self.queued_lines[throttle_space:]
|
|
|
|
for line in to_buffer:
|
|
|
|
self.write_buffer += line.data
|
|
|
|
self.buffered_lines.append(line)
|
2018-11-08 13:31:05 +00:00
|
|
|
|
2019-02-10 14:09:27 +00:00
|
|
|
bytes_written_i = self.socket.send(self.write_buffer)
|
|
|
|
bytes_written = self.write_buffer[:bytes_written_i]
|
|
|
|
lines_sent = bytes_written.count(b"\r\n")
|
|
|
|
for i in range(lines_sent):
|
|
|
|
self.buffered_lines.pop(0).sent()
|
|
|
|
|
|
|
|
self.write_buffer = self.write_buffer[bytes_written_i:]
|
|
|
|
|
|
|
|
self.bytes_written += bytes_written_i
|
2018-08-29 07:38:43 +00:00
|
|
|
|
2018-11-27 11:56:03 +00:00
|
|
|
if not self.waiting_send():
|
|
|
|
self.events.on("writebuffer.empty").call(server=self)
|
|
|
|
|
2018-08-29 07:38:43 +00:00
|
|
|
now = time.monotonic()
|
|
|
|
self.recent_sends.append(now)
|
|
|
|
self.last_send = now
|
2018-11-21 20:13:56 +00:00
|
|
|
def waiting_send(self) -> bool:
|
2019-02-10 14:09:27 +00:00
|
|
|
return bool(len(self.write_buffer)) or bool(len(self.queued_lines))
|
|
|
|
|
2018-11-21 20:13:56 +00:00
|
|
|
def throttle_done(self) -> bool:
|
2018-08-28 14:32:50 +00:00
|
|
|
return self.send_throttle_timeout() == 0
|
2018-08-29 07:38:43 +00:00
|
|
|
|
2019-02-10 14:09:27 +00:00
|
|
|
def throttle_prune(self):
|
2018-08-29 07:38:43 +00:00
|
|
|
now = time.monotonic()
|
|
|
|
popped = 0
|
|
|
|
for i, recent_send in enumerate(self.recent_sends[:]):
|
|
|
|
time_since = now-recent_send
|
|
|
|
if time_since >= THROTTLE_SECONDS:
|
|
|
|
self.recent_sends.pop(i-popped)
|
|
|
|
popped += 1
|
|
|
|
|
2019-02-10 14:09:27 +00:00
|
|
|
def throttle_space(self) -> int:
|
|
|
|
if not self._write_throttling:
|
|
|
|
return UNTHROTTLED_MAX_LINES
|
|
|
|
return max(0, THROTTLE_LINES-len(self.recent_sends))
|
|
|
|
|
|
|
|
def send_throttle_timeout(self) -> float:
|
|
|
|
if len(self.write_buffer) or not self._write_throttling:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
self.throttle_prune()
|
|
|
|
if self.throttle_space() > 0:
|
2018-08-28 13:36:16 +00:00
|
|
|
return 0
|
2018-08-29 07:38:43 +00:00
|
|
|
|
|
|
|
time_left = self.recent_sends[0]+THROTTLE_SECONDS
|
|
|
|
time_left = time_left-now
|
|
|
|
return time_left
|
2019-02-10 14:09:27 +00:00
|
|
|
|
2018-11-19 10:12:52 +00:00
|
|
|
def set_write_throttling(self, is_on: bool):
|
|
|
|
self._write_throttling = is_on
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_user(self, username: str, realname: str):
|
2018-09-17 12:49:48 +00:00
|
|
|
self.send("USER %s 0 * :%s" % (username, realname))
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_nick(self, nickname: str):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("NICK %s" % nickname)
|
2017-07-12 09:00:27 +00:00
|
|
|
|
2018-09-03 10:14:27 +00:00
|
|
|
def send_capibility_ls(self):
|
2018-09-07 14:51:41 +00:00
|
|
|
self.send("CAP LS 302")
|
2018-10-30 14:58:48 +00:00
|
|
|
def queue_capability(self, capability: str):
|
2018-09-03 10:14:27 +00:00
|
|
|
self._capability_queue.add(capability)
|
2018-10-30 14:58:48 +00:00
|
|
|
def queue_capabilities(self, capabilities: typing.List[str]):
|
2018-09-03 10:47:11 +00:00
|
|
|
self._capability_queue.update(capabilities)
|
2018-09-03 10:14:27 +00:00
|
|
|
def send_capability_queue(self):
|
|
|
|
if self.has_capability_queue():
|
|
|
|
capabilities = " ".join(self._capability_queue)
|
2019-02-05 19:20:02 +00:00
|
|
|
self.requested_capabilities = list(self._capability_queue)
|
2018-09-03 10:14:27 +00:00
|
|
|
self._capability_queue.clear()
|
|
|
|
self.send_capability_request(capabilities)
|
|
|
|
def has_capability_queue(self):
|
|
|
|
return bool(len(self._capability_queue))
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_capability_request(self, capability: str):
|
2018-09-03 10:14:27 +00:00
|
|
|
self.send("CAP REQ :%s" % capability)
|
2017-07-12 09:00:27 +00:00
|
|
|
def send_capability_end(self):
|
|
|
|
self.send("CAP END")
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_authenticate(self, text: str):
|
2017-07-12 09:00:27 +00:00
|
|
|
self.send("AUTHENTICATE %s" % text)
|
|
|
|
|
2018-11-21 20:13:56 +00:00
|
|
|
def waiting_for_capabilities(self) -> bool:
|
2018-09-03 10:18:20 +00:00
|
|
|
return bool(len(self._capabilities_waiting))
|
2018-10-30 14:58:48 +00:00
|
|
|
def wait_for_capability(self, capability: str):
|
2018-09-03 10:14:27 +00:00
|
|
|
self._capabilities_waiting.add(capability)
|
2018-10-30 14:58:48 +00:00
|
|
|
def capability_done(self, capability: str):
|
2019-02-06 15:44:09 +00:00
|
|
|
self._capabilities_waiting.discard(capability)
|
2018-11-09 10:43:05 +00:00
|
|
|
if self.cap_started and not self._capabilities_waiting:
|
2018-09-03 10:14:27 +00:00
|
|
|
self.send_capability_end()
|
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_pass(self, password: str):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("PASS %s" % password)
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_ping(self, nonce: str="hello"):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("PING :%s" % nonce)
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_pong(self, nonce: str="hello"):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("PONG :%s" % nonce)
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def try_rejoin(self, event: EventManager.Event):
|
2016-04-19 12:25:50 +00:00
|
|
|
if event["server_id"] == self.id and event["channel_name"
|
|
|
|
] in self.attempted_join:
|
|
|
|
self.send_join(event["channel_name"], event["key"])
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_join(self, channel_name: str, key: str=None):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("JOIN %s%s" % (channel_name,
|
|
|
|
"" if key == None else " %s" % key))
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_part(self, channel_name: str, reason: str=None):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("PART %s%s" % (channel_name,
|
2016-05-17 16:58:20 +00:00
|
|
|
"" if reason == None else " %s" % reason))
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_quit(self, reason: str="Leaving"):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("QUIT :%s" % reason)
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-11-21 20:13:56 +00:00
|
|
|
def _tag_str(self, tags: dict) -> str:
|
2018-10-01 16:26:31 +00:00
|
|
|
tag_str = ""
|
|
|
|
for tag, value in tags.items():
|
|
|
|
if tag_str:
|
|
|
|
tag_str += ","
|
|
|
|
tag_str += tag
|
|
|
|
if value:
|
|
|
|
tag_str += "=%s" % value
|
|
|
|
if tag_str:
|
|
|
|
tag_str = "@%s " % tag_str
|
2018-10-02 20:40:34 +00:00
|
|
|
return tag_str
|
2018-10-01 16:26:31 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_message(self, target: str, message: str, prefix: str=None,
|
|
|
|
tags: dict={}):
|
2018-07-14 08:50:12 +00:00
|
|
|
full_message = message if not prefix else prefix+message
|
2018-10-02 20:40:34 +00:00
|
|
|
self.send("%sPRIVMSG %s :%s" % (self._tag_str(tags), target,
|
|
|
|
full_message))
|
2018-09-03 21:17:34 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_notice(self, target: str, message: str, prefix: str=None,
|
|
|
|
tags: dict={}):
|
2018-10-02 20:40:34 +00:00
|
|
|
full_message = message if not prefix else prefix+message
|
|
|
|
self.send("%sNOTICE %s :%s" % (self._tag_str(tags), target,
|
|
|
|
full_message))
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_mode(self, target: str, mode: str=None, args: str=None):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("MODE %s%s%s" % (target, "" if mode == None else " %s" % mode,
|
|
|
|
"" if args == None else " %s" % args))
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_topic(self, channel_name: str, topic: str):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("TOPIC %s :%s" % (channel_name, topic))
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_kick(self, channel_name: str, target: str, reason: str=None):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("KICK %s %s%s" % (channel_name, target,
|
|
|
|
"" if reason == None else " :%s" % reason))
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_names(self, channel_name: str):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("NAMES %s" % channel_name)
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_list(self, search_for: str=None):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send(
|
|
|
|
"LIST%s" % "" if search_for == None else " %s" % search_for)
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_invite(self, target: str, channel_name: str):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("INVITE %s %s" % (target, channel_name))
|
2018-07-15 12:30:27 +00:00
|
|
|
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_whois(self, target: str):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("WHOIS %s" % target)
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_whowas(self, target: str, amount: int=None, server: str=None):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("WHOWAS %s%s%s" % (target,
|
|
|
|
"" if amount == None else " %s" % amount,
|
|
|
|
"" if server == None else " :%s" % server))
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_who(self, filter: str=None):
|
2016-03-29 11:56:58 +00:00
|
|
|
self.send("WHO%s" % ("" if filter == None else " %s" % filter))
|
2018-10-30 14:58:48 +00:00
|
|
|
def send_whox(self, mask: str, filter: str, fields: str, label: str=None):
|
2018-09-13 09:27:50 +00:00
|
|
|
self.send("WHO %s %s%%%s%s" % (mask, filter, fields,
|
2018-09-05 10:58:10 +00:00
|
|
|
","+label if label else ""))
|