diff options
author | Tomasz Kramkowski <tk@the-tk.com> | 2021-06-16 00:56:55 +0100 |
---|---|---|
committer | Tomasz Kramkowski <tk@the-tk.com> | 2021-06-16 00:56:55 +0100 |
commit | d30ffb7b1490eb12feb6041be84c44d115f22b0c (patch) | |
tree | 2a038cd7555684af7afa4bfeeff06b8fff3cbdce | |
download | columbo-master.tar.gz columbo-master.tar.xz columbo-master.zip |
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | columbo/__init__.py | 24 | ||||
-rw-r--r-- | columbo/irc_client.py | 70 |
3 files changed, 95 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/columbo/__init__.py b/columbo/__init__.py new file mode 100644 index 0000000..6318e6d --- /dev/null +++ b/columbo/__init__.py @@ -0,0 +1,24 @@ +from . import irc_client +from dataclasses import dataclass +import irctokens +import trio + +@dataclass +class Config: + clients: list[irc_client.Config] + +async def start(config): + async with trio.open_nursery() as nursery: + channel, inbox = trio.open_memory_channel(0) + for client in config.clients: + nursery.start_soon(irc_client.connect, client, channel) + async for source, line in inbox: + outbox, client = source + print(f'Got line: {line.format()}') + reply_target = None + if line.params[0] == client.current_nick: + reply_target = line.hostmask.nickname + elif line.params[1].startswith(','): + reply_target = line.params[0] + if reply_target: + await outbox.send(irctokens.build('PRIVMSG', [reply_target, f'Hi {line.hostmask.nickname}'])) diff --git a/columbo/irc_client.py b/columbo/irc_client.py new file mode 100644 index 0000000..e227941 --- /dev/null +++ b/columbo/irc_client.py @@ -0,0 +1,70 @@ +from base64 import b64encode +from dataclasses import dataclass +from irctokens import StatefulDecoder, build +from typing import Optional +import trio + +@dataclass +class Config: + host: str + nick: str + password: str + port: int = 6667 + leader: str = ',' + user: str = 'columbo' + channels: Optional[list[str]] = None + realname: str = 'columbo-irc_client v0.1' + current_nick: Optional[str] = None + encoding: str = 'utf-8' + +async def _sender(stream, config, inbox): + def prepare(line): + print(f'>{line}') + return f'{line.format()}\r\n'.encode(config.encoding) + config.current_nick = config.nick + preamble = [ + ('CAP', ['REQ', 'sasl']), + ('USER', [config.user, '0', '*', config.realname]), + ('NICK', [config.nick]), + ] + for args in preamble: + await stream.send_all(prepare(build(*args))) + async with inbox: + async for line in inbox: + await stream.send_all(prepare(line)) + +async def _handle(config, channel, outbox, line): + print(f'<{line}') + if line.command == 'PING': + await outbox.send(build('PONG', [line.params[0]])) + elif line.command == 'CAP': + if line.params[1] == 'ACK' and line.params[2] == 'sasl': + await outbox.send(build('AUTHENTICATE', ['PLAIN'])) + elif line.command == 'AUTHENTICATE': + if line.params[0] == '+': + creds = b64encode(f'\0{config.nick}\0{config.password}'.encode(config.encoding)).decode() + await outbox.send(build('AUTHENTICATE', [creds])) + elif line.command == '900': # RPL_LOGGEDIN + await outbox.send(build('CAP', ['END'])) + elif line.command == '001': # RPL_WELCOME + config.current_nick = line.params[0] + if config.channels: + for channel in config.channels: + await outbox.send(build('JOIN', [channel])) + elif line.command == 'PRIVMSG': + await channel.send(((outbox, config), line)) + +async def _receiver(stream, config, channel, outbox): + decoder = StatefulDecoder() + async with channel, outbox: + async for data in stream: + for line in decoder.push(data): + await _handle(config, channel, outbox, line) + +async def connect(config, channel): + stream = await trio.open_tcp_stream(config.host, config.port) + outbox_send, outbox_recv = trio.open_memory_channel(0) + async with stream: + async with trio.open_nursery() as nursery: + nursery.start_soon(_sender, stream, config, outbox_recv) + nursery.start_soon(_receiver, stream, config, channel, outbox_send) |