#!/usr/bin/python3
# Copyright (c) 2016-2017, henry232323
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
"""
A small simple port of aioyoyo to use asyncio instead of its original
threading client. Creating an IRCClient instance will create the protocol
instance.
To start the connection run IRCClient.connect(); (coroutine)
"""
import asyncio
import logging
from .oyoyo.parse import parse_raw_irc_command
from .protocol import ClientProtocol
from aioyoyo.oyoyo.cmdhandler import IRCClientError
[docs]class IRCClient(object):
def __init__(self, loop=None, address=None, port=None, protocol=ClientProtocol):
"""
A basic Async IRC client. Use coroutine IRCClient.connect to initiate
the connection. Takes the event loop, a host (address, port) and if
wanted an alternate protocol can be defined. By default will use the
ClientProtocol class, which just uses the IRCClient's tracebacks and
passes received data to the client.
"""
self.loop = loop if loop else asyncio.get_event_loop()
self.host = (address, port)
self.address = address
self.port = port
self.protocol = protocol(self)
self.logger = logging.getLogger("aioyoyo")
self.logger.setLevel(logging.INFO)
def __repr__(self):
return "{0}(address={1}, port={2}, protocol={3})".format(self.__class__.__name__, self.host[0], self.host[1], self.protocol.__class__.__name__)
[docs] async def connect(self):
"""Initiate the connection, creates a connection using the defined
protocol"""
await self.loop.create_connection(lambda: self.protocol, self.address, self.port)
[docs] async def connection_made(self):
"""Called on a successful connection, by default forwarded by
protocol.connection_made"""
logging.info('connecting to %s:%s' % self.host)
[docs] async def data_received(self, data):
"""Called when data is received by the connection, by default
forwarded by protocol.data_received, passes bytes not str"""
logging.info('received: %s' % data.decode())
[docs] async def connection_lost(self, exc):
"""Called when the connection is dropped, by default prints
the exception if there is one. Forwarded by protocol.connection_lost"""
logging.info('connection lost: %s' % exc)
[docs] async def send(self, *args):
"""Send a message to the connected server. all arguments are joined
with a space for convenience, for example the following are identical
>>> cli.send("JOIN %s" % some_room)
>>> cli.send("JOIN", some_room)
In python 3, all args must be of type str or bytes, *BUT* if they are
str they will be converted to bytes with the encoding specified by the
'encoding' keyword argument (default 'utf8').
"""
# Convert all args to bytes if not already
bargs = []
for arg in args:
if isinstance(arg, str):
bargs.append(arg.encode())
elif isinstance(arg, bytes):
bargs.append(arg)
else:
raise IRCClientError('Refusing to send one of the args from provided: %s'
% repr([(type(arg), arg) for arg in args]))
msg = b" ".join(bargs)
await self.protocol.send_raw(msg + b"\r\n")
logging.info('---> send "%s"' % msg)
[docs] async def send_msg(self, message):
"""Send a str to the server from absolute raw, none of the formatting
from IRCClient.send"""
await self.protocol.send(message)
[docs] async def send_raw(self, data):
"""Send raw bytes to the server, none of the formatting from IRCClient.send"""
await self.protocol.send_raw(data)
[docs] async def close(self):
"""Close the connection"""
logging.info('close transport')
self.protocol.transport.close()
[docs] def run(self):
"""Starts the client, blocking. For a non-blocking coroutine use client.connect()"""
self.loop.run_until_complete(self.connect())
[docs]class CommandClient(IRCClient):
"""IRCClient, using a command handler"""
def __init__(self, cmd_handler, **kwargs):
"""Takes a command handler (see oyoyo.cmdhandler.CommandHandler)
whose attributes are the commands you want callable, for example
with a privmsg cmdhandler.privmsg will be awaited with the
appropriate *args, decorate methods with @protected to make it
uncallable as a command"""
IRCClient.__init__(self, **kwargs)
self.command_handler = cmd_handler(self)
[docs] async def data_received(self, data):
"""On IRCClient.data_received parse for a command and pass to the
command_handler to run()"""
prefix, command, args = parse_raw_irc_command(data)
await self.command_handler.run(command, prefix, *args)