mirror of https://github.com/Shizmob/pydle.git
Merge branch 'asyncio'
This commit is contained in:
commit
a7053805cf
|
@ -1,12 +1,15 @@
|
|||
from . import async, connection, protocol, client, features
|
||||
from . import connection, protocol, client, features
|
||||
|
||||
from .async import coroutine, Future
|
||||
from .client import Error, NotInChannel, AlreadyInChannel, BasicClient, ClientPool
|
||||
from .features.ircv3.cap import NEGOTIATING as CAPABILITY_NEGOTIATING, FAILED as CAPABILITY_FAILED, NEGOTIATED as CAPABILITY_NEGOTIATED
|
||||
from .features.ircv3.cap import NEGOTIATING as CAPABILITY_NEGOTIATING, FAILED as CAPABILITY_FAILED, \
|
||||
NEGOTIATED as CAPABILITY_NEGOTIATED
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from asyncio import coroutine, Future
|
||||
|
||||
__name__ = 'pydle'
|
||||
__version__ = '0.8.4'
|
||||
__version_info__ = (0, 8, 4)
|
||||
__version__ = '0.8.5'
|
||||
__version_info__ = (0, 8, 5)
|
||||
__license__ = 'BSD'
|
||||
|
||||
|
||||
|
@ -22,13 +25,16 @@ def featurize(*features):
|
|||
return 0
|
||||
|
||||
sorted_features = sorted(features, key=cmp_to_key(compare_subclass))
|
||||
name = 'FeaturizedClient[{features}]'.format(features=', '.join(feature.__name__ for feature in sorted_features))
|
||||
name = 'FeaturizedClient[{features}]'.format(
|
||||
features=', '.join(feature.__name__ for feature in sorted_features))
|
||||
return type(name, tuple(sorted_features), {})
|
||||
|
||||
|
||||
class Client(featurize(*features.ALL)):
|
||||
""" A fully featured IRC client. """
|
||||
pass
|
||||
|
||||
|
||||
class MinimalClient(featurize(*features.LITE)):
|
||||
""" A cut-down, less-featured IRC client. """
|
||||
pass
|
||||
|
|
215
pydle/async.py
215
pydle/async.py
|
@ -1,215 +0,0 @@
|
|||
## async.py
|
||||
# Light wrapper around whatever async library pydle uses.
|
||||
import functools
|
||||
import datetime
|
||||
import traceback
|
||||
import asyncio
|
||||
|
||||
FUTURE_TIMEOUT = 30
|
||||
|
||||
|
||||
class Future(asyncio.Future):
|
||||
"""
|
||||
A future. An object that represents a result that has yet to be created or returned.
|
||||
"""
|
||||
|
||||
def coroutine(f):
|
||||
return asyncio.coroutine(f)
|
||||
|
||||
def parallel(*futures):
|
||||
return asyncio.gather(*futures)
|
||||
|
||||
|
||||
class EventLoop:
|
||||
""" A light wrapper around what event loop mechanism pydle uses underneath. """
|
||||
|
||||
def __init__(self, loop=None):
|
||||
self.loop = loop or asyncio.get_event_loop()
|
||||
self.future_timeout = FUTURE_TIMEOUT
|
||||
self._future_timeouts = {}
|
||||
self._tasks = []
|
||||
|
||||
def __del__(self):
|
||||
self.loop.close()
|
||||
|
||||
|
||||
@property
|
||||
def running(self):
|
||||
return self.loop.is_running()
|
||||
|
||||
def create_future(self):
|
||||
return Future(loop=self.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def connect(self, dest, tls=None, **kwargs):
|
||||
(host, port) = dest
|
||||
return (yield from asyncio.open_connection(host=host, port=port, ssl=tls, **kwargs))
|
||||
|
||||
def on_future(self, _future, _callback, *_args, **_kwargs):
|
||||
""" Add a callback for when the given future has been resolved. """
|
||||
callback = functools.partial(self._do_on_future, _callback, _args, _kwargs)
|
||||
|
||||
# Create timeout handler and regular handler.
|
||||
self._future_timeouts[_future] = self.schedule_in(self.future_timeout, callback)
|
||||
future.add_done_callback(callback)
|
||||
|
||||
def _do_on_future(self, callback, args, kwargs, future):
|
||||
# This was a time-out.
|
||||
if not future.done():
|
||||
future.set_exception(TimeoutError('Future timed out before yielding a result.'))
|
||||
del self._future_timeouts[future]
|
||||
# This was a time-out that already has been handled.
|
||||
elif isinstance(future.exception(), TimeoutError):
|
||||
return
|
||||
# A regular result. Cancel the timeout.
|
||||
else:
|
||||
self.unschedule(self._future_timeouts.pop(future))
|
||||
|
||||
# Call callback.
|
||||
callback(*args, **kwargs)
|
||||
|
||||
|
||||
def schedule(self, _callback, *_args, **_kwargs):
|
||||
"""
|
||||
Schedule a callback to be ran as soon as possible in this loop.
|
||||
Will return an opaque handle that can be passed to `unschedule` to unschedule the function.
|
||||
"""
|
||||
@coroutine
|
||||
@functools.wraps(_callback)
|
||||
def inner():
|
||||
_callback(*_args, **_kwargs)
|
||||
|
||||
return self.schedule_async(inner())
|
||||
|
||||
def schedule_async(self, _callback):
|
||||
"""
|
||||
Schedule a coroutine to be ran as soon as possible in this loop.
|
||||
Will return an opaque handle that can be passed to `unschedule` to unschedule the function.
|
||||
"""
|
||||
@coroutine
|
||||
@functools.wraps(_callback)
|
||||
def inner():
|
||||
try:
|
||||
return (yield from _callback)
|
||||
except (GeneratorExit, asyncio.CancelledError):
|
||||
raise
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
task = asyncio.ensure_future(inner())
|
||||
self._tasks.append(task)
|
||||
return task
|
||||
|
||||
def schedule_in(self, _when, _callback, *_args, **_kwargs):
|
||||
"""
|
||||
Schedule a callback to be ran as soon as possible after `when` seconds have passed.
|
||||
Will return an opaque handle that can be passed to `unschedule` to unschedule the function.
|
||||
"""
|
||||
if isinstance(_when, datetime.timedelta):
|
||||
_when = _when.total_seconds()
|
||||
|
||||
@coroutine
|
||||
@functools.wraps(_callback)
|
||||
def inner():
|
||||
yield from asyncio.sleep(_when)
|
||||
_callback(*_args, **_kwargs)
|
||||
|
||||
return self.schedule_async(inner())
|
||||
|
||||
def schedule_async_in(self, _when, _callback):
|
||||
"""
|
||||
Schedule a coroutine to be ran as soon as possible after `when` seconds have passed.
|
||||
Will return an opaque handle that can be passed to `unschedule` to unschedule the function.
|
||||
"""
|
||||
if isinstance(_when, datetime.timedelta):
|
||||
_when = _when.total_seconds()
|
||||
|
||||
@coroutine
|
||||
@functools.wraps(_callback)
|
||||
def inner():
|
||||
yield from asyncio.sleep(_when)
|
||||
yield from _callback
|
||||
|
||||
return self.schedule_async(inner())
|
||||
|
||||
def schedule_periodically(self, _interval, _callback, *_args, **_kwargs):
|
||||
"""
|
||||
Schedule a callback to be ran every `interval` seconds.
|
||||
Will return an opaque handle that can be passed to unschedule() to unschedule the interval function.
|
||||
A function will also stop being scheduled if it returns False or raises an Exception.
|
||||
"""
|
||||
if isinstance(_interval, datetime.timedelta):
|
||||
_interval = _interval.total_seconds()
|
||||
|
||||
@coroutine
|
||||
@functools.wraps(_callback)
|
||||
def inner():
|
||||
while True:
|
||||
yield from asyncio.sleep(_when)
|
||||
ret = _callback(*_args, **_kwargs)
|
||||
if ret is False:
|
||||
break
|
||||
|
||||
return self.schedule_async(inner())
|
||||
|
||||
def schedule_async_periodically(self, _interval, _callback, *_args, **_kwargs):
|
||||
"""
|
||||
Schedule a coroutine to be ran every `interval` seconds.
|
||||
Will return an opaque handle that can be passed to unschedule() to unschedule the interval function.
|
||||
A function will also stop being scheduled if it returns False or raises an Exception.
|
||||
"""
|
||||
if isinstance(_when, datetime.timedelta):
|
||||
_when = _when.total_seconds()
|
||||
|
||||
@coroutine
|
||||
@functools.wraps(_callback)
|
||||
def inner():
|
||||
while True:
|
||||
yield from asyncio.sleep(_when)
|
||||
ret = yield from _callback(*_args, **_kwargs)
|
||||
if ret is False:
|
||||
break
|
||||
|
||||
return self.schedule_async(inner())
|
||||
|
||||
def is_scheduled(self, handle):
|
||||
""" Return whether or not the given handle is still scheduled. """
|
||||
return not handle.cancelled()
|
||||
|
||||
def unschedule(self, handle):
|
||||
""" Unschedule a given timeout or periodical callback. """
|
||||
if self.is_scheduled(handle):
|
||||
self.schedule(handle.cancel)
|
||||
|
||||
def _unschedule_all(self):
|
||||
for task in self._tasks:
|
||||
task.cancel()
|
||||
|
||||
|
||||
def run(self):
|
||||
""" Run the event loop. """
|
||||
if not self.running:
|
||||
self.loop.run_forever()
|
||||
|
||||
def run_with(self, func):
|
||||
""" Run loop, call function, stop loop. If function returns a future, run until the future has been resolved. """
|
||||
@coroutine
|
||||
@functools.wraps(func)
|
||||
def inner():
|
||||
yield from func
|
||||
self._unschedule_all()
|
||||
self.loop.run_until_complete(asyncio.ensure_future(inner()))
|
||||
|
||||
def run_until(self, future):
|
||||
""" Run until future is resolved. """
|
||||
@coroutine
|
||||
def inner():
|
||||
yield from future
|
||||
self._unschedule_all()
|
||||
self.loop.run_until_complete(asyncio.ensure_future(inner()))
|
||||
|
||||
def stop(self):
|
||||
""" Stop the event loop. """
|
||||
if self.running:
|
||||
self._unschedule_all()
|
||||
self.loop.stop()
|
172
pydle/client.py
172
pydle/client.py
|
@ -1,8 +1,9 @@
|
|||
## client.py
|
||||
# Basic IRC client implementation.
|
||||
import logging
|
||||
from asyncio import ensure_future, new_event_loop, BaseEventLoop, gather, TimerHandle, get_event_loop
|
||||
from typing import Set
|
||||
|
||||
from . import async
|
||||
from . import connection
|
||||
from . import protocol
|
||||
|
||||
|
@ -14,11 +15,13 @@ class Error(Exception):
|
|||
""" Base class for all pydle errors. """
|
||||
pass
|
||||
|
||||
|
||||
class NotInChannel(Error):
|
||||
def __init__(self, channel):
|
||||
super().__init__('Not in channel: {}'.format(channel))
|
||||
self.channel = channel
|
||||
|
||||
|
||||
class AlreadyInChannel(Error):
|
||||
def __init__(self, channel):
|
||||
super().__init__('Already in channel: {}'.format(channel))
|
||||
|
@ -36,7 +39,8 @@ class BasicClient:
|
|||
RECONNECT_DELAYED = True
|
||||
RECONNECT_DELAYS = [0, 5, 10, 30, 120, 600]
|
||||
|
||||
def __init__(self, nickname, fallback_nicknames=[], username=None, realname=None, eventloop=None, **kwargs):
|
||||
def __init__(self, nickname, fallback_nicknames=[], username=None, realname=None,
|
||||
eventloop=None, **kwargs):
|
||||
""" Create a client. """
|
||||
self._nicknames = [nickname] + fallback_nicknames
|
||||
self.username = username or nickname.lower()
|
||||
|
@ -44,7 +48,8 @@ class BasicClient:
|
|||
if eventloop:
|
||||
self.eventloop = eventloop
|
||||
else:
|
||||
self.eventloop = async.EventLoop()
|
||||
self.eventloop: BaseEventLoop = get_event_loop()
|
||||
|
||||
self.own_eventloop = not eventloop
|
||||
self._reset_connection_attributes()
|
||||
self._reset_attributes()
|
||||
|
@ -78,19 +83,17 @@ class BasicClient:
|
|||
self._autojoin_channels = []
|
||||
self._reconnect_attempts = 0
|
||||
|
||||
|
||||
## Connection.
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
""" Connect and run bot in event loop. """
|
||||
self.eventloop.schedule_async(self.connect(*args, **kwargs))
|
||||
self.eventloop.run_until_complete(self.connect(*args, **kwargs))
|
||||
try:
|
||||
self.eventloop.run()
|
||||
self.eventloop.run_forever()
|
||||
finally:
|
||||
self.eventloop.stop()
|
||||
|
||||
@async.coroutine
|
||||
def connect(self, hostname=None, port=None, reconnect=False, **kwargs):
|
||||
async def connect(self, hostname=None, port=None, reconnect=False, **kwargs):
|
||||
""" Connect to IRC server. """
|
||||
if (not hostname or not port) and not reconnect:
|
||||
raise ValueError('Have to specify hostname and port if not reconnecting.')
|
||||
|
@ -102,33 +105,28 @@ class BasicClient:
|
|||
# Reset attributes and connect.
|
||||
if not reconnect:
|
||||
self._reset_connection_attributes()
|
||||
try:
|
||||
yield from self._connect(hostname=hostname, port=port, reconnect=reconnect, **kwargs)
|
||||
except OSError:
|
||||
yield from self.on_disconnect(expected=False)
|
||||
await self._connect(hostname=hostname, port=port, reconnect=reconnect, **kwargs)
|
||||
|
||||
# Set logger name.
|
||||
if self.server_tag:
|
||||
self.logger = logging.getLogger(self.__class__.__name__ + ':' + self.server_tag)
|
||||
|
||||
self.eventloop.schedule_async(self.handle_forever())
|
||||
ensure_future(self.handle_forever(), loop=self.eventloop)
|
||||
|
||||
def disconnect(self, expected=True):
|
||||
""" Disconnect from server. """
|
||||
if self.connected:
|
||||
# Unschedule ping checker.
|
||||
if self._ping_checker_handle:
|
||||
self.eventloop.unschedule(self._ping_checker_handle)
|
||||
self._ping_checker_handle.cancel()
|
||||
# Schedule disconnect.
|
||||
self.eventloop.schedule_async(self._disconnect(expected))
|
||||
ensure_future(self._disconnect(expected), loop=self.eventloop)
|
||||
|
||||
@async.coroutine
|
||||
def _disconnect(self, expected):
|
||||
async def _disconnect(self, expected):
|
||||
# Shutdown connection.
|
||||
yield from self.connection.disconnect()
|
||||
await self.connection.disconnect()
|
||||
|
||||
# Callback.
|
||||
yield from self.on_disconnect(expected)
|
||||
await self.on_disconnect(expected)
|
||||
|
||||
# Shut down event loop.
|
||||
if expected and self.own_eventloop:
|
||||
|
@ -137,17 +135,18 @@ class BasicClient:
|
|||
# Reset any attributes.
|
||||
self._reset_attributes()
|
||||
|
||||
@async.coroutine
|
||||
def _connect(self, hostname, port, reconnect=False, channels=[], encoding=protocol.DEFAULT_ENCODING, source_address=None):
|
||||
async def _connect(self, hostname, port, reconnect=False, channels=[],
|
||||
encoding=protocol.DEFAULT_ENCODING, source_address=None):
|
||||
""" Connect to IRC host. """
|
||||
# Create connection if we can't reuse it.
|
||||
if not reconnect or not self.connection:
|
||||
self._autojoin_channels = channels
|
||||
self.connection = connection.Connection(hostname, port, source_address=source_address, eventloop=self.eventloop)
|
||||
self.connection = connection.Connection(hostname, port, source_address=source_address,
|
||||
eventloop=self.eventloop)
|
||||
self.encoding = encoding
|
||||
|
||||
# Connect.
|
||||
yield from self.connection.connect()
|
||||
await self.connection.connect()
|
||||
|
||||
def _reconnect_delay(self):
|
||||
""" Calculate reconnection delay. """
|
||||
|
@ -159,12 +158,12 @@ class BasicClient:
|
|||
else:
|
||||
return 0
|
||||
|
||||
@async.coroutine
|
||||
def _perform_ping_timeout(self):
|
||||
async def _perform_ping_timeout(self):
|
||||
""" Handle timeout gracefully. """
|
||||
error = TimeoutError('Ping timeout: no data received from server in {timeout} seconds.'.format(timeout=self.PING_TIMEOUT))
|
||||
yield from self.on_data_error(error)
|
||||
|
||||
error = TimeoutError(
|
||||
'Ping timeout: no data received from server in {timeout} seconds.'.format(
|
||||
timeout=self.PING_TIMEOUT))
|
||||
await self.on_data_error(error)
|
||||
|
||||
## Internal database management.
|
||||
|
||||
|
@ -179,7 +178,6 @@ class BasicClient:
|
|||
self._destroy_user(user, channel)
|
||||
del self.channels[channel]
|
||||
|
||||
|
||||
def _create_user(self, nickname):
|
||||
# Servers are NOT users.
|
||||
if not nickname or '.' in nickname:
|
||||
|
@ -198,7 +196,6 @@ class BasicClient:
|
|||
self._create_user(nick)
|
||||
if nick not in self.users:
|
||||
return
|
||||
|
||||
self.users[nick].update(metadata)
|
||||
|
||||
def _rename_user(self, user, new):
|
||||
|
@ -219,7 +216,7 @@ class BasicClient:
|
|||
|
||||
def _destroy_user(self, nickname, channel=None):
|
||||
if channel:
|
||||
channels = [ self.channels[channel] ]
|
||||
channels = [self.channels[channel]]
|
||||
else:
|
||||
channels = self.channels.values()
|
||||
|
||||
|
@ -237,13 +234,13 @@ class BasicClient:
|
|||
raise NotImplementedError()
|
||||
|
||||
def _format_user_mask(self, nickname):
|
||||
user = self.users.get(nickname, { "nickname": nickname, "username": "*", "hostname": "*" })
|
||||
return self._format_host_mask(user['nickname'], user['username'] or '*', user['hostname'] or '*')
|
||||
user = self.users.get(nickname, {"nickname": nickname, "username": "*", "hostname": "*"})
|
||||
return self._format_host_mask(user['nickname'], user['username'] or '*',
|
||||
user['hostname'] or '*')
|
||||
|
||||
def _format_host_mask(self, nick, user, host):
|
||||
return '{n}!{u}@{h}'.format(n=nick, u=user, h=host)
|
||||
|
||||
|
||||
## IRC helpers.
|
||||
|
||||
def is_channel(self, chan):
|
||||
|
@ -262,7 +259,6 @@ class BasicClient:
|
|||
""" Check if given channel names are equal. """
|
||||
return left == right
|
||||
|
||||
|
||||
## IRC attributes.
|
||||
|
||||
@property
|
||||
|
@ -297,49 +293,44 @@ class BasicClient:
|
|||
else:
|
||||
return None
|
||||
|
||||
|
||||
## IRC API.
|
||||
|
||||
@async.coroutine
|
||||
def raw(self, message):
|
||||
async def raw(self, message):
|
||||
""" Send raw command. """
|
||||
yield from self._send(message)
|
||||
await self._send(message)
|
||||
|
||||
@async.coroutine
|
||||
def rawmsg(self, command, *args, **kwargs):
|
||||
async def rawmsg(self, command, *args, **kwargs):
|
||||
""" Send raw message. """
|
||||
message = str(self._create_message(command, *args, **kwargs))
|
||||
yield from self._send(message)
|
||||
|
||||
await self._send(message)
|
||||
|
||||
## Overloadable callbacks.
|
||||
|
||||
@async.coroutine
|
||||
def on_connect(self):
|
||||
async def on_connect(self):
|
||||
""" Callback called when the client has connected successfully. """
|
||||
# Reset reconnect attempts.
|
||||
self._reconnect_attempts = 0
|
||||
|
||||
@async.coroutine
|
||||
def on_disconnect(self, expected):
|
||||
async def on_disconnect(self, expected):
|
||||
if not expected:
|
||||
# Unexpected disconnect. Reconnect?
|
||||
if self.RECONNECT_ON_ERROR and (self.RECONNECT_MAX_ATTEMPTS is None or self._reconnect_attempts < self.RECONNECT_MAX_ATTEMPTS):
|
||||
if self.RECONNECT_ON_ERROR and (
|
||||
self.RECONNECT_MAX_ATTEMPTS is None or self._reconnect_attempts < self.RECONNECT_MAX_ATTEMPTS):
|
||||
# Calculate reconnect delay.
|
||||
delay = self._reconnect_delay()
|
||||
self._reconnect_attempts += 1
|
||||
|
||||
if delay > 0:
|
||||
self.logger.error('Unexpected disconnect. Attempting to reconnect within %s seconds.', delay)
|
||||
self.logger.error(
|
||||
'Unexpected disconnect. Attempting to reconnect within %s seconds.', delay)
|
||||
else:
|
||||
self.logger.error('Unexpected disconnect. Attempting to reconnect.')
|
||||
|
||||
# Wait and reconnect.
|
||||
self.eventloop.schedule_async_in(delay, self.connect(reconnect=True))
|
||||
self.eventloop.call_soon(delay, self.connect(reconnect=True))
|
||||
else:
|
||||
self.logger.error('Unexpected disconnect. Giving up.')
|
||||
|
||||
|
||||
## Message dispatch.
|
||||
|
||||
def _has_message(self):
|
||||
|
@ -352,56 +343,54 @@ class BasicClient:
|
|||
def _parse_message(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@async.coroutine
|
||||
def _send(self, input):
|
||||
async def _send(self, input):
|
||||
if not isinstance(input, (bytes, str)):
|
||||
input = str(input)
|
||||
if isinstance(input, str):
|
||||
input = input.encode(self.encoding)
|
||||
|
||||
self.logger.debug('>> %s', input.decode(self.encoding))
|
||||
yield from self.connection.send(input)
|
||||
await self.connection.send(input)
|
||||
|
||||
@async.coroutine
|
||||
def handle_forever(self):
|
||||
async def handle_forever(self):
|
||||
""" Handle data forever. """
|
||||
while self.connected:
|
||||
data = yield from self.connection.recv()
|
||||
data = await self.connection.recv()
|
||||
if not data:
|
||||
if self.connected:
|
||||
self.disconnect(expected=False)
|
||||
break
|
||||
yield from self.on_data(data)
|
||||
|
||||
await self.on_data(data)
|
||||
|
||||
## Raw message handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_data(self, data):
|
||||
async def on_data(self, data):
|
||||
""" Handle received data. """
|
||||
self._receive_buffer += data
|
||||
|
||||
# Schedule new timeout event.
|
||||
if self._ping_checker_handle:
|
||||
self.eventloop.unschedule(self._ping_checker_handle)
|
||||
self._ping_checker_handle = self.eventloop.schedule_async_in(self.PING_TIMEOUT, self._perform_ping_timeout())
|
||||
self._ping_checker_handle.cancel()
|
||||
|
||||
self._ping_checker_handle: TimerHandle = self.eventloop.call_later(self.PING_TIMEOUT,
|
||||
self._perform_ping_timeout)
|
||||
|
||||
while self._has_message():
|
||||
message = self._parse_message()
|
||||
self.eventloop.schedule_async(self.on_raw(message))
|
||||
ensure_future(self.on_raw(message), loop=self.eventloop)
|
||||
|
||||
@async.coroutine
|
||||
def on_data_error(self, exception):
|
||||
async def on_data_error(self, exception):
|
||||
""" Handle error. """
|
||||
self.logger.error('Encountered error on socket.', exc_info=(type(exception), exception, None))
|
||||
self.logger.error('Encountered error on socket.',
|
||||
exc_info=(type(exception), exception, None))
|
||||
self.disconnect(expected=False)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw(self, message):
|
||||
async def on_raw(self, message):
|
||||
""" Handle a single message. """
|
||||
self.logger.debug('<< %s', message._raw)
|
||||
if not message._valid:
|
||||
self.logger.warning('Encountered strictly invalid IRC message from server: %s', message._raw)
|
||||
self.logger.warning('Encountered strictly invalid IRC message from server: %s',
|
||||
message._raw)
|
||||
|
||||
if isinstance(message.command, int):
|
||||
cmd = str(message.command).zfill(3)
|
||||
|
@ -418,17 +407,16 @@ class BasicClient:
|
|||
handler = getattr(self, method)
|
||||
self._handler_top_level = False
|
||||
|
||||
yield from handler(message)
|
||||
await handler(message)
|
||||
except:
|
||||
self.logger.exception('Failed to execute %s handler.', method)
|
||||
|
||||
@async.coroutine
|
||||
def on_unknown(self, message):
|
||||
async def on_unknown(self, message):
|
||||
""" Unknown command. """
|
||||
self.logger.warning('Unknown command: [%s] %s %s', message.source, message.command, message.params)
|
||||
self.logger.warning('Unknown command: [%s] %s %s', message.source, message.command,
|
||||
message.params)
|
||||
|
||||
@async.coroutine
|
||||
def _ignored(self, message):
|
||||
async def _ignored(self, message):
|
||||
""" Ignore message. """
|
||||
pass
|
||||
|
||||
|
@ -453,14 +441,17 @@ class ClientPool:
|
|||
""" A pool of clients that are ran and handled in parallel. """
|
||||
|
||||
def __init__(self, clients=None, eventloop=None):
|
||||
self.eventloop = eventloop or async.EventLoop()
|
||||
self.clients = set(clients or [])
|
||||
self.eventloop = eventloop if eventloop else new_event_loop()
|
||||
self.clients: Set[BasicClient] = set(clients or [])
|
||||
self.connect_args = {}
|
||||
|
||||
def connect(self, client, *args, **kwargs):
|
||||
def connect(self, client: BasicClient, *args, **kwargs):
|
||||
""" Add client to pool. """
|
||||
self.clients.add(client)
|
||||
self.connect_args[client] = (args, kwargs)
|
||||
# hack the clients event loop to use the pools own event loop
|
||||
client.eventloop = self.eventloop
|
||||
# necessary to run multiple clients in the same thread via the pool
|
||||
|
||||
def disconnect(self, client):
|
||||
""" Remove client from pool. """
|
||||
|
@ -471,16 +462,23 @@ class ClientPool:
|
|||
def __contains__(self, item):
|
||||
return item in self.clients
|
||||
|
||||
|
||||
## High-level.
|
||||
|
||||
def handle_forever(self):
|
||||
""" Main loop of the pool: handle clients forever, until the event loop is stopped. """
|
||||
for c in self.clients:
|
||||
args, kwargs = self.connect_args[c]
|
||||
self.eventloop.schedule_async(c.connect(*args, **kwargs))
|
||||
# container for all the client connection coros
|
||||
connection_list = []
|
||||
for client in self.clients:
|
||||
args, kwargs = self.connect_args[client]
|
||||
connection_list.append(client.connect(*args, **kwargs))
|
||||
# single future for executing the connections
|
||||
connections = gather(*connection_list, loop=self.eventloop)
|
||||
|
||||
self.eventloop.run()
|
||||
# run the connections
|
||||
self.eventloop.run_until_complete(connections)
|
||||
|
||||
for c in self.clients:
|
||||
c.disconnect()
|
||||
# run the clients
|
||||
self.eventloop.run_forever()
|
||||
|
||||
for client in self.clients:
|
||||
client.disconnect()
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import sys
|
||||
import asyncio
|
||||
import os.path as path
|
||||
|
||||
import ssl
|
||||
|
||||
from . import async
|
||||
import sys
|
||||
|
||||
__all__ = ['Connection']
|
||||
|
||||
|
@ -21,7 +19,9 @@ class Connection:
|
|||
""" A TCP connection over the IRC protocol. """
|
||||
CONNECT_TIMEOUT = 10
|
||||
|
||||
def __init__(self, hostname, port, tls=False, tls_verify=True, tls_certificate_file=None, tls_certificate_keyfile=None, tls_certificate_password=None, ping_timeout=240, source_address=None, eventloop=None):
|
||||
def __init__(self, hostname, port, tls=False, tls_verify=True, tls_certificate_file=None,
|
||||
tls_certificate_keyfile=None, tls_certificate_password=None, ping_timeout=240,
|
||||
source_address=None, eventloop=None):
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
self.source_address = source_address
|
||||
|
@ -36,16 +36,22 @@ class Connection:
|
|||
|
||||
self.reader = None
|
||||
self.writer = None
|
||||
self.eventloop = eventloop or async.EventLoop()
|
||||
self.eventloop = eventloop or asyncio.new_event_loop()
|
||||
|
||||
@async.coroutine
|
||||
def connect(self):
|
||||
async def connect(self):
|
||||
""" Connect to target. """
|
||||
self.tls_context = None
|
||||
|
||||
if self.tls:
|
||||
self.tls_context = self.create_tls_context()
|
||||
(self.reader, self.writer) = yield from self.eventloop.connect((self.hostname, self.port), local_addr=self.source_address, tls=self.tls_context)
|
||||
|
||||
(self.reader, self.writer) = await asyncio.open_connection(
|
||||
host=self.hostname,
|
||||
port=self.port,
|
||||
local_addr=self.source_address,
|
||||
ssl=self.tls_context,
|
||||
loop=self.eventloop
|
||||
)
|
||||
|
||||
def create_tls_context(self):
|
||||
""" Transform our regular socket into a TLS socket. """
|
||||
|
@ -54,7 +60,8 @@ class Connection:
|
|||
|
||||
# Load client/server certificate.
|
||||
if self.tls_certificate_file:
|
||||
tls_context.load_cert_chain(self.tls_certificate_file, self.tls_certificate_keyfile, password=self.tls_certificate_password)
|
||||
tls_context.load_cert_chain(self.tls_certificate_file, self.tls_certificate_keyfile,
|
||||
password=self.tls_certificate_password)
|
||||
|
||||
# Set some relevant options:
|
||||
# - No server should use SSLv2 or SSLv3 any more, they are outdated and full of security holes. (RFC6176, RFC7568)
|
||||
|
@ -97,8 +104,7 @@ class Connection:
|
|||
|
||||
return None
|
||||
|
||||
@async.coroutine
|
||||
def disconnect(self):
|
||||
async def disconnect(self):
|
||||
""" Disconnect from target. """
|
||||
if not self.connected:
|
||||
return
|
||||
|
@ -112,18 +118,14 @@ class Connection:
|
|||
""" Whether this connection is... connected to something. """
|
||||
return self.reader is not None and self.writer is not None
|
||||
|
||||
|
||||
def stop(self):
|
||||
""" Stop event loop. """
|
||||
self.eventloop.schedule(lambda: self.eventloop.stop())
|
||||
|
||||
|
||||
@async.coroutine
|
||||
def send(self, data):
|
||||
async def send(self, data):
|
||||
""" Add data to send queue. """
|
||||
self.writer.write(data)
|
||||
yield from self.writer.drain()
|
||||
await self.writer.drain()
|
||||
|
||||
@async.coroutine
|
||||
def recv(self):
|
||||
return (yield from self.reader.readline())
|
||||
async def recv(self):
|
||||
return await self.reader.readline()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
## account.py
|
||||
# Account system support.
|
||||
from pydle import async
|
||||
from pydle.features import rfc1459
|
||||
import asyncio
|
||||
|
||||
class AccountSupport(rfc1459.RFC1459Support):
|
||||
|
||||
|
@ -18,24 +18,20 @@ class AccountSupport(rfc1459.RFC1459Support):
|
|||
def _rename_user(self, user, new):
|
||||
super()._rename_user(user, new)
|
||||
# Unset account info to be certain until we get a new response.
|
||||
self._sync_user(new, { 'account': None, 'identified': False })
|
||||
self._sync_user(new, {'account': None, 'identified': False})
|
||||
self.whois(new)
|
||||
|
||||
|
||||
## IRC API.
|
||||
|
||||
@async.coroutine
|
||||
@asyncio.coroutine
|
||||
def whois(self, nickname):
|
||||
info = yield from super().whois(nickname)
|
||||
info.setdefault('account', None)
|
||||
info.setdefault('identified', False)
|
||||
return info
|
||||
|
||||
|
||||
## Message handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_307(self, message):
|
||||
async def on_raw_307(self, message):
|
||||
""" WHOIS: User has identified for this nickname. (Anope) """
|
||||
target, nickname = message.params[:2]
|
||||
info = {
|
||||
|
@ -47,8 +43,7 @@ class AccountSupport(rfc1459.RFC1459Support):
|
|||
if nickname in self._pending['whois']:
|
||||
self._whois_info[nickname].update(info)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_330(self, message):
|
||||
async def on_raw_330(self, message):
|
||||
""" WHOIS account name (Atheme). """
|
||||
target, nickname, account = message.params[:3]
|
||||
info = {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
## ctcp.py
|
||||
# Client-to-Client-Protocol (CTCP) support.
|
||||
from pydle import async
|
||||
import pydle.protocol
|
||||
from pydle.features import rfc1459
|
||||
|
||||
|
@ -16,8 +15,7 @@ class CTCPSupport(rfc1459.RFC1459Support):
|
|||
|
||||
## Callbacks.
|
||||
|
||||
@async.coroutine
|
||||
def on_ctcp(self, by, target, what, contents):
|
||||
async def on_ctcp(self, by, target, what, contents):
|
||||
"""
|
||||
Callback called when the user received a CTCP message.
|
||||
Client subclasses can override on_ctcp_<type> to be called when receiving a message of that specific CTCP type,
|
||||
|
@ -25,8 +23,7 @@ class CTCPSupport(rfc1459.RFC1459Support):
|
|||
"""
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_ctcp_reply(self, by, target, what, response):
|
||||
async def on_ctcp_reply(self, by, target, what, response):
|
||||
"""
|
||||
Callback called when the user received a CTCP response.
|
||||
Client subclasses can override on_ctcp_<type>_reply to be called when receiving a reply of that specific CTCP type,
|
||||
|
@ -34,8 +31,7 @@ class CTCPSupport(rfc1459.RFC1459Support):
|
|||
"""
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_ctcp_version(self, by, target, contents):
|
||||
async def on_ctcp_version(self, by, target, contents):
|
||||
""" Built-in CTCP version as some networks seem to require it. """
|
||||
import pydle
|
||||
|
||||
|
@ -45,27 +41,24 @@ class CTCPSupport(rfc1459.RFC1459Support):
|
|||
|
||||
## IRC API.
|
||||
|
||||
@async.coroutine
|
||||
def ctcp(self, target, query, contents=None):
|
||||
async def ctcp(self, target, query, contents=None):
|
||||
""" Send a CTCP request to a target. """
|
||||
if self.is_channel(target) and not self.in_channel(target):
|
||||
raise client.NotInChannel(target)
|
||||
|
||||
yield from self.message(target, construct_ctcp(query, contents))
|
||||
await self.message(target, construct_ctcp(query, contents))
|
||||
|
||||
@async.coroutine
|
||||
def ctcp_reply(self, target, query, response):
|
||||
async def ctcp_reply(self, target, query, response):
|
||||
""" Send a CTCP reply to a target. """
|
||||
if self.is_channel(target) and not self.in_channel(target):
|
||||
raise client.NotInChannel(target)
|
||||
|
||||
yield from self.notice(target, construct_ctcp(query, response))
|
||||
await self.notice(target, construct_ctcp(query, response))
|
||||
|
||||
|
||||
## Handler overrides.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_privmsg(self, message):
|
||||
async def on_raw_privmsg(self, message):
|
||||
""" Modify PRIVMSG to redirect CTCP messages. """
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
target, msg = message.params
|
||||
|
@ -77,14 +70,14 @@ class CTCPSupport(rfc1459.RFC1459Support):
|
|||
# Find dedicated handler if it exists.
|
||||
attr = 'on_ctcp_' + pydle.protocol.identifierify(type)
|
||||
if hasattr(self, attr):
|
||||
yield from getattr(self, attr)(nick, target, contents)
|
||||
await getattr(self, attr)(nick, target, contents)
|
||||
# Invoke global handler.
|
||||
yield from self.on_ctcp(nick, target, type, contents)
|
||||
await self.on_ctcp(nick, target, type, contents)
|
||||
else:
|
||||
yield from super().on_raw_privmsg(message)
|
||||
await super().on_raw_privmsg(message)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_notice(self, message):
|
||||
|
||||
async def on_raw_notice(self, message):
|
||||
""" Modify NOTICE to redirect CTCP messages. """
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
target, msg = message.params
|
||||
|
@ -96,11 +89,11 @@ class CTCPSupport(rfc1459.RFC1459Support):
|
|||
# Find dedicated handler if it exists.
|
||||
attr = 'on_ctcp_' + pydle.protocol.identifierify(type) + '_reply'
|
||||
if hasattr(self, attr):
|
||||
yield from getattr(self, attr)(user, target, response)
|
||||
await getattr(self, attr)(user, target, response)
|
||||
# Invoke global handler.
|
||||
yield from self.on_ctcp_reply(user, target, type, response)
|
||||
await self.on_ctcp_reply(user, target, type, response)
|
||||
else:
|
||||
yield from super().on_raw_notice(message)
|
||||
await super().on_raw_notice(message)
|
||||
|
||||
|
||||
## Helpers.
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
## cap.py
|
||||
# Server <-> client optional extension indication support.
|
||||
# See also: http://ircv3.atheme.org/specification/capability-negotiation-3.1
|
||||
from pydle import async
|
||||
import pydle.protocol
|
||||
from pydle.features import rfc1459
|
||||
|
||||
|
@ -29,17 +28,16 @@ class CapabilityNegotiationSupport(rfc1459.RFC1459Support):
|
|||
self._capabilities_requested = set()
|
||||
self._capabilities_negotiating = set()
|
||||
|
||||
@async.coroutine
|
||||
def _register(self):
|
||||
async def _register(self):
|
||||
""" Hijack registration to send a CAP LS first. """
|
||||
if self.registered:
|
||||
return
|
||||
|
||||
# Ask server to list capabilities.
|
||||
yield from self.rawmsg('CAP', 'LS', '302')
|
||||
await self.rawmsg('CAP', 'LS', '302')
|
||||
|
||||
# Register as usual.
|
||||
yield from super()._register()
|
||||
await super()._register()
|
||||
|
||||
def _capability_normalize(self, cap):
|
||||
cap = cap.lstrip(PREFIXES).lower()
|
||||
|
@ -53,19 +51,17 @@ class CapabilityNegotiationSupport(rfc1459.RFC1459Support):
|
|||
|
||||
## API.
|
||||
|
||||
@async.coroutine
|
||||
def _capability_negotiated(self, capab):
|
||||
async def _capability_negotiated(self, capab):
|
||||
""" Mark capability as negotiated, and end negotiation if we're done. """
|
||||
self._capabilities_negotiating.discard(capab)
|
||||
|
||||
if not self._capabilities_requested and not self._capabilities_negotiating:
|
||||
yield from self.rawmsg('CAP', 'END')
|
||||
await self.rawmsg('CAP', 'END')
|
||||
|
||||
|
||||
## Message handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_cap(self, message):
|
||||
async def on_raw_cap(self, message):
|
||||
""" Handle CAP message. """
|
||||
target, subcommand = message.params[:2]
|
||||
params = message.params[2:]
|
||||
|
@ -73,12 +69,11 @@ class CapabilityNegotiationSupport(rfc1459.RFC1459Support):
|
|||
# Call handler.
|
||||
attr = 'on_raw_cap_' + pydle.protocol.identifierify(subcommand)
|
||||
if hasattr(self, attr):
|
||||
yield from getattr(self, attr)(params)
|
||||
await getattr(self, attr)(params)
|
||||
else:
|
||||
self.logger.warning('Unknown CAP subcommand sent from server: %s', subcommand)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_cap_ls(self, params):
|
||||
async def on_raw_cap_ls(self, params):
|
||||
""" Update capability mapping. Request capabilities. """
|
||||
to_request = set()
|
||||
|
||||
|
@ -91,7 +86,7 @@ class CapabilityNegotiationSupport(rfc1459.RFC1459Support):
|
|||
|
||||
# Check if we support the capability.
|
||||
attr = 'on_capability_' + pydle.protocol.identifierify(capab) + '_available'
|
||||
supported = (yield from getattr(self, attr)(value)) if hasattr(self, attr) else False
|
||||
supported = (await getattr(self, attr)(value)) if hasattr(self, attr) else False
|
||||
|
||||
if supported:
|
||||
if isinstance(supported, str):
|
||||
|
@ -104,13 +99,12 @@ class CapabilityNegotiationSupport(rfc1459.RFC1459Support):
|
|||
if to_request:
|
||||
# Request some capabilities.
|
||||
self._capabilities_requested.update(x.split(CAPABILITY_VALUE_DIVIDER, 1)[0] for x in to_request)
|
||||
yield from self.rawmsg('CAP', 'REQ', ' '.join(to_request))
|
||||
await self.rawmsg('CAP', 'REQ', ' '.join(to_request))
|
||||
else:
|
||||
# No capabilities requested, end negotiation.
|
||||
yield from self.rawmsg('CAP', 'END')
|
||||
await self.rawmsg('CAP', 'END')
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_cap_list(self, params):
|
||||
async def on_raw_cap_list(self, params):
|
||||
""" Update active capabilities. """
|
||||
self._capabilities = { capab: False for capab in self._capabilities }
|
||||
|
||||
|
@ -118,8 +112,7 @@ class CapabilityNegotiationSupport(rfc1459.RFC1459Support):
|
|||
capab, value = self._capability_normalize(capab)
|
||||
self._capabilities[capab] = value if value else True
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_cap_ack(self, params):
|
||||
async def on_raw_cap_ack(self, params):
|
||||
""" Update active capabilities: requested capability accepted. """
|
||||
for capab in params[0].split():
|
||||
cp, value = self._capability_normalize(capab)
|
||||
|
@ -139,11 +132,11 @@ class CapabilityNegotiationSupport(rfc1459.RFC1459Support):
|
|||
|
||||
# Indicate we're gonna use this capability if needed.
|
||||
if capab.startswith(ACKNOWLEDGEMENT_REQUIRED_PREFIX):
|
||||
yield from self.rawmsg('CAP', 'ACK', cp)
|
||||
await self.rawmsg('CAP', 'ACK', cp)
|
||||
|
||||
# Run callback.
|
||||
if hasattr(self, attr):
|
||||
status = yield from getattr(self, attr)()
|
||||
status = await getattr(self, attr)()
|
||||
else:
|
||||
status = NEGOTIATED
|
||||
|
||||
|
@ -154,15 +147,14 @@ class CapabilityNegotiationSupport(rfc1459.RFC1459Support):
|
|||
# Ruh-roh, negotiation failed. Disable the capability.
|
||||
self.logger.warning('Capability negotiation for %s failed. Attempting to disable capability again.', cp)
|
||||
|
||||
yield from self.rawmsg('CAP', 'REQ', '-' + cp)
|
||||
await self.rawmsg('CAP', 'REQ', '-' + cp)
|
||||
self._capabilities_requested.add(cp)
|
||||
|
||||
# If we have no capabilities left to process, end it.
|
||||
if not self._capabilities_requested and not self._capabilities_negotiating:
|
||||
yield from self.rawmsg('CAP', 'END')
|
||||
await self.rawmsg('CAP', 'END')
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_cap_nak(self, params):
|
||||
async def on_raw_cap_nak(self, params):
|
||||
""" Update active capabilities: requested capability rejected. """
|
||||
for capab in params[0].split():
|
||||
capab, _ = self._capability_normalize(capab)
|
||||
|
@ -171,39 +163,34 @@ class CapabilityNegotiationSupport(rfc1459.RFC1459Support):
|
|||
|
||||
# If we have no capabilities left to process, end it.
|
||||
if not self._capabilities_requested and not self._capabilities_negotiating:
|
||||
yield from self.rawmsg('CAP', 'END')
|
||||
await self.rawmsg('CAP', 'END')
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_cap_del(self, params):
|
||||
async def on_raw_cap_del(self, params):
|
||||
for capab in params[0].split():
|
||||
attr = 'on_capability_{}_disabled'.format(pydle.protocol.identifierify(capab))
|
||||
if self._capabilities.get(capab, False) and hasattr(self, attr):
|
||||
yield from getattr(self, attr)()
|
||||
yield from self.on_raw_cap_nak(params)
|
||||
await getattr(self, attr)()
|
||||
await self.on_raw_cap_nak(params)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_cap_new(self, params):
|
||||
yield from self.on_raw_cap_ls(params)
|
||||
async def on_raw_cap_new(self, params):
|
||||
await self.on_raw_cap_ls(params)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_410(self, message):
|
||||
async def on_raw_410(self, message):
|
||||
""" Unknown CAP subcommand or CAP error. Force-end negotiations. """
|
||||
self.logger.error('Server sent "Unknown CAP subcommand: %s". Aborting capability negotiation.', message.params[0])
|
||||
|
||||
self._capabilities_requested = set()
|
||||
self._capabilities_negotiating = set()
|
||||
yield from self.rawmsg('CAP', 'END')
|
||||
await self.rawmsg('CAP', 'END')
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_421(self, message):
|
||||
async def on_raw_421(self, message):
|
||||
""" Hijack to ignore the absence of a CAP command. """
|
||||
if message.params[0] == 'CAP':
|
||||
return
|
||||
yield from super().on_raw_421(message)
|
||||
await super().on_raw_421(message)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_451(self, message):
|
||||
async def on_raw_451(self, message):
|
||||
""" Hijack to ignore the absence of a CAP command. """
|
||||
if message.params[0] == 'CAP':
|
||||
return
|
||||
yield from super().on_raw_451(message)
|
||||
await super().on_raw_451(message)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
## ircv3_1.py
|
||||
# IRCv3.1 full spec support.
|
||||
from pydle import async
|
||||
from pydle.features import account, tls
|
||||
from . import cap
|
||||
from . import sasl
|
||||
|
@ -27,36 +26,30 @@ class IRCv3_1Support(sasl.SASLSupport, cap.CapabilityNegotiationSupport, account
|
|||
|
||||
## IRC callbacks.
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_account_notify_available(self, value):
|
||||
async def on_capability_account_notify_available(self, value):
|
||||
""" Take note of user account changes. """
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_away_notify_available(self, value):
|
||||
async def on_capability_away_notify_available(self, value):
|
||||
""" Take note of AWAY messages. """
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_extended_join_available(self, value):
|
||||
async def on_capability_extended_join_available(self, value):
|
||||
""" Take note of user account and realname on JOIN. """
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_multi_prefix_available(self, value):
|
||||
async def on_capability_multi_prefix_available(self, value):
|
||||
""" Thanks to how underlying client code works we already support multiple prefixes. """
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_tls_available(self, value):
|
||||
async def on_capability_tls_available(self, value):
|
||||
""" We never need to request this explicitly. """
|
||||
return False
|
||||
|
||||
|
||||
## Message handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_account(self, message):
|
||||
async def on_raw_account(self, message):
|
||||
""" Changes in the associated account for a nickname. """
|
||||
if not self._capabilities.get('account-notify', False):
|
||||
return
|
||||
|
@ -73,8 +66,7 @@ class IRCv3_1Support(sasl.SASLSupport, cap.CapabilityNegotiationSupport, account
|
|||
else:
|
||||
self._sync_user(nick, { 'account': account, 'identified': True })
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_away(self, message):
|
||||
async def on_raw_away(self, message):
|
||||
""" Process AWAY messages. """
|
||||
if 'away-notify' not in self._capabilities or not self._capabilities['away-notify']:
|
||||
return
|
||||
|
@ -87,8 +79,7 @@ class IRCv3_1Support(sasl.SASLSupport, cap.CapabilityNegotiationSupport, account
|
|||
self.users[nick]['away'] = len(message.params) > 0
|
||||
self.users[nick]['away_message'] = message.params[0] if len(message.params) > 0 else None
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_join(self, message):
|
||||
async def on_raw_join(self, message):
|
||||
""" Process extended JOIN messages. """
|
||||
if 'extended-join' in self._capabilities and self._capabilities['extended-join']:
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
|
@ -98,11 +89,11 @@ class IRCv3_1Support(sasl.SASLSupport, cap.CapabilityNegotiationSupport, account
|
|||
|
||||
# Emit a fake join message.
|
||||
fakemsg = self._create_message('JOIN', channels, source=message.source)
|
||||
yield from super().on_raw_join(fakemsg)
|
||||
await super().on_raw_join(fakemsg)
|
||||
|
||||
if account == NO_ACCOUNT:
|
||||
account = None
|
||||
self.users[nick]['account'] = account
|
||||
self.users[nick]['realname'] = realname
|
||||
else:
|
||||
yield from super().on_raw_join(message)
|
||||
await super().on_raw_join(message)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
## ircv3_2.py
|
||||
# IRCv3.2 support (in progress).
|
||||
from pydle import async
|
||||
from . import ircv3_1
|
||||
from . import tags
|
||||
from . import monitor
|
||||
|
@ -14,75 +13,64 @@ class IRCv3_2Support(metadata.MetadataSupport, monitor.MonitoringSupport, tags.T
|
|||
|
||||
## IRC callbacks.
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_account_tag_available(self, value):
|
||||
async def on_capability_account_tag_available(self, value):
|
||||
""" Add an account message tag to user messages. """
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_cap_notify_available(self, value):
|
||||
async def on_capability_cap_notify_available(self, value):
|
||||
""" Take note of new or removed capabilities. """
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_chghost_available(self, value):
|
||||
async def on_capability_chghost_available(self, value):
|
||||
""" Server reply to indicate a user we are in a common channel with changed user and/or host. """
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_echo_message_available(self, value):
|
||||
async def on_capability_echo_message_available(self, value):
|
||||
""" Echo PRIVMSG and NOTICEs back to client. """
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_invite_notify_available(self, value):
|
||||
async def on_capability_invite_notify_available(self, value):
|
||||
""" Broadcast invite messages to certain other clients. """
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_userhost_in_names_available(self, value):
|
||||
async def on_capability_userhost_in_names_available(self, value):
|
||||
""" Show full user!nick@host in NAMES list. We already parse it like that. """
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_uhnames_available(self, value):
|
||||
async def on_capability_uhnames_available(self, value):
|
||||
""" Possibly outdated alias for userhost-in-names. """
|
||||
return (yield from self.on_capability_userhost_in_names_available(value))
|
||||
return (await self.on_capability_userhost_in_names_available(value))
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_uhnames(self, value):
|
||||
async def on_isupport_uhnames(self, value):
|
||||
""" Let the server know that we support UHNAMES using the old ISUPPORT method, for legacy support. """
|
||||
yield from self.rawmsg('PROTOCTL', 'UHNAMES')
|
||||
await self.rawmsg('PROTOCTL', 'UHNAMES')
|
||||
|
||||
|
||||
|
||||
## API overrides.
|
||||
|
||||
@async.coroutine
|
||||
def message(self, target, message):
|
||||
yield from super().message(target, message)
|
||||
async def message(self, target, message):
|
||||
await super().message(target, message)
|
||||
if not self._capabilities.get('echo-message'):
|
||||
yield from self.on_message(target, self.nickname, message)
|
||||
await self.on_message(target, self.nickname, message)
|
||||
if self.is_channel(target):
|
||||
yield from self.on_channel_message(target, self.nickname, message)
|
||||
await self.on_channel_message(target, self.nickname, message)
|
||||
else:
|
||||
yield from self.on_private_message(target, self.nickname, message)
|
||||
await self.on_private_message(target, self.nickname, message)
|
||||
|
||||
@async.coroutine
|
||||
def notice(self, target, message):
|
||||
yield from super().notice(target, message)
|
||||
async def notice(self, target, message):
|
||||
await super().notice(target, message)
|
||||
if not self._capabilities.get('echo-message'):
|
||||
yield from self.on_notice(target, self.nickname, message)
|
||||
await self.on_notice(target, self.nickname, message)
|
||||
if self.is_channel(target):
|
||||
yield from self.on_channel_notice(target, self.nickname, message)
|
||||
await self.on_channel_notice(target, self.nickname, message)
|
||||
else:
|
||||
yield from self.on_private_notice(target, self.nickname, message)
|
||||
await self.on_private_notice(target, self.nickname, message)
|
||||
|
||||
|
||||
## Message handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw(self, message):
|
||||
async def on_raw(self, message):
|
||||
if 'account' in message.tags:
|
||||
nick, _ = self._parse_user(message.source)
|
||||
if nick in self.users:
|
||||
|
@ -91,10 +79,9 @@ class IRCv3_2Support(metadata.MetadataSupport, monitor.MonitoringSupport, tags.T
|
|||
'account': message.tags['account']
|
||||
}
|
||||
self._sync_user(nick, metadata)
|
||||
yield from super().on_raw(message)
|
||||
await super().on_raw(message)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_chghost(self, message):
|
||||
async def on_raw_chghost(self, message):
|
||||
""" Change user and/or host of user. """
|
||||
if 'chghost' not in self._capabilities or not self._capabilities['chghost']:
|
||||
return
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
## ircv3_3.py
|
||||
# IRCv3.3 support (in progress).
|
||||
from pydle import async
|
||||
from . import ircv3_2
|
||||
|
||||
__all__ = [ 'IRCv3_3Support' ]
|
||||
|
@ -11,7 +10,6 @@ class IRCv3_3Support(ircv3_2.IRCv3_2Support):
|
|||
|
||||
## IRC callbacks.
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_message_tags_available(self, value):
|
||||
async def on_capability_message_tags_available(self, value):
|
||||
""" Indicate that we can in fact parse arbitrary tags. """
|
||||
return True
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from pydle import async
|
||||
from . import cap
|
||||
|
||||
VISIBLITY_ALL = '*'
|
||||
|
@ -17,16 +16,15 @@ class MetadataSupport(cap.CapabilityNegotiationSupport):
|
|||
|
||||
## IRC API.
|
||||
|
||||
@async.coroutine
|
||||
def get_metadata(self, target):
|
||||
async def get_metadata(self, target):
|
||||
"""
|
||||
Return user metadata information.
|
||||
This is a blocking asynchronous method: it has to be called from a coroutine, as follows:
|
||||
|
||||
metadata = yield from self.get_metadata('#foo')
|
||||
metadata = await self.get_metadata('#foo')
|
||||
"""
|
||||
if target not in self._pending['metadata']:
|
||||
yield from self.rawmsg('METADATA', target, 'LIST')
|
||||
await self.rawmsg('METADATA', target, 'LIST')
|
||||
|
||||
self._metadata_queue.append(target)
|
||||
self._metadata_info[target] = {}
|
||||
|
@ -34,34 +32,28 @@ class MetadataSupport(cap.CapabilityNegotiationSupport):
|
|||
|
||||
return self._pending['metadata'][target]
|
||||
|
||||
@async.coroutine
|
||||
def set_metadata(self, target, key, value):
|
||||
yield from self.rawmsg('METADATA', target, 'SET', key, value)
|
||||
async def set_metadata(self, target, key, value):
|
||||
await self.rawmsg('METADATA', target, 'SET', key, value)
|
||||
|
||||
@async.coroutine
|
||||
def unset_metadata(self, target, key):
|
||||
yield from self.rawmsg('METADATA', target, 'SET', key)
|
||||
async def unset_metadata(self, target, key):
|
||||
await self.rawmsg('METADATA', target, 'SET', key)
|
||||
|
||||
@async.coroutine
|
||||
def clear_metadata(self, target):
|
||||
yield from self.rawmsg('METADATA', target, 'CLEAR')
|
||||
async def clear_metadata(self, target):
|
||||
await self.rawmsg('METADATA', target, 'CLEAR')
|
||||
|
||||
|
||||
## Callbacks.
|
||||
|
||||
@async.coroutine
|
||||
def on_metadata(self, target, key, value, visibility=None):
|
||||
async def on_metadata(self, target, key, value, visibility=None):
|
||||
pass
|
||||
|
||||
|
||||
## Message handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_metadata_notify_available(self, value):
|
||||
async def on_capability_metadata_notify_available(self, value):
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_metadata(self, message):
|
||||
async def on_raw_metadata(self, message):
|
||||
""" Metadata event. """
|
||||
target, targetmeta = self._parse_user(message.params[0])
|
||||
key, visibility, value = message.params[1:4]
|
||||
|
@ -70,10 +62,9 @@ class MetadataSupport(cap.CapabilityNegotiationSupport):
|
|||
|
||||
if target in self.users:
|
||||
self._sync_user(target, targetmeta)
|
||||
yield from self.on_metadata(target, key, value, visibility=visibility)
|
||||
await self.on_metadata(target, key, value, visibility=visibility)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_760(self, message):
|
||||
async def on_raw_760(self, message):
|
||||
""" Metadata key/value for whois. """
|
||||
target, targetmeta = self._parse_user(message.params[0])
|
||||
key, _, value = message.params[1:4]
|
||||
|
@ -86,8 +77,7 @@ class MetadataSupport(cap.CapabilityNegotiationSupport):
|
|||
self._whois_info[target].setdefault('metadata', {})
|
||||
self._whois_info[target]['metadata'][key] = value
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_761(self, message):
|
||||
async def on_raw_761(self, message):
|
||||
""" Metadata key/value. """
|
||||
target, targetmeta = self._parse_user(message.params[0])
|
||||
key, visibility = message.params[1:3]
|
||||
|
@ -100,8 +90,7 @@ class MetadataSupport(cap.CapabilityNegotiationSupport):
|
|||
|
||||
self._metadata_info[target][key] = value
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_762(self, message):
|
||||
async def on_raw_762(self, message):
|
||||
""" End of metadata. """
|
||||
# No way to figure out whose query this belongs to, so make a best guess
|
||||
# it was the first one.
|
||||
|
@ -112,13 +101,11 @@ class MetadataSupport(cap.CapabilityNegotiationSupport):
|
|||
future = self._pending['metadata'].pop(nickname)
|
||||
future.set_result(self._metadata_info.pop(nickname))
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_764(self, message):
|
||||
async def on_raw_764(self, message):
|
||||
""" Metadata limit reached. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_765(self, message):
|
||||
async def on_raw_765(self, message):
|
||||
""" Invalid metadata target. """
|
||||
target, targetmeta = self._parse_user(message.params[0])
|
||||
|
||||
|
@ -133,22 +120,18 @@ class MetadataSupport(cap.CapabilityNegotiationSupport):
|
|||
future = self._pending['metadata'].pop(target)
|
||||
future.set_result(None)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_766(self, message):
|
||||
async def on_raw_766(self, message):
|
||||
""" Unknown metadata key. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_767(self, message):
|
||||
async def on_raw_767(self, message):
|
||||
""" Invalid metadata key. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_768(self, message):
|
||||
async def on_raw_768(self, message):
|
||||
""" Metadata key not set. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_769(self, message):
|
||||
async def on_raw_769(self, message):
|
||||
""" Metadata permission denied. """
|
||||
pass
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
## monitor.py
|
||||
# Online status monitoring support.
|
||||
from pydle import async
|
||||
from . import cap
|
||||
|
||||
|
||||
|
@ -62,46 +61,39 @@ class MonitoringSupport(cap.CapabilityNegotiationSupport):
|
|||
|
||||
## Callbacks.
|
||||
|
||||
@async.coroutine
|
||||
def on_user_online(self, nickname):
|
||||
async def on_user_online(self, nickname):
|
||||
""" Callback called when a monitored user appears online. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_user_offline(self, nickname):
|
||||
async def on_user_offline(self, nickname):
|
||||
""" Callback called when a monitored users goes offline. """
|
||||
pass
|
||||
|
||||
|
||||
## Message handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_monitor_notify_available(self, value):
|
||||
async def on_capability_monitor_notify_available(self, value):
|
||||
return True
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_730(self, message):
|
||||
async def on_raw_730(self, message):
|
||||
""" Someone we are monitoring just came online. """
|
||||
for nick in message.params[1].split(','):
|
||||
self._create_user(nick)
|
||||
yield from self.on_user_online(nickname)
|
||||
await self.on_user_online(nickname)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_731(self, message):
|
||||
async def on_raw_731(self, message):
|
||||
""" Someone we are monitoring got offline. """
|
||||
for nick in message.params[1].split(','):
|
||||
self._destroy_user(nick, monitor_override=True)
|
||||
yield from self.on_user_offline(nickname)
|
||||
await self.on_user_offline(nickname)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_732(self, message):
|
||||
async def on_raw_732(self, message):
|
||||
""" List of users we're monitoring. """
|
||||
self._monitoring.update(message.params[1].split(','))
|
||||
|
||||
on_raw_733 = cap.CapabilityNegotiationSupport._ignored # End of MONITOR list.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_734(self, message):
|
||||
async def on_raw_734(self, message):
|
||||
""" Monitor list is full, can't add target. """
|
||||
# Remove from monitoring list, not much else we can do.
|
||||
self._monitoring.difference_update(message.params[1].split(','))
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
## sasl.py
|
||||
# SASL authentication support. Currently we only support PLAIN authentication.
|
||||
import base64
|
||||
from functools import partial
|
||||
|
||||
try:
|
||||
import puresasl
|
||||
import puresasl.client
|
||||
except ImportError:
|
||||
puresasl = None
|
||||
|
||||
from pydle import async
|
||||
from . import cap
|
||||
|
||||
__all__ = [ 'SASLSupport' ]
|
||||
|
@ -41,15 +42,15 @@ class SASLSupport(cap.CapabilityNegotiationSupport):
|
|||
|
||||
## SASL functionality.
|
||||
|
||||
@async.coroutine
|
||||
def _sasl_start(self, mechanism):
|
||||
async def _sasl_start(self, mechanism):
|
||||
""" Initiate SASL authentication. """
|
||||
# The rest will be handled in on_raw_authenticate()/_sasl_respond().
|
||||
yield from self.rawmsg('AUTHENTICATE', mechanism)
|
||||
self._sasl_timer = self.eventloop.schedule_async_in(self.SASL_TIMEOUT, self._sasl_abort(timeout=True))
|
||||
await self.rawmsg('AUTHENTICATE', mechanism)
|
||||
# create a partial, required for our callback to get the kwarg
|
||||
_sasl_partial = partial(self._sasl_abort, timeout=True)
|
||||
self._sasl_timer = self.eventloop.call_later(self.SASL_TIMEOUT, _sasl_partial)
|
||||
|
||||
@async.coroutine
|
||||
def _sasl_abort(self, timeout=False):
|
||||
async def _sasl_abort(self, timeout=False):
|
||||
""" Abort SASL authentication. """
|
||||
if timeout:
|
||||
self.logger.error('SASL authentication timed out: aborting.')
|
||||
|
@ -57,23 +58,22 @@ class SASLSupport(cap.CapabilityNegotiationSupport):
|
|||
self.logger.error('SASL authentication aborted.')
|
||||
|
||||
if self._sasl_timer:
|
||||
self.eventloop.unschedule(self._sasl_timer)
|
||||
self._sasl_timer.cancel()
|
||||
|
||||
self._sasl_timer = None
|
||||
|
||||
# We're done here.
|
||||
yield from self.rawmsg('AUTHENTICATE', ABORT_MESSAGE)
|
||||
yield from self._capability_negotiated('sasl')
|
||||
await self.rawmsg('AUTHENTICATE', ABORT_MESSAGE)
|
||||
await self._capability_negotiated('sasl')
|
||||
|
||||
@async.coroutine
|
||||
def _sasl_end(self):
|
||||
async def _sasl_end(self):
|
||||
""" Finalize SASL authentication. """
|
||||
if self._sasl_timer:
|
||||
self.eventloop.unschedule(self._sasl_timer)
|
||||
self._sasl_timer.cancel()
|
||||
self._sasl_timer = None
|
||||
yield from self._capability_negotiated('sasl')
|
||||
await self._capability_negotiated('sasl')
|
||||
|
||||
@async.coroutine
|
||||
def _sasl_respond(self):
|
||||
async def _sasl_respond(self):
|
||||
""" Respond to SASL challenge with response. """
|
||||
# Formulate a response.
|
||||
if self._sasl_client:
|
||||
|
@ -84,7 +84,7 @@ class SASLSupport(cap.CapabilityNegotiationSupport):
|
|||
|
||||
if response is None:
|
||||
self.logger.warning('SASL challenge processing failed: aborting SASL authentication.')
|
||||
yield from self._sasl_abort()
|
||||
await self._sasl_abort()
|
||||
else:
|
||||
response = b''
|
||||
|
||||
|
@ -94,19 +94,18 @@ class SASLSupport(cap.CapabilityNegotiationSupport):
|
|||
|
||||
# Send response in chunks.
|
||||
while to_send > 0:
|
||||
yield from self.rawmsg('AUTHENTICATE', response[:RESPONSE_LIMIT])
|
||||
await self.rawmsg('AUTHENTICATE', response[:RESPONSE_LIMIT])
|
||||
response = response[RESPONSE_LIMIT:]
|
||||
to_send -= RESPONSE_LIMIT
|
||||
|
||||
# If our message fit exactly in SASL_RESPOSE_LIMIT-byte chunks, send an empty message to indicate we're done.
|
||||
if to_send == 0:
|
||||
yield from self.rawmsg('AUTHENTICATE', EMPTY_MESSAGE)
|
||||
await self.rawmsg('AUTHENTICATE', EMPTY_MESSAGE)
|
||||
|
||||
|
||||
## Capability callbacks.
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_sasl_available(self, value):
|
||||
async def on_capability_sasl_available(self, value):
|
||||
""" Check whether or not SASL is available. """
|
||||
if value:
|
||||
self._sasl_mechanisms = value.upper().split(',')
|
||||
|
@ -119,8 +118,7 @@ class SASLSupport(cap.CapabilityNegotiationSupport):
|
|||
self.logger.warning('SASL credentials set but puresasl module not found: not initiating SASL authentication.')
|
||||
return False
|
||||
|
||||
@async.coroutine
|
||||
def on_capability_sasl_enabled(self):
|
||||
async def on_capability_sasl_enabled(self):
|
||||
""" Start SASL authentication. """
|
||||
if self.sasl_mechanism:
|
||||
if self._sasl_mechanisms and self.sasl_mechanism not in self._sasl_mechanisms:
|
||||
|
@ -147,19 +145,18 @@ class SASLSupport(cap.CapabilityNegotiationSupport):
|
|||
mechanism = self._sasl_client.mechanism.upper()
|
||||
|
||||
# Initialize SASL.
|
||||
yield from self._sasl_start(mechanism)
|
||||
await self._sasl_start(mechanism)
|
||||
# Tell caller we need more time, and to not end capability negotiation just yet.
|
||||
return cap.NEGOTIATING
|
||||
|
||||
|
||||
## Message handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_authenticate(self, message):
|
||||
async def on_raw_authenticate(self, message):
|
||||
""" Received part of the authentication challenge. """
|
||||
# Cancel timeout timer.
|
||||
if self._sasl_timer:
|
||||
self.eventloop.unschedule(self._sasl_timer)
|
||||
self._sasl_timer.cancel()
|
||||
self._sasl_timer = None
|
||||
|
||||
# Add response data.
|
||||
|
@ -169,28 +166,25 @@ class SASLSupport(cap.CapabilityNegotiationSupport):
|
|||
|
||||
# If the response ain't exactly SASL_RESPONSE_LIMIT bytes long, it's the end. Process.
|
||||
if len(response) % RESPONSE_LIMIT > 0:
|
||||
yield from self._sasl_respond()
|
||||
await self._sasl_respond()
|
||||
else:
|
||||
# Response not done yet. Restart timer.
|
||||
self._sasl_timer = self.eventloop.schedule_async_in(self.SASL_TIMEOUT, self._sasl_abort(timeout=True))
|
||||
self._sasl_timer = self.eventloop.call_later(self.SASL_TIMEOUT, self._sasl_abort(timeout=True))
|
||||
|
||||
|
||||
on_raw_900 = cap.CapabilityNegotiationSupport._ignored # You are now logged in as...
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_903(self, message):
|
||||
async def on_raw_903(self, message):
|
||||
""" SASL authentication successful. """
|
||||
yield from self._sasl_end()
|
||||
await self._sasl_end()
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_904(self, message):
|
||||
async def on_raw_904(self, message):
|
||||
""" Invalid mechanism or authentication failed. Abort SASL. """
|
||||
yield from self._sasl_abort()
|
||||
await self._sasl_abort()
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_905(self, message):
|
||||
async def on_raw_905(self, message):
|
||||
""" Authentication failed. Abort SASL. """
|
||||
yield from self._sasl_abort()
|
||||
await self._sasl_abort()
|
||||
|
||||
on_raw_906 = cap.CapabilityNegotiationSupport._ignored # Completed registration while authenticating/registration aborted.
|
||||
on_raw_907 = cap.CapabilityNegotiationSupport._ignored # Already authenticated over SASL.
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
# ISUPPORT (server-side IRC extension indication) support.
|
||||
# See: http://tools.ietf.org/html/draft-hardy-irc-isupport-00
|
||||
import collections
|
||||
from pydle import async
|
||||
import pydle.protocol
|
||||
from pydle.features import rfc1459
|
||||
|
||||
|
@ -36,8 +35,7 @@ class ISUPPORTSupport(rfc1459.RFC1459Support):
|
|||
|
||||
## Command handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_005(self, message):
|
||||
async def on_raw_005(self, message):
|
||||
""" ISUPPORT indication. """
|
||||
isupport = {}
|
||||
|
||||
|
@ -64,31 +62,27 @@ class ISUPPORTSupport(rfc1459.RFC1459Support):
|
|||
|
||||
method = 'on_isupport_' + pydle.protocol.identifierify(entry)
|
||||
if hasattr(self, method):
|
||||
yield from getattr(self, method)(value)
|
||||
await getattr(self, method)(value)
|
||||
|
||||
|
||||
## ISUPPORT handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_awaylen(self, value):
|
||||
async def on_isupport_awaylen(self, value):
|
||||
""" Away message length limit. """
|
||||
self._away_message_length_limit = int(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_casemapping(self, value):
|
||||
async def on_isupport_casemapping(self, value):
|
||||
""" IRC case mapping for nickname and channel name comparisons. """
|
||||
if value in rfc1459.protocol.CASE_MAPPINGS:
|
||||
self._case_mapping = value
|
||||
self.channels = rfc1459.parsing.NormalizingDict(self.channels, case_mapping=value)
|
||||
self.users = rfc1459.parsing.NormalizingDict(self.users, case_mapping=value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_channellen(self, value):
|
||||
async def on_isupport_channellen(self, value):
|
||||
""" Channel name length limit. """
|
||||
self._channel_length_limit = int(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_chanlimit(self, value):
|
||||
async def on_isupport_chanlimit(self, value):
|
||||
""" Simultaneous channel limits for user. """
|
||||
self._channel_limits = {}
|
||||
|
||||
|
@ -100,8 +94,7 @@ class ISUPPORTSupport(rfc1459.RFC1459Support):
|
|||
for prefix in types:
|
||||
self._channel_limit_groups[prefix] = frozenset(types)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_chanmodes(self, value):
|
||||
async def on_isupport_chanmodes(self, value):
|
||||
""" Valid channel modes and their behaviour. """
|
||||
list, param, param_set, noparams = [ set(modes) for modes in value.split(',')[:4] ]
|
||||
self._channel_modes.update(set(value.replace(',', '')))
|
||||
|
@ -123,45 +116,39 @@ class ISUPPORTSupport(rfc1459.RFC1459Support):
|
|||
self._channel_modes_behaviour[rfc1459.protocol.BEHAVIOUR_NO_PARAMETER] = set()
|
||||
self._channel_modes_behaviour[rfc1459.protocol.BEHAVIOUR_NO_PARAMETER].update(noparams)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_chantypes(self, value):
|
||||
async def on_isupport_chantypes(self, value):
|
||||
""" Channel name prefix symbols. """
|
||||
if not value:
|
||||
value = ''
|
||||
self._channel_prefixes = set(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_excepts(self, value):
|
||||
async def on_isupport_excepts(self, value):
|
||||
""" Server allows ban exceptions. """
|
||||
if not value:
|
||||
value = BAN_EXCEPT_MODE
|
||||
self._channel_modes.add(value)
|
||||
self._channel_modes_behaviour[rfc1459.protocol.BEHAVIOUR_LIST].add(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_extban(self, value):
|
||||
async def on_isupport_extban(self, value):
|
||||
""" Extended ban prefixes. """
|
||||
self._extban_prefix, types = value.split(',')
|
||||
self._extban_types = set(types)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_invex(self, value):
|
||||
async def on_isupport_invex(self, value):
|
||||
""" Server allows invite exceptions. """
|
||||
if not value:
|
||||
value = INVITE_EXCEPT_MODE
|
||||
self._channel_modes.add(value)
|
||||
self._channel_modes_behaviour[rfc1459.protocol.BEHAVIOUR_LIST].add(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_maxbans(self, value):
|
||||
async def on_isupport_maxbans(self, value):
|
||||
""" Maximum entries in ban list. Replaced by MAXLIST. """
|
||||
if 'MAXLIST' not in self._isupport:
|
||||
if not self._list_limits:
|
||||
self._list_limits = {}
|
||||
self._list_limits['b'] = int(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_maxchannels(self, value):
|
||||
async def on_isupport_maxchannels(self, value):
|
||||
""" Old version of CHANLIMIT. """
|
||||
if 'CHANTYPES' in self._isupport and 'CHANLIMIT' not in self._isupport:
|
||||
self._channel_limits = {}
|
||||
|
@ -172,8 +159,7 @@ class ISUPPORTSupport(rfc1459.RFC1459Support):
|
|||
for prefix in prefixes:
|
||||
self._channel_limit_groups[prefix] = frozenset(prefixes)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_maxlist(self, value):
|
||||
async def on_isupport_maxlist(self, value):
|
||||
""" Limits on channel modes involving lists. """
|
||||
self._list_limits = {}
|
||||
|
||||
|
@ -185,33 +171,27 @@ class ISUPPORTSupport(rfc1459.RFC1459Support):
|
|||
for mode in modes:
|
||||
self._list_limit_groups[mode] = frozenset(modes)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_maxpara(self, value):
|
||||
async def on_isupport_maxpara(self, value):
|
||||
""" Limits to parameters given to command. """
|
||||
self._command_parameter_limit = int(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_modes(self, value):
|
||||
async def on_isupport_modes(self, value):
|
||||
""" Maximum number of variable modes to change in a single MODE command. """
|
||||
self._mode_limit = int(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_namesx(self, value):
|
||||
async def on_isupport_namesx(self, value):
|
||||
""" Let the server know we do in fact support NAMESX. Effectively the same as CAP multi-prefix. """
|
||||
yield from self.rawmsg('PROTOCTL', 'NAMESX')
|
||||
await self.rawmsg('PROTOCTL', 'NAMESX')
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_network(self, value):
|
||||
async def on_isupport_network(self, value):
|
||||
""" IRC network name. """
|
||||
self.network = value
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_nicklen(self, value):
|
||||
async def on_isupport_nicklen(self, value):
|
||||
""" Nickname length limit. """
|
||||
self._nickname_length_limit = int(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_prefix(self, value):
|
||||
async def on_isupport_prefix(self, value):
|
||||
""" Nickname prefixes on channels and their associated modes. """
|
||||
if not value:
|
||||
# No prefixes support.
|
||||
|
@ -230,13 +210,11 @@ class ISUPPORTSupport(rfc1459.RFC1459Support):
|
|||
for mode, prefix in zip(modes, prefixes):
|
||||
self._nickname_prefixes[prefix] = mode
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_statusmsg(self, value):
|
||||
async def on_isupport_statusmsg(self, value):
|
||||
""" Support for messaging every member on a channel with given status or higher. """
|
||||
self._status_message_prefixes.update(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_targmax(self, value):
|
||||
async def on_isupport_targmax(self, value):
|
||||
""" The maximum number of targets certain types of commands can affect. """
|
||||
if not value:
|
||||
return
|
||||
|
@ -247,13 +225,11 @@ class ISUPPORTSupport(rfc1459.RFC1459Support):
|
|||
continue
|
||||
self._target_limits[command] = int(limit)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_topiclen(self, value):
|
||||
async def on_isupport_topiclen(self, value):
|
||||
""" Channel topic length limit. """
|
||||
self._topic_length_limit = int(value)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_wallchops(self, value):
|
||||
async def on_isupport_wallchops(self, value):
|
||||
""" Support for messaging every opped member or higher on a channel. Replaced by STATUSMSG. """
|
||||
for prefix, mode in self._nickname_prefixes.items():
|
||||
if mode == 'o':
|
||||
|
@ -262,8 +238,7 @@ class ISUPPORTSupport(rfc1459.RFC1459Support):
|
|||
prefix = '@'
|
||||
self._status_message_prefixes.add(prefix)
|
||||
|
||||
@async.coroutine
|
||||
def on_isupport_wallvoices(self, value):
|
||||
async def on_isupport_wallvoices(self, value):
|
||||
""" Support for messaging every voiced member or higher on a channel. Replaced by STATUSMSG. """
|
||||
for prefix, mode in self._nickname_prefixes.items():
|
||||
if mode == 'v':
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
## rfc1459.py
|
||||
# Basic RFC1459 stuff.
|
||||
import datetime
|
||||
import itertools
|
||||
import copy
|
||||
import datetime
|
||||
import ipaddress
|
||||
import itertools
|
||||
|
||||
from pydle import async
|
||||
from pydle.client import BasicClient, NotInChannel, AlreadyInChannel
|
||||
from . import parsing
|
||||
from . import protocol
|
||||
|
@ -98,7 +97,7 @@ class RFC1459Support(BasicClient):
|
|||
|
||||
def _destroy_user(self, user, channel=None):
|
||||
if channel:
|
||||
channels = [ self.channels[channel] ]
|
||||
channels = [self.channels[channel]]
|
||||
else:
|
||||
channels = self.channels.values()
|
||||
|
||||
|
@ -185,19 +184,18 @@ class RFC1459Support(BasicClient):
|
|||
|
||||
## Connection.
|
||||
|
||||
@async.coroutine
|
||||
def connect(self, hostname=None, port=None, password=None, **kwargs):
|
||||
async def connect(self, hostname=None, port=None, password=None, **kwargs):
|
||||
port = port or protocol.DEFAULT_PORT
|
||||
|
||||
# Connect...
|
||||
yield from super().connect(hostname, port, **kwargs)
|
||||
await super().connect(hostname, port, **kwargs)
|
||||
# Set password.
|
||||
self.password = password
|
||||
# And initiate the IRC connection.
|
||||
yield from self._register()
|
||||
await self._register()
|
||||
|
||||
@async.coroutine
|
||||
def _register(self):
|
||||
|
||||
async def _register(self):
|
||||
""" Perform IRC connection registration. """
|
||||
if self.registered:
|
||||
return
|
||||
|
@ -209,15 +207,14 @@ class RFC1459Support(BasicClient):
|
|||
|
||||
# Password first.
|
||||
if self.password:
|
||||
yield from self.rawmsg('PASS', self.password)
|
||||
await self.rawmsg('PASS', self.password)
|
||||
|
||||
# Then nickname...
|
||||
yield from self.set_nickname(self._attempt_nicknames.pop(0))
|
||||
await self.set_nickname(self._attempt_nicknames.pop(0))
|
||||
# And now for the rest of the user information.
|
||||
yield from self.rawmsg('USER', self.username, '0', '*', self.realname)
|
||||
await self.rawmsg('USER', self.username, '0', '*', self.realname)
|
||||
|
||||
@async.coroutine
|
||||
def _registration_completed(self, message):
|
||||
async def _registration_completed(self, message):
|
||||
""" We're connected and registered. Receive proper nickname and emit fake NICK message. """
|
||||
if not self.registered:
|
||||
# Re-enable throttling.
|
||||
|
@ -226,8 +223,7 @@ class RFC1459Support(BasicClient):
|
|||
|
||||
target = message.params[0]
|
||||
fakemsg = self._create_message('NICK', target, source=self.nickname)
|
||||
yield from self.on_raw_nick(fakemsg)
|
||||
|
||||
await self.on_raw_nick(fakemsg)
|
||||
|
||||
## Message handling.
|
||||
|
||||
|
@ -245,53 +241,48 @@ class RFC1459Support(BasicClient):
|
|||
self._receive_buffer = data
|
||||
return parsing.RFC1459Message.parse(message + sep, encoding=self.encoding)
|
||||
|
||||
|
||||
## IRC API.
|
||||
|
||||
@async.coroutine
|
||||
def set_nickname(self, nickname):
|
||||
async def set_nickname(self, nickname):
|
||||
"""
|
||||
Set nickname to given nickname.
|
||||
Users should only rely on the nickname actually being changed when receiving an on_nick_change callback.
|
||||
"""
|
||||
yield from self.rawmsg('NICK', nickname)
|
||||
await self.rawmsg('NICK', nickname)
|
||||
|
||||
@async.coroutine
|
||||
def join(self, channel, password=None):
|
||||
async def join(self, channel, password=None):
|
||||
""" Join channel, optionally with password. """
|
||||
if self.in_channel(channel):
|
||||
raise AlreadyInChannel(channel)
|
||||
|
||||
if password:
|
||||
yield from self.rawmsg('JOIN', channel, password)
|
||||
await self.rawmsg('JOIN', channel, password)
|
||||
else:
|
||||
yield from self.rawmsg('JOIN', channel)
|
||||
await self.rawmsg('JOIN', channel)
|
||||
|
||||
@async.coroutine
|
||||
def part(self, channel, message=None):
|
||||
async def part(self, channel, message=None):
|
||||
""" Leave channel, optionally with message. """
|
||||
if not self.in_channel(channel):
|
||||
raise NotInChannel(channel)
|
||||
|
||||
# Message seems to be an extension to the spec.
|
||||
if message:
|
||||
yield from self.rawmsg('PART', channel, message)
|
||||
await self.rawmsg('PART', channel, message)
|
||||
else:
|
||||
yield from self.rawmsg('PART', channel)
|
||||
await self.rawmsg('PART', channel)
|
||||
|
||||
@async.coroutine
|
||||
def kick(self, channel, target, reason=None):
|
||||
|
||||
async def kick(self, channel, target, reason=None):
|
||||
""" Kick user from channel. """
|
||||
if not self.in_channel(channel):
|
||||
raise NotInChannel(channel)
|
||||
|
||||
if reason:
|
||||
yield from self.rawmsg('KICK', channel, target, reason)
|
||||
await self.rawmsg('KICK', channel, target, reason)
|
||||
else:
|
||||
yield from self.rawmsg('KICK', channel, target)
|
||||
await self.rawmsg('KICK', channel, target)
|
||||
|
||||
@async.coroutine
|
||||
def ban(self, channel, target, range=0):
|
||||
async def ban(self, channel, target, range=0):
|
||||
"""
|
||||
Ban user from channel. Target can be either a user or a host.
|
||||
This command will not kick: use kickban() for that.
|
||||
|
@ -305,10 +296,9 @@ class RFC1459Support(BasicClient):
|
|||
|
||||
host = self._format_host_range(host, range)
|
||||
mask = self._format_host_mask('*', '*', host)
|
||||
yield from self.rawmsg('MODE', channel, '+b', mask)
|
||||
await self.rawmsg('MODE', channel, '+b', mask)
|
||||
|
||||
@async.coroutine
|
||||
def unban(self, channel, target, range=0):
|
||||
async def unban(self, channel, target, range=0):
|
||||
"""
|
||||
Unban user from channel. Target can be either a user or a host.
|
||||
See ban documentation for the range parameter.
|
||||
|
@ -320,60 +310,56 @@ class RFC1459Support(BasicClient):
|
|||
|
||||
host = self._format_host_range(host, range)
|
||||
mask = self._format_host_mask('*', '*', host)
|
||||
yield from self.rawmsg('MODE', channel, '-b', mask)
|
||||
await self.rawmsg('MODE', channel, '-b', mask)
|
||||
|
||||
@async.coroutine
|
||||
def kickban(self, channel, target, reason=None, range=0):
|
||||
async def kickban(self, channel, target, reason=None, range=0):
|
||||
"""
|
||||
Kick and ban user from channel.
|
||||
"""
|
||||
yield from self.ban(channel, target, range)
|
||||
yield from self.kick(channel, target, reason)
|
||||
await self.ban(channel, target, range)
|
||||
await self.kick(channel, target, reason)
|
||||
|
||||
@async.coroutine
|
||||
def quit(self, message=None):
|
||||
async def quit(self, message=None):
|
||||
""" Quit network. """
|
||||
if message is None:
|
||||
message = self.DEFAULT_QUIT_MESSAGE
|
||||
|
||||
yield from self.rawmsg('QUIT', message)
|
||||
await self.rawmsg('QUIT', message)
|
||||
self.disconnect(expected=True)
|
||||
|
||||
@async.coroutine
|
||||
def cycle(self, channel):
|
||||
async def cycle(self, channel):
|
||||
""" Rejoin channel. """
|
||||
if not self.in_channel(channel):
|
||||
raise NotInChannel(channel)
|
||||
|
||||
password = self.channels[channel]['password']
|
||||
yield from self.part(channel)
|
||||
yield from self.join(channel, password)
|
||||
await self.part(channel)
|
||||
await self.join(channel, password)
|
||||
|
||||
@async.coroutine
|
||||
def message(self, target, message):
|
||||
async def message(self, target, message):
|
||||
""" Message channel or user. """
|
||||
hostmask = self._format_user_mask(self.nickname)
|
||||
# Leeway.
|
||||
chunklen = protocol.MESSAGE_LENGTH_LIMIT - len('{hostmask} PRIVMSG {target} :'.format(hostmask=hostmask, target=target)) - 25
|
||||
chunklen = protocol.MESSAGE_LENGTH_LIMIT - len(
|
||||
'{hostmask} PRIVMSG {target} :'.format(hostmask=hostmask, target=target)) - 25
|
||||
|
||||
for line in message.replace('\r', '').split('\n'):
|
||||
for chunk in chunkify(line, chunklen):
|
||||
# Some IRC servers respond with "412 Bot :No text to send" on empty messages.
|
||||
yield from self.rawmsg('PRIVMSG', target, chunk or ' ')
|
||||
await self.rawmsg('PRIVMSG', target, chunk or ' ')
|
||||
|
||||
@async.coroutine
|
||||
def notice(self, target, message):
|
||||
async def notice(self, target, message):
|
||||
""" Notice channel or user. """
|
||||
hostmask = self._format_user_mask(self.nickname)
|
||||
# Leeway.
|
||||
chunklen = protocol.MESSAGE_LENGTH_LIMIT - len('{hostmask} NOTICE {target} :'.format(hostmask=hostmask, target=target)) - 25
|
||||
chunklen = protocol.MESSAGE_LENGTH_LIMIT - len(
|
||||
'{hostmask} NOTICE {target} :'.format(hostmask=hostmask, target=target)) - 25
|
||||
|
||||
for line in message.replace('\r', '').split('\n'):
|
||||
for chunk in chunkify(line, chunklen):
|
||||
yield from self.rawmsg('NOTICE', target, chunk)
|
||||
await self.rawmsg('NOTICE', target, chunk)
|
||||
|
||||
@async.coroutine
|
||||
def set_mode(self, target, *modes):
|
||||
async def set_mode(self, target, *modes):
|
||||
"""
|
||||
Set mode on target.
|
||||
Users should only rely on the mode actually being changed when receiving an on_{channel,user}_mode_change callback.
|
||||
|
@ -381,10 +367,9 @@ class RFC1459Support(BasicClient):
|
|||
if self.is_channel(target) and not self.in_channel(target):
|
||||
raise NotInChannel(target)
|
||||
|
||||
yield from self.rawmsg('MODE', target, *modes)
|
||||
await self.rawmsg('MODE', target, *modes)
|
||||
|
||||
@async.coroutine
|
||||
def set_topic(self, channel, topic):
|
||||
async def set_topic(self, channel, topic):
|
||||
"""
|
||||
Set topic on channel.
|
||||
Users should only rely on the topic actually being changed when receiving an on_topic_change callback.
|
||||
|
@ -394,25 +379,22 @@ class RFC1459Support(BasicClient):
|
|||
elif not self.in_channel(channel):
|
||||
raise NotInChannel(channel)
|
||||
|
||||
yield from self.rawmsg('TOPIC', channel, topic)
|
||||
await self.rawmsg('TOPIC', channel, topic)
|
||||
|
||||
@async.coroutine
|
||||
def away(self, message):
|
||||
async def away(self, message):
|
||||
""" Mark self as away. """
|
||||
yield from self.rawmsg('AWAY', message)
|
||||
await self.rawmsg('AWAY', message)
|
||||
|
||||
@async.coroutine
|
||||
def back(self):
|
||||
async def back(self):
|
||||
""" Mark self as not away. """
|
||||
yield from self.rawmsg('AWAY')
|
||||
await self.rawmsg('AWAY')
|
||||
|
||||
@async.coroutine
|
||||
def whois(self, nickname):
|
||||
async def whois(self, nickname):
|
||||
"""
|
||||
Return information about user.
|
||||
This is an blocking asynchronous method: it has to be called from a coroutine, as follows:
|
||||
|
||||
info = yield from self.whois('Nick')
|
||||
info = await self.whois('Nick')
|
||||
"""
|
||||
# Some IRCDs are wonky and send strange responses for spaces in nicknames.
|
||||
# We just check if there's a space in the nickname -- if there is,
|
||||
|
@ -423,7 +405,7 @@ class RFC1459Support(BasicClient):
|
|||
return result
|
||||
|
||||
if nickname not in self._pending['whois']:
|
||||
yield from self.rawmsg('WHOIS', nickname)
|
||||
await self.rawmsg('WHOIS', nickname)
|
||||
self._whois_info[nickname] = {
|
||||
'oper': False,
|
||||
'idle': 0,
|
||||
|
@ -434,15 +416,14 @@ class RFC1459Support(BasicClient):
|
|||
# Create a future for when the WHOIS requests succeeds.
|
||||
self._pending['whois'][nickname] = self.eventloop.create_future()
|
||||
|
||||
return (yield from self._pending['whois'][nickname])
|
||||
return await self._pending['whois'][nickname]
|
||||
|
||||
@async.coroutine
|
||||
def whowas(self, nickname):
|
||||
async def whowas(self, nickname):
|
||||
"""
|
||||
Return information about offline user.
|
||||
This is an blocking asynchronous method: it has to be called from a coroutine, as follows:
|
||||
|
||||
info = yield from self.whowas('Nick')
|
||||
info = await self.whowas('Nick')
|
||||
"""
|
||||
# Same treatment as nicknames in whois.
|
||||
if protocol.ARGUMENT_SEPARATOR.search(nickname) is not None:
|
||||
|
@ -451,14 +432,13 @@ class RFC1459Support(BasicClient):
|
|||
return result
|
||||
|
||||
if nickname not in self._pending['whowas']:
|
||||
yield from self.rawmsg('WHOWAS', nickname)
|
||||
await self.rawmsg('WHOWAS', nickname)
|
||||
self._whowas_info[nickname] = {}
|
||||
|
||||
# Create a future for when the WHOWAS requests succeeds.
|
||||
self._pending['whowas'][nickname] = self.eventloop.create_future()
|
||||
|
||||
return (yield from self._pending['whowas'][nickname])
|
||||
|
||||
return (await self._pending['whowas'][nickname])
|
||||
|
||||
## IRC helpers.
|
||||
|
||||
|
@ -476,111 +456,89 @@ class RFC1459Support(BasicClient):
|
|||
""" Check if given nicknames are equal in the server's case mapping. """
|
||||
return self.normalize(left) == self.normalize(right)
|
||||
|
||||
|
||||
## Overloadable callbacks.
|
||||
|
||||
@async.coroutine
|
||||
def on_connect(self):
|
||||
async def on_connect(self):
|
||||
# Auto-join channels.
|
||||
for channel in self._autojoin_channels:
|
||||
yield from self.join(channel)
|
||||
await self.join(channel)
|
||||
|
||||
@async.coroutine
|
||||
def on_invite(self, channel, by):
|
||||
async def on_invite(self, channel, by):
|
||||
""" Callback called when the client was invited into a channel by someone. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_user_invite(self, target, channel, by):
|
||||
async def on_user_invite(self, target, channel, by):
|
||||
""" Callback called when another user was invited into a channel by someone. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_join(self, channel, user):
|
||||
async def on_join(self, channel, user):
|
||||
""" Callback called when a user, possibly the client, has joined the channel. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_kill(self, target, by, reason):
|
||||
async def on_kill(self, target, by, reason):
|
||||
""" Callback called when a user, possibly the client, was killed from the server. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_kick(self, channel, target, by, reason=None):
|
||||
async def on_kick(self, channel, target, by, reason=None):
|
||||
""" Callback called when a user, possibly the client, was kicked from a channel. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_mode_change(self, channel, modes, by):
|
||||
async def on_mode_change(self, channel, modes, by):
|
||||
""" Callback called when the mode on a channel was changed. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_user_mode_change(self, modes):
|
||||
async def on_user_mode_change(self, modes):
|
||||
""" Callback called when a user mode change occurred for the client. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_message(self, target, by, message):
|
||||
async def on_message(self, target, by, message):
|
||||
""" Callback called when the client received a message. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_channel_message(self, target, by, message):
|
||||
async def on_channel_message(self, target, by, message):
|
||||
""" Callback received when the client received a message in a channel. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_private_message(self, target, by, message):
|
||||
async def on_private_message(self, target, by, message):
|
||||
""" Callback called when the client received a message in private. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_nick_change(self, old, new):
|
||||
async def on_nick_change(self, old, new):
|
||||
""" Callback called when a user, possibly the client, changed their nickname. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_notice(self, target, by, message):
|
||||
async def on_notice(self, target, by, message):
|
||||
""" Callback called when the client received a notice. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_channel_notice(self, target, by, message):
|
||||
async def on_channel_notice(self, target, by, message):
|
||||
""" Callback called when the client received a notice in a channel. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_private_notice(self, target, by, message):
|
||||
async def on_private_notice(self, target, by, message):
|
||||
""" Callback called when the client received a notice in private. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_part(self, channel, user, message=None):
|
||||
async def on_part(self, channel, user, message=None):
|
||||
""" Callback called when a user, possibly the client, left a channel. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_topic_change(self, channel, message, by):
|
||||
async def on_topic_change(self, channel, message, by):
|
||||
""" Callback called when the topic for a channel was changed. """
|
||||
pass
|
||||
|
||||
@async.coroutine
|
||||
def on_quit(self, user, message=None):
|
||||
async def on_quit(self, user, message=None):
|
||||
""" Callback called when a user, possibly the client, left the network. """
|
||||
pass
|
||||
|
||||
|
||||
## Callback handlers.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_error(self, message):
|
||||
async def on_raw_error(self, message):
|
||||
""" Server encountered an error and will now close the connection. """
|
||||
error = protocol.ServerError(' '.join(message.params))
|
||||
yield from self.on_data_error(error)
|
||||
await self.on_data_error(error)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_invite(self, message):
|
||||
async def on_raw_invite(self, message):
|
||||
""" INVITE command. """
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
self._sync_user(nick, metadata)
|
||||
|
@ -589,12 +547,11 @@ class RFC1459Support(BasicClient):
|
|||
target, metadata = self._parse_user(target)
|
||||
|
||||
if self.is_same_nick(self.nickname, target):
|
||||
yield from self.on_invite(channel, nick)
|
||||
await self.on_invite(channel, nick)
|
||||
else:
|
||||
yield from self.on_user_invite(target, channel, nick)
|
||||
await self.on_user_invite(target, channel, nick)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_join(self, message):
|
||||
async def on_raw_join(self, message):
|
||||
""" JOIN command. """
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
self._sync_user(nick, metadata)
|
||||
|
@ -607,7 +564,7 @@ class RFC1459Support(BasicClient):
|
|||
self._create_channel(channel)
|
||||
|
||||
# Request channel mode from IRCd.
|
||||
yield from self.rawmsg('MODE', channel)
|
||||
await self.rawmsg('MODE', channel)
|
||||
else:
|
||||
# Add user to channel user list.
|
||||
for channel in channels:
|
||||
|
@ -615,10 +572,9 @@ class RFC1459Support(BasicClient):
|
|||
self.channels[channel]['users'].add(nick)
|
||||
|
||||
for channel in channels:
|
||||
yield from self.on_join(channel, nick)
|
||||
await self.on_join(channel, nick)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_kick(self, message):
|
||||
async def on_raw_kick(self, message):
|
||||
""" KICK command. """
|
||||
kicker, kickermeta = self._parse_user(message.source)
|
||||
self._sync_user(kicker, kickermeta)
|
||||
|
@ -643,10 +599,9 @@ class RFC1459Support(BasicClient):
|
|||
if self.in_channel(channel):
|
||||
self._destroy_user(target, channel)
|
||||
|
||||
yield from self.on_kick(channel, target, kicker, reason)
|
||||
await self.on_kick(channel, target, kicker, reason)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_kill(self, message):
|
||||
async def on_raw_kill(self, message):
|
||||
""" KILL command. """
|
||||
by, bymeta = self._parse_user(message.source)
|
||||
target, targetmeta = self._parse_user(message.params[0])
|
||||
|
@ -656,14 +611,13 @@ class RFC1459Support(BasicClient):
|
|||
if by in self.users:
|
||||
self._sync_user(by, bymeta)
|
||||
|
||||
yield from self.on_kill(target, by, reason)
|
||||
await self.on_kill(target, by, reason)
|
||||
if self.is_same_nick(self.nickname, target):
|
||||
self.disconnect(expected=False)
|
||||
else:
|
||||
self._destroy_user(target)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_mode(self, message):
|
||||
async def on_raw_mode(self, message):
|
||||
""" MODE command. """
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
target, modes = message.params[0], message.params[1:]
|
||||
|
@ -674,7 +628,7 @@ class RFC1459Support(BasicClient):
|
|||
# Parse modes.
|
||||
self.channels[target]['modes'] = self._parse_channel_modes(target, modes)
|
||||
|
||||
yield from self.on_mode_change(target, modes, nick)
|
||||
await self.on_mode_change(target, modes, nick)
|
||||
else:
|
||||
target, targetmeta = self._parse_user(target)
|
||||
self._sync_user(target, targetmeta)
|
||||
|
@ -683,10 +637,9 @@ class RFC1459Support(BasicClient):
|
|||
if self.is_same_nick(self.nickname, nick):
|
||||
self._mode = self._parse_user_modes(nick, modes, current=self._mode)
|
||||
|
||||
yield from self.on_user_mode_change(modes)
|
||||
await self.on_user_mode_change(modes)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_nick(self, message):
|
||||
async def on_raw_nick(self, message):
|
||||
""" NICK command. """
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
new = message.params[0]
|
||||
|
@ -701,24 +654,22 @@ class RFC1459Support(BasicClient):
|
|||
self._rename_user(nick, new)
|
||||
|
||||
# Call handler.
|
||||
yield from self.on_nick_change(nick, new)
|
||||
await self.on_nick_change(nick, new)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_notice(self, message):
|
||||
async def on_raw_notice(self, message):
|
||||
""" NOTICE command. """
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
target, message = message.params
|
||||
|
||||
self._sync_user(nick, metadata)
|
||||
|
||||
yield from self.on_notice(target, nick, message)
|
||||
await self.on_notice(target, nick, message)
|
||||
if self.is_channel(target):
|
||||
yield from self.on_channel_notice(target, nick, message)
|
||||
await self.on_channel_notice(target, nick, message)
|
||||
else:
|
||||
yield from self.on_private_notice(target, nick, message)
|
||||
await self.on_private_notice(target, nick, message)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_part(self, message):
|
||||
async def on_raw_part(self, message):
|
||||
""" PART command. """
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
channels = message.params[0].split(',')
|
||||
|
@ -733,35 +684,32 @@ class RFC1459Support(BasicClient):
|
|||
for channel in channels:
|
||||
if self.in_channel(channel):
|
||||
self._destroy_channel(channel)
|
||||
yield from self.on_part(channel, nick, reason)
|
||||
await self.on_part(channel, nick, reason)
|
||||
else:
|
||||
# Someone else left. Remove them.
|
||||
for channel in channels:
|
||||
self._destroy_user(nick, channel)
|
||||
yield from self.on_part(channel, nick, reason)
|
||||
await self.on_part(channel, nick, reason)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_ping(self, message):
|
||||
async def on_raw_ping(self, message):
|
||||
""" PING command. """
|
||||
# Respond with a pong.
|
||||
yield from self.rawmsg('PONG', *message.params)
|
||||
await self.rawmsg('PONG', *message.params)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_privmsg(self, message):
|
||||
async def on_raw_privmsg(self, message):
|
||||
""" PRIVMSG command. """
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
target, message = message.params
|
||||
|
||||
self._sync_user(nick, metadata)
|
||||
|
||||
yield from self.on_message(target, nick, message)
|
||||
await self.on_message(target, nick, message)
|
||||
if self.is_channel(target):
|
||||
yield from self.on_channel_message(target, nick, message)
|
||||
await self.on_channel_message(target, nick, message)
|
||||
else:
|
||||
yield from self.on_private_message(target, nick, message)
|
||||
await self.on_private_message(target, nick, message)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_quit(self, message):
|
||||
async def on_raw_quit(self, message):
|
||||
""" QUIT command. """
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
|
||||
|
@ -771,7 +719,7 @@ class RFC1459Support(BasicClient):
|
|||
else:
|
||||
reason = None
|
||||
|
||||
yield from self.on_quit(nick, reason)
|
||||
await self.on_quit(nick, reason)
|
||||
# Remove user from database.
|
||||
if not self.is_same_nick(self.nickname, nick):
|
||||
self._destroy_user(nick)
|
||||
|
@ -779,8 +727,7 @@ class RFC1459Support(BasicClient):
|
|||
elif self.connected:
|
||||
self.disconnect(expected=True)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_topic(self, message):
|
||||
async def on_raw_topic(self, message):
|
||||
""" TOPIC command. """
|
||||
setter, settermeta = self._parse_user(message.source)
|
||||
target, topic = message.params
|
||||
|
@ -793,20 +740,18 @@ class RFC1459Support(BasicClient):
|
|||
self.channels[target]['topic_by'] = setter
|
||||
self.channels[target]['topic_set'] = datetime.datetime.now()
|
||||
|
||||
yield from self.on_topic_change(target, topic, setter)
|
||||
|
||||
await self.on_topic_change(target, topic, setter)
|
||||
|
||||
## Numeric responses.
|
||||
|
||||
# Since RFC1459 specifies no specific banner message upon completion of registration,
|
||||
# take any of the below commands as an indication that registration succeeded.
|
||||
|
||||
on_raw_001 = _registration_completed # Welcome message.
|
||||
on_raw_002 = _registration_completed # Server host.
|
||||
on_raw_003 = _registration_completed # Server creation time.
|
||||
on_raw_001 = _registration_completed # Welcome message.
|
||||
on_raw_002 = _registration_completed # Server host.
|
||||
on_raw_003 = _registration_completed # Server creation time.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_004(self, message):
|
||||
async def on_raw_004(self, message):
|
||||
""" Basic server information. """
|
||||
target, hostname, ircd, user_modes, channel_modes = message.params[:5]
|
||||
|
||||
|
@ -814,19 +759,18 @@ class RFC1459Support(BasicClient):
|
|||
self._channel_modes = set(channel_modes)
|
||||
self._user_modes = set(user_modes)
|
||||
|
||||
on_raw_008 = _registration_completed # Server notice mask.
|
||||
on_raw_042 = _registration_completed # Unique client ID.
|
||||
on_raw_250 = _registration_completed # Connection statistics.
|
||||
on_raw_251 = _registration_completed # Amount of users online.
|
||||
on_raw_252 = _registration_completed # Amount of operators online.
|
||||
on_raw_253 = _registration_completed # Amount of unknown connections.
|
||||
on_raw_254 = _registration_completed # Amount of channels.
|
||||
on_raw_255 = _registration_completed # Amount of local users and servers.
|
||||
on_raw_265 = _registration_completed # Amount of local users.
|
||||
on_raw_266 = _registration_completed # Amount of global users.
|
||||
on_raw_008 = _registration_completed # Server notice mask.
|
||||
on_raw_042 = _registration_completed # Unique client ID.
|
||||
on_raw_250 = _registration_completed # Connection statistics.
|
||||
on_raw_251 = _registration_completed # Amount of users online.
|
||||
on_raw_252 = _registration_completed # Amount of operators online.
|
||||
on_raw_253 = _registration_completed # Amount of unknown connections.
|
||||
on_raw_254 = _registration_completed # Amount of channels.
|
||||
on_raw_255 = _registration_completed # Amount of local users and servers.
|
||||
on_raw_265 = _registration_completed # Amount of local users.
|
||||
on_raw_266 = _registration_completed # Amount of global users.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_301(self, message):
|
||||
async def on_raw_301(self, message):
|
||||
""" User is away. """
|
||||
target, nickname, message = message.params
|
||||
info = {
|
||||
|
@ -839,8 +783,7 @@ class RFC1459Support(BasicClient):
|
|||
if nickname in self._pending['whois']:
|
||||
self._whois_info[nickname].update(info)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_311(self, message):
|
||||
async def on_raw_311(self, message):
|
||||
""" WHOIS user info. """
|
||||
target, nickname, username, hostname, _, realname = message.params
|
||||
info = {
|
||||
|
@ -853,8 +796,7 @@ class RFC1459Support(BasicClient):
|
|||
if nickname in self._pending['whois']:
|
||||
self._whois_info[nickname].update(info)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_312(self, message):
|
||||
async def on_raw_312(self, message):
|
||||
""" WHOIS server info. """
|
||||
target, nickname, server, serverinfo = message.params
|
||||
info = {
|
||||
|
@ -867,8 +809,7 @@ class RFC1459Support(BasicClient):
|
|||
if nickname in self._pending['whowas']:
|
||||
self._whowas_info[nickname].update(info)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_313(self, message):
|
||||
async def on_raw_313(self, message):
|
||||
""" WHOIS operator info. """
|
||||
target, nickname = message.params[:2]
|
||||
info = {
|
||||
|
@ -878,8 +819,7 @@ class RFC1459Support(BasicClient):
|
|||
if nickname in self._pending['whois']:
|
||||
self._whois_info[nickname].update(info)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_314(self, message):
|
||||
async def on_raw_314(self, message):
|
||||
""" WHOWAS user info. """
|
||||
target, nickname, username, hostname, _, realname = message.params
|
||||
info = {
|
||||
|
@ -891,10 +831,9 @@ class RFC1459Support(BasicClient):
|
|||
if nickname in self._pending['whowas']:
|
||||
self._whowas_info[nickname].update(info)
|
||||
|
||||
on_raw_315 = BasicClient._ignored # End of /WHO list.
|
||||
on_raw_315 = BasicClient._ignored # End of /WHO list.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_317(self, message):
|
||||
async def on_raw_317(self, message):
|
||||
""" WHOIS idle time. """
|
||||
target, nickname, idle_time = message.params[:3]
|
||||
info = {
|
||||
|
@ -904,21 +843,19 @@ class RFC1459Support(BasicClient):
|
|||
if nickname in self._pending['whois']:
|
||||
self._whois_info[nickname].update(info)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_318(self, message):
|
||||
async def on_raw_318(self, message):
|
||||
""" End of /WHOIS list. """
|
||||
target, nickname = message.params[:2]
|
||||
target, nickname = message.params[:2]
|
||||
|
||||
# Mark future as done.
|
||||
if nickname in self._pending['whois']:
|
||||
future = self._pending['whois'].pop(nickname)
|
||||
future.set_result(self._whois_info[nickname])
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_319(self, message):
|
||||
async def on_raw_319(self, message):
|
||||
""" WHOIS active channels. """
|
||||
target, nickname, channels = message.params[:3]
|
||||
channels = { channel.lstrip() for channel in channels.strip().split(' ') }
|
||||
channels = {channel.lstrip() for channel in channels.strip().split(' ')}
|
||||
info = {
|
||||
'channels': channels
|
||||
}
|
||||
|
@ -926,8 +863,7 @@ class RFC1459Support(BasicClient):
|
|||
if nickname in self._pending['whois']:
|
||||
self._whois_info[nickname].update(info)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_324(self, message):
|
||||
async def on_raw_324(self, message):
|
||||
""" Channel mode. """
|
||||
target, channel = message.params[:2]
|
||||
modes = message.params[2:]
|
||||
|
@ -936,8 +872,7 @@ class RFC1459Support(BasicClient):
|
|||
|
||||
self.channels[channel]['modes'] = self._parse_channel_modes(channel, modes)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_329(self, message):
|
||||
async def on_raw_329(self, message):
|
||||
""" Channel creation time. """
|
||||
target, channel, timestamp = message.params
|
||||
if not self.in_channel(channel):
|
||||
|
@ -945,8 +880,7 @@ class RFC1459Support(BasicClient):
|
|||
|
||||
self.channels[channel]['created'] = datetime.datetime.fromtimestamp(int(timestamp))
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_332(self, message):
|
||||
async def on_raw_332(self, message):
|
||||
""" Current topic on channel join. """
|
||||
target, channel, topic = message.params
|
||||
if not self.in_channel(channel):
|
||||
|
@ -954,8 +888,7 @@ class RFC1459Support(BasicClient):
|
|||
|
||||
self.channels[channel]['topic'] = topic
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_333(self, message):
|
||||
async def on_raw_333(self, message):
|
||||
""" Topic setter and time on channel join. """
|
||||
target, channel, setter, timestamp = message.params
|
||||
if not self.in_channel(channel):
|
||||
|
@ -965,8 +898,7 @@ class RFC1459Support(BasicClient):
|
|||
self.channels[channel]['topic_by'] = self._parse_user(setter)[0]
|
||||
self.channels[channel]['topic_set'] = datetime.datetime.fromtimestamp(int(timestamp))
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_353(self, message):
|
||||
async def on_raw_353(self, message):
|
||||
""" Response to /NAMES. """
|
||||
target, visibility, channel, names = message.params
|
||||
if not self.in_channel(channel):
|
||||
|
@ -1004,29 +936,25 @@ class RFC1459Support(BasicClient):
|
|||
self.channels[channel]['modes'][status] = []
|
||||
self.channels[channel]['modes'][status].append(nick)
|
||||
|
||||
on_raw_366 = BasicClient._ignored # End of /NAMES list.
|
||||
on_raw_366 = BasicClient._ignored # End of /NAMES list.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_375(self, message):
|
||||
async def on_raw_375(self, message):
|
||||
""" Start message of the day. """
|
||||
self._registration_completed(message)
|
||||
self.motd = message.params[1] + '\n'
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_372(self, message):
|
||||
async def on_raw_372(self, message):
|
||||
""" Append message of the day. """
|
||||
self.motd += message.params[1] + '\n'
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_376(self, message):
|
||||
async def on_raw_376(self, message):
|
||||
""" End of message of the day. """
|
||||
self.motd += message.params[1] + '\n'
|
||||
|
||||
# MOTD is done, let's tell our bot the connection is ready.
|
||||
yield from self.on_connect()
|
||||
await self.on_connect()
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_401(self, message):
|
||||
async def on_raw_401(self, message):
|
||||
""" No such nick/channel. """
|
||||
nickname = message.params[1]
|
||||
|
||||
|
@ -1036,32 +964,27 @@ class RFC1459Support(BasicClient):
|
|||
future.set_result(None)
|
||||
del self._whois_info[nickname]
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_402(self, message):
|
||||
async def on_raw_402(self, message):
|
||||
""" No such server. """
|
||||
return (yield from self.on_raw_401(message))
|
||||
return (await self.on_raw_401(message))
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_422(self, message):
|
||||
async def on_raw_422(self, message):
|
||||
""" MOTD is missing. """
|
||||
self._registration_completed(message)
|
||||
await self._registration_completed(message)
|
||||
self.motd = None
|
||||
yield from self.on_connect()
|
||||
await self.on_connect()
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_421(self, message):
|
||||
async def on_raw_421(self, message):
|
||||
""" Server responded with 'unknown command'. """
|
||||
self.logger.warning('Server responded with "Unknown command: %s"', message.params[0])
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_432(self, message):
|
||||
async def on_raw_432(self, message):
|
||||
""" Erroneous nickname. """
|
||||
if not self.registered:
|
||||
# Nothing else we can do than try our next nickname.
|
||||
yield from self.on_raw_433(message)
|
||||
await self.on_raw_433(message)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_433(self, message):
|
||||
async def on_raw_433(self, message):
|
||||
""" Nickname in use. """
|
||||
if not self.registered:
|
||||
self._registration_attempts += 1
|
||||
|
@ -1069,17 +992,17 @@ class RFC1459Support(BasicClient):
|
|||
if self._attempt_nicknames:
|
||||
self.set_nickname(self._attempt_nicknames.pop(0))
|
||||
else:
|
||||
self.set_nickname(self._nicknames[0] + '_' * (self._registration_attempts - len(self._nicknames)))
|
||||
self.set_nickname(
|
||||
self._nicknames[0] + '_' * (self._registration_attempts - len(self._nicknames)))
|
||||
|
||||
on_raw_436 = BasicClient._ignored # Nickname collision, issued right before the server kills us.
|
||||
on_raw_436 = BasicClient._ignored # Nickname collision, issued right before the server kills us.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_451(self, message):
|
||||
async def on_raw_451(self, message):
|
||||
""" We have to register first before doing X. """
|
||||
self.logger.warning('Attempted to send non-registration command before being registered.')
|
||||
|
||||
on_raw_451 = BasicClient._ignored # You have to register first.
|
||||
on_raw_462 = BasicClient._ignored # You may not re-register.
|
||||
on_raw_451 = BasicClient._ignored # You have to register first.
|
||||
on_raw_462 = BasicClient._ignored # You may not re-register.
|
||||
|
||||
|
||||
## Helpers.
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
## tls.py
|
||||
# TLS support.
|
||||
from pydle import async
|
||||
import pydle.protocol
|
||||
from pydle.features import rfc1459
|
||||
from .. import connection
|
||||
|
@ -26,18 +25,16 @@ class TLSSupport(rfc1459.RFC1459Support):
|
|||
self.tls_client_cert_key = tls_client_cert_key
|
||||
self.tls_client_cert_password = tls_client_cert_password
|
||||
|
||||
@async.coroutine
|
||||
def connect(self, hostname=None, port=None, tls=False, **kwargs):
|
||||
async def connect(self, hostname=None, port=None, tls=False, **kwargs):
|
||||
""" Connect to a server, optionally over TLS. See pydle.features.RFC1459Support.connect for misc parameters. """
|
||||
if not port:
|
||||
if tls:
|
||||
port = DEFAULT_TLS_PORT
|
||||
else:
|
||||
port = rfc1459.protocol.DEFAULT_PORT
|
||||
return (yield from super().connect(hostname, port, tls=tls, **kwargs))
|
||||
return await super().connect(hostname, port, tls=tls, **kwargs)
|
||||
|
||||
@async.coroutine
|
||||
def _connect(self, hostname, port, reconnect=False, password=None, encoding=pydle.protocol.DEFAULT_ENCODING, channels=[], tls=False, tls_verify=False, source_address=None):
|
||||
async def _connect(self, hostname, port, reconnect=False, password=None, encoding=pydle.protocol.DEFAULT_ENCODING, channels=[], tls=False, tls_verify=False, source_address=None):
|
||||
""" Connect to IRC server, optionally over TLS. """
|
||||
self.password = password
|
||||
|
||||
|
@ -54,22 +51,20 @@ class TLSSupport(rfc1459.RFC1459Support):
|
|||
self.encoding = encoding
|
||||
|
||||
# Connect.
|
||||
yield from self.connection.connect()
|
||||
await self.connection.connect()
|
||||
|
||||
|
||||
## API.
|
||||
|
||||
@async.coroutine
|
||||
def whois(self, nickname):
|
||||
info = yield from super().whois(nickname)
|
||||
async def whois(self, nickname):
|
||||
info = await super().whois(nickname)
|
||||
info.setdefault('secure', False)
|
||||
return info
|
||||
|
||||
|
||||
## Message callbacks.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_671(self, message):
|
||||
async def on_raw_671(self, message):
|
||||
""" WHOIS: user is connected securely. """
|
||||
target, nickname = message.params[:2]
|
||||
info = {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
## whox.py
|
||||
# WHOX support.
|
||||
from pydle import async
|
||||
from pydle.features import isupport, account
|
||||
|
||||
NO_ACCOUNT = '0'
|
||||
|
@ -11,10 +10,9 @@ class WHOXSupport(isupport.ISUPPORTSupport, account.AccountSupport):
|
|||
|
||||
## Overrides.
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_join(self, message):
|
||||
async def on_raw_join(self, message):
|
||||
""" Override JOIN to send WHOX. """
|
||||
yield from super().on_raw_join(message)
|
||||
await super().on_raw_join(message)
|
||||
nick, metadata = self._parse_user(message.source)
|
||||
channels = message.params[0].split(',')
|
||||
|
||||
|
@ -22,7 +20,7 @@ class WHOXSupport(isupport.ISUPPORTSupport, account.AccountSupport):
|
|||
# We joined.
|
||||
if 'WHOX' in self._isupport and self._isupport['WHOX']:
|
||||
# Get more relevant channel info thanks to WHOX.
|
||||
yield from self.rawmsg('WHO', ','.join(channels), '%tnurha,{id}'.format(id=WHOX_IDENTIFIER))
|
||||
await self.rawmsg('WHO', ','.join(channels), '%tnurha,{id}'.format(id=WHOX_IDENTIFIER))
|
||||
else:
|
||||
# Find account name of person.
|
||||
pass
|
||||
|
@ -32,8 +30,7 @@ class WHOXSupport(isupport.ISUPPORTSupport, account.AccountSupport):
|
|||
if self.registered and 'WHOX' not in self._isupport:
|
||||
self.whois(nickname)
|
||||
|
||||
@async.coroutine
|
||||
def on_raw_354(self, message):
|
||||
async def on_raw_354(self, message):
|
||||
""" WHOX results have arrived. """
|
||||
# Is the message for us?
|
||||
target, identifier = message.params[:2]
|
||||
|
|
|
@ -8,9 +8,9 @@ import logging
|
|||
import asyncio
|
||||
from asyncio.streams import FlowControlMixin
|
||||
|
||||
from .. import async, Client, __version__
|
||||
from .. import Client, __version__
|
||||
from . import _args
|
||||
|
||||
import asyncio
|
||||
|
||||
class IRCCat(Client):
|
||||
""" irccat. Takes raw messages on stdin, dumps raw messages to stdout. Life has never been easier. """
|
||||
|
@ -18,12 +18,12 @@ class IRCCat(Client):
|
|||
super().__init__(*args, **kwargs)
|
||||
self.async_stdin = None
|
||||
|
||||
@async.coroutine
|
||||
@asyncio.coroutine
|
||||
def _send(self, data):
|
||||
sys.stdout.write(data)
|
||||
yield from super()._send(data)
|
||||
|
||||
@async.coroutine
|
||||
@asyncio.coroutine
|
||||
def process_stdin(self):
|
||||
""" Yes. """
|
||||
loop = self.eventloop.loop
|
||||
|
@ -40,12 +40,12 @@ class IRCCat(Client):
|
|||
|
||||
yield from self.quit('EOF')
|
||||
|
||||
@async.coroutine
|
||||
@asyncio.coroutine
|
||||
def on_raw(self, message):
|
||||
print(message._raw)
|
||||
yield from super().on_raw(message)
|
||||
|
||||
@async.coroutine
|
||||
@asyncio.coroutine
|
||||
def on_ctcp_version(self, source, target, contents):
|
||||
self.ctcp_reply(source, 'VERSION', 'pydle-irccat v{}'.format(__version__))
|
||||
|
||||
|
|
Loading…
Reference in New Issue