Code Monkey home page Code Monkey logo

bottom's People

Contributors

amorporkian avatar dependabot[bot] avatar doc-hex avatar fahhem avatar gbw avatar johanlorenzo avatar lalitmaganti avatar larsks avatar miedzinski avatar nedbat avatar numberoverzero avatar yay295 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

bottom's Issues

Dispatch blocks, incorrect async

After unpacking, the reading loop will yield from the event dispatch, which means it waits until all handlers run for the event, before continuing.

Instead, events.trigger should be a non-blocking call. That is, it should push the event into a queue, then return execution to the caller.


I'm working on accordian to fix this - however, in trying to use the 3.5 syntax (await and async as keywords) I've run into other problems. Once I've finished testing the library, I'll integrate it into bottom. This will require 3.5+ to use bottom, but the migration from 3.4 (current requirement) to 3.5 is virtually painless (I haven't seen any reports of problems migrating).

Explain RFC deviations in docs

#45 dropped the server1/server2 params of ping/pong since the spec is ambiguous and existing implementations didn't clearly map to one interpretation. That needs to be called out clearly and in an easy to find location. Probably part of any doc rewrite for #43.

Client API - should connect/disconnect be coroutines?

I've decided to revert the change that made connect/disconnect synchronous but non-blocking, and will again make them coroutines. I expected this to be a tough decision, but after writing out the examples below it seems rather straightforward.

I think the examples are valuable enough for future discussions about the API to keep the issue around, even though I intend to implement the coroutine version immediately.

Waiting for connection to establish after connect()

The coroutine form is the clear winner here, with an explicit "wait until this happens before executing the rest of the body". Contrasting, the other form requires setting an asyncio.Event in a different function and awaiting it in the place we care about it. If any other function can clear/set that event, things become spaghetti quickly.

As a coroutine

@client.on("client_disconnect")
def reconnect(**kwargs):
    await asyncio.sleep(3, loop=client.loop)
    await client.connect()
    client.trigger("reconnect.complete")

Non-blocking non-coroutine

There are a few ways to solve this, but let's use an asyncio.Event for now.

conn_established = asyncio.Event(loop=client.loop)


@client.on("client_connect")
def _mark_connect_complete(**kwargs):
    conn_established.set()


@client.on("client_disconnect")
def reconnect(**kwargs):
    await asyncio.sleep(3, loop=client.loop)
    conn_established.clear()
    client.connect()
    await conn_established.wait()
    client.trigger("reconnect.complete")

Schedule connection without waiting for it to complete

The non-coroutine form wins here, but I don't think it's by much. Because it's so easy to schedule coroutines for an event loop, and Client exposes that loop, it's barely more code for the coroutine version.

As a coroutine

@client.on("client_disconnect")
def reconnect(**kwargs):
    client.loop.create_task(client.connect())
    print("I print immediately")

Non-blocking non-coroutine

@client.on("client_disconnect")
def reconnect(**kwargs):
    client.connect()
    print("I print immediately")

Example fails to run on python 3.10

It appears that the asyncio API has changed in python 3.10, causing Bottom to exception.

Python 3.10.1 (main, Dec 11 2021, 17:22:55) [GCC 11.1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import bottom
>>> import asyncio
>>> host = 'chat.freenode.net'
>>> port = 6697
>>> ssl = True
>>> NICK = "bottom-bot"
>>> CHANNEL = "#bottom-dev"
>>> bot = bottom.Client(host=host, port=port, ssl=ssl)
>>> bot.loop.create_task(bot.connect())
<Task pending name='Task-1' coro=<RawClient.connect() running at /home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py:69>>
>>> bot.loop.run_forever()
Task exception was never retrieved
future: <Task finished name='Task-2' coro=<process() done, defined at /home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py:12> exception=TypeError('As of 3.10, the *loop* parameter was removed from Event() since it is no longer necessary')>
Traceback (most recent call last):
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 28, in process
    await next_handler(message)
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 26, in next_handler
    await handler(next_handler, message)
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 191, in handler
    client.trigger(event, **kwargs)
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 102, in trigger
    async_event = self._events[event]
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 53, in <lambda>
    lambda: asyncio.Event(loop=self.loop))
  File "/usr/lib/python3.10/asyncio/locks.py", line 167, in __init__
    super().__init__(loop=loop)
  File "/usr/lib/python3.10/asyncio/mixins.py", line 17, in __init__
    raise TypeError(
TypeError: As of 3.10, the *loop* parameter was removed from Event() since it is no longer necessary
Task exception was never retrieved
future: <Task finished name='Task-3' coro=<process() done, defined at /home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py:12> exception=TypeError('As of 3.10, the *loop* parameter was removed from Event() since it is no longer necessary')>
Traceback (most recent call last):
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 28, in process
    await next_handler(message)
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 26, in next_handler
    await handler(next_handler, message)
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 191, in handler
    client.trigger(event, **kwargs)
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 102, in trigger
    async_event = self._events[event]
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 53, in <lambda>
    lambda: asyncio.Event(loop=self.loop))
  File "/usr/lib/python3.10/asyncio/locks.py", line 167, in __init__
    super().__init__(loop=loop)
  File "/usr/lib/python3.10/asyncio/mixins.py", line 17, in __init__
    raise TypeError(
TypeError: As of 3.10, the *loop* parameter was removed from Event() since it is no longer necessary
Task exception was never retrieved
future: <Task finished name='Task-4' coro=<process() done, defined at /home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py:12> exception=TypeError('As of 3.10, the *loop* parameter was removed from Event() since it is no longer necessary')>
Traceback (most recent call last):
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 28, in process
    await next_handler(message)
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 26, in next_handler
    await handler(next_handler, message)
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 191, in handler
    client.trigger(event, **kwargs)
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 102, in trigger
    async_event = self._events[event]
  File "/home/uriah/git/urwbot/.venv/lib/python3.10/site-packages/bottom/client.py", line 53, in <lambda>
    lambda: asyncio.Event(loop=self.loop))
  File "/usr/lib/python3.10/asyncio/locks.py", line 167, in __init__
    super().__init__(loop=loop)
  File "/usr/lib/python3.10/asyncio/mixins.py", line 17, in __init__
    raise TypeError(
TypeError: As of 3.10, the *loop* parameter was removed from Event() since it is no longer necessary
^CTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.10/asyncio/base_events.py", line 595, in run_forever
    self._run_once()
  File "/usr/lib/python3.10/asyncio/base_events.py", line 1845, in _run_once
    event_list = self._selector.select(timeout)
  File "/usr/lib/python3.10/selectors.py", line 469, in select
    fd_event_list = self._selector.poll(timeout, max_ev)
KeyboardInterrupt

partial_bind fails for bound methods

partial_bind is failing to preserve self when handed a bound method, probably because the signature is inspected after wrapping non-coroutines. Inspecting first should take care of the problem.

minimal repro:

class Object:
    def handle(self, host, port):
        print("Connected: {} {}".format(host, port))
obj = Object()
bound_method = obj.handle

import asnycio
import bottom
client = bottom.Client("localhost", "8080")
client.on("client_connect")(bound_method)
# Throws when connecting because Object.handle gets params "host" and "port" but not "self"
asyncio.get_event_loop().run_until_complete(client.run())

collections.abc error

collections.abc was moved to a separate module in 3.3 rather than being the abc attribute of the collections module. Therefore it needs to be imported in pack.py else an AttributeError is raised when packing iterables.

Feature Request: send raw commands

Add a "raw" command type. such as:
bot.send('RAW', message='<server specific command / non-rfc2812 command here>')
Have it sort of a "use at your own peril" sort of thing (meaning there would be no error handling, just dump the raw error message out).

IE: pack.py:

elif command == "RAW":
    return "{}".format(f("message", kwargs))

I totally understand the concept that this would circumvent the whole "doing commands as routes",, but this could make it simpler for those of use that have extended upon the original library for functionality of non-standardized operations within a specific IRC server.

Feature Request: add Blowfish adapter to User Guide

This request is from an external source, not sure if they have a github account. It should be possible either with the existing event handlers, or with the addition of a raw handler (see #32). I haven't personally used Blowfish with IRC, but it looks neat. Extract of some comments that could get this started:

most every channel i am in however uses blowfish. for mIRC people use FISH10. [...] is there any easy way to implement decryption of blowfish messages in channels? these channels have fixed blowfish keys..

[...] each channel has it's own blowfish key. to know if the message is encrypted it starts with: +OK which signifies any text following is encrypted with the fish key for the channel, the same key must be used to decrypt the encrypted text. so encrypt/decrypt is always the same value/key for a particular channel.

[...] an implementation for irc and blowfish in python. the only important piece for me is: "blowcrypt, Fish etc" [...].
http://www.bjrn.se/code/irccrypt/irccrypt.py

you have to decrypt whatever comes after "+OK " which is the first 4 bytes of the PRIVMSG with the fish key for the channel. there are unique keys for nicknames which are automatically derived with the DH1080 key exchange between the bot and the user.

Many Non-numeric events not being parsed

It would be nice to be able to @client.on('MODE') for catching user and channel mode changes.
EDIT: it looks like TOPIC is also not an event.
EDIT 2: It looks like a lot of the RFC Non-numeric events are being missed, (KICK is on the list as well)

Support aliasing irc commands

(first proposed in #29)

Following the rfc2812 spec leaves some particular warts, especially when it comes to the historical command names like RPL_WHOREPLY and RPL_NAMREPLY.

There should be an easy way to specify a set of aliases when hooking into the rfc commands, so that reading the code is more natural.

This isn't the intended implementation, just a quick example of how the bot.on would hook up for some alias:

@bot.on("who_response")
def handle(...):
    ...

bot.aliases["RPL_WHOREPLY"].add("who_response")

I don't know if there's a risk of confusing a custom event for an irc command, but I think that's always been possible since there's no restriction on what the client can trigger. Worth considering but probably not an issue.

Tests use default event loop

Similar to accordian we should be creating a new event loop per test.

This will be a larger part of the loop refactor that needs to occur for the main components: Client and Connection.

IRC is not connecting to freenode.

The client is not connecting to freenode. Here's my code:

import asyncio
import bottom

client = bottom.Client(host="chat.freenode.net", port=6697, ssl=True))

@client.on("ping")
def handle(message=None, **kwargs):
    message = message or ""
    client.send("pong", message=message)


@client.on("client_disconnect")
async def reconnect(**kwargs):
    await asyncio.sleep(2, loop=client.loop)

    client.loop.create_task(client.connect())


def waiter(client):
    async def wait_for(*events, return_when=asyncio.FIRST_COMPLETED):
        if not events:
            return
        done, pending = await asyncio.wait(
            [client.wait(event) for event in events],
            loop=client.loop,
            return_when=return_when,
        )

        # Get the result(s) of the completed task(s).
        ret = [future.result() for future in done]

        # Cancel any events that didn't come in.
        for future in pending:
            future.cancel()

        # Return list of completed event names.
        return ret

    return wait_for


# taken from :ref:`Patterns`
wait_for = waiter(client)


@client.on("client_connect")
async def connect(**kwargs):
    client.default_name = "qpowieurtyturiewqop"
    client.send("nick", nick=client.default_name)
    client.send("user", user=client.default_name, realname=client.default_name)

    events = await wait_for("rpl_endofmotd", "err_nomotd") # it gets stuck at this line
    print("Connected")

    client.send("join", channel="#channel")

client.loop.create_task(client.connect())
client.loop.run_forever()

I tried this on another PC and it worked.

Possibility of Python 2 support

Hey!

Just wanted to discuss if you would be willing to support Python 2 on a different branch? One of the projects I'm working on has a hard dependency on a project which is Python 2 only and there are few alternatives to this library.

And I don't want to replace Bottom with another library either as the terseness of it is very appealing.

I would be willing to maintain it as on a "best effort basis" as you make changes to the Python 3 branch. Or if you prefer, I could maintain it as a separate project (called "Top" :P).

Assertion that transport is an instance of asyncio.WriteTransport is not needed

Bottom's protocol makes an assertion that the transport is an instance of asyncio.WriteTransport (see https://github.com/numberoverzero/bottom/blob/master/bottom/protocol.py#L24 )

This assertion isn't compatible with uvloop library - an alternative event loop with better performance ( https://github.com/MagicStack/uvloop )

Is there a good reason to keep the assertion? I tried to remove it and my program works as expected. Thanks in advance for your help.

ImportError: cannot import name 'TYPE_CHECKING'

Python 3.5.1 (v3.5.1:37a07cee5969, Dec  6 2015, 01:54:25) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import bottom
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\Admin\AppData\Local\Programs\Python\Python35\lib\site-packages\bottom\__init__.py", line 2, in <module>
    from bottom.client import Client
  File "C:\Users\Admin\AppData\Local\Programs\Python\Python35\lib\site-packages\bottom\client.py", line 5, in <module>
    from bottom.protocol import Protocol
  File "C:\Users\Admin\AppData\Local\Programs\Python\Python35\lib\site-packages\bottom\protocol.py", line 4, in <module>
    from typing import Optional, TYPE_CHECKING
ImportError: cannot import name 'TYPE_CHECKING'

Just installed it, typed in import bottom and it just errors out.

Running on Python version 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:54:25) [MSC v.1900 64 bit (AMD64)], and on a Windows-7-6.1.7601-SP1 machine.

Allow Sending RAW Commands via bot.send()

Currently, bot.send() interprets a list of pre-coded IRC commands and extends them to raw. It is not possible to send raw command directly to a IRC server, rendering the library less useful to work with different non-standard commands without modifying or monkey-patching bottom.

For example, a vast majority of IRCv3 commands is an extension to RFC-2812.

Could you please add a way to send raw commands?

Example on README isn't working

the example posted on the README:

import bottom

NICK = 'bottom-bot'
CHANNEL = '#python'

bot = bottom.Client('localhost', 6697)


@bot.on('CLIENT_CONNECT')
def connect(**kwargs):
    bot.send('NICK', nick=NICK)
    bot.send('USER', user=NICK, realname='Bot using bottom.py')
    bot.send('JOIN', channel=CHANNEL)


@bot.on('PING')
def keepalive(message, **kwargs):
    bot.send('PONG', message=message)


@bot.on('PRIVMSG')
def message(nick, target, message, **kwargs):
    """ Echo all messages """

    # Don't echo ourselves
    if nick == NICK:
        return
    # Respond directly to direct messages
    if target == NICK:
        bot.send("PRIVMSG", target=nick, message=message)
    # Channel message
    else:
        bot.send("PRIVMSG", target=target, message=message)

# This schedules a connection to be created when the bot's event loop
# is run.  Nothing will happen until the loop starts running to clear
# the pending coroutines.
bot.loop.create_task(bot.connect())

# Ctrl + C to quit
bot.loop.run_forever()

is giving me this error:

Traceback (most recent call last):
  File "bottom_test.py3", line 38, in <module>
    bot.loop.create_task(bot.connect())
AttributeError: 'Client' object has no attribute 'loop'

Running python 3.5.1, bottom==0.9.13.

Use a different nickname on any message

Hello, I am making a Discord bridge for my IRC server. Since I have only 1 bot, I thought I could change the bot's nick to the person on Discord who sent that message. Though, I can't, because it will show MyIRCBot is now known as Name, and that's a big problem. Is there a way to change the nickname without showing that message and then send the message?

Correctly unpack parameters with optional delimiter

It is entirely valid for a server to use a trailing delimiter in the response parameters when one is not required. (source)

That means the following are functionally equivalent:

:nick!user@host JOIN #channel
:nick!user@host JOIN :#channel

Bot doesn't connect

I'm using Python 3.5.1, and trying to use the example echo.py on a variety of efnet servers. I set ssl to False. I tested on both OSX and Debian. I added some debug statements like this:

@bot.on('CLIENT_CONNECT')
def connect(**kwargs):
    print(kwargs)

    print('in connect sending NICK')
    bot.send('NICK', nick=config.nick)

    print('sending USER')
    bot.send('USER', user=config.nick, realname=config.real_name)
    print('sending JOIN')
    bot.send('JOIN', channel=config.channel)
    print('sent JOIN')

And the output is:

in connect sending NICK
sending USER
sending JOIN
sent JOIN

However, the bot appears to hang at that point. I don't see it join my channel nor do I see it connected to the server at all. Let me know if there is any other info I can provide to help debug. Thanks!

Connect to multiple servers ?

Is there anyway to connect to multiple servers?.

Something similar to a main function that would launch each client instance that are in multiple files?

Ex: lets i have , Freenode.py for freenoed and libera.py for libera.chat.

Currently I run each one of them on their own screen session?
Is there anyway I could just run a single main.py to run both the clients? (without something like subprocess)

Add an event for nickname change

Sometimes a server might change a user's nickname. Right now I have no way of telling if the nickname was changed.

I've used this to print each and every event I get:

def handle_logging(session):
    for event in [
        'CLIENT_CONNECT',
        'JOIN',
        'NOTICE',
        'PART',
        'PING',
        'PRIVMSG',
        'RPL_BOUNCE',
        'RPL_CREATED',
        'RPL_ENDOFMOTD',
        'RPL_LUSERCHANNELS',
        'RPL_LUSERCLIENT',
        'RPL_LUSERME',
        'RPL_LUSEROP',
        'RPL_LUSERUNKNOWN',
        'RPL_MOTD',
        'RPL_MOTDSTART',
        'RPL_MYINFO',
        'RPL_WELCOME',
        'RPL_YOURHOST',
    ]:
        session.bot.on(
            event,
            lambda **kwargs: print(
                '{}: {} {}'.format(
                    arrow.utcnow(),
                    event,
                    kwargs,
                )))

None of the events were a nickname change event, just a RPL_YOURHOST warning me that I have 30 seconds to change my nickname and 30 seconds later RPL_YOURHOST telling me that it'll change my nickname. Nothing standard.

Many thanks!

Investigate asyncio's ssl handling

source

self.reader, self.writer = yield from asyncio.open_connection(
self.host, self.port, ssl=self.ssl)

It's worth checking out asyncio.open_connection to see what ssl=True does, and what additional settings are available (eg. certificate validation) to harden against common attacks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.