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) | 
