numberoverzero / bottom

asyncio-based rfc2812-compliant IRC Client
http://bottom-docs.readthedocs.io
MIT License
74 stars 23 forks source link

Bot doesn't connect #28

Closed jiujitsu closed 8 years ago

jiujitsu commented 8 years ago

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!

numberoverzero commented 8 years ago

What endpoint are you trying to connect to? chat.freenode.net:6697 will fail with Broken Pipe if ssl=False, but chat.freenode.net:6667 will work with ssl=False.

As an aside, you can get more asyncio debugging information by passing an event loop with debug enabled:

import asyncio
loop = asyncio.get_event_loop()
loop.set_debug(True)

bot = bottom.Client(..., loop=loop)

Test script:

import asyncio
import bottom
import logging

logging.basicConfig(level=logging.DEBUG)
config = type("Config", (object,), {})()

config.nick = "bottom-bot"
config.real_name = "bottom-real"
config.channel = "#bottom-dev"

config.host = "chat.freenode.net"
config.port = 6667
config.ssl = False
config.loop = asyncio.get_event_loop()
config.loop.set_debug(True)

bot = bottom.Client(
    host=config.host, port=config.port,
    ssl=config.ssl, loop=config.loop)

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

bot.loop.create_task(bot.connect())
bot.loop.run_forever()

This is connecting to 6667 without ssl - to enable ssl, use:

config.ssl = True
config.port = 6697
jiujitsu commented 8 years ago

Aha! I was using the ssl port on a non ssl server. Once I got past that, I ran into another issue that might be worth documenting. Before sending the join, I had to do await bot.wait('PING'), and change the connect function to be async. This may just be an efnet thing, where it will say you aren't registered if you try to send the join command before responding to the ping. For reference, I the server was: efnet.port80.se.

Anywhoo, I am closing this ticket. Thanks for the help! Showing me how to enable debugging was also very useful.

thebigmunch commented 7 years ago

I forgot to respond to this issue a while back. The solution of waiting for the server 'PING' is not ideal nor does it reveal the direct cause. The cause is that the examples given send the 'JOIN' command before the server has processed your 'NICK' and 'USER' commands. Also, servers aren't necessarily going to be sending a 'PING' to clients for some time after they connect.

Technically, you should be able to wait for any message from the server starting at 'RPL_WELCOME'.

You might also choose to wait for the end of the message of the day ('RPL_ENDOFMOTD')/no message of the day ('ERR_NOMOTD') or the server to send the user modes message 'MODE'. Waiting for 'MODE' would require support be added for it to bottom.

The examples should probably be changed to include waiting for a server response before sending the 'JOIN' command to demonstrate best practices.

numberoverzero commented 7 years ago

This is really good info, thanks @thebigmunch!

I think jiujitsu's comment above is the only mention of waiting for PING, right? I don't see any waiting in echo.py or README.rst.

I'd like to add something to examples/ that demonstrates sending NICK, USER and then waiting for <SOMETHING> before sending JOIN. For a general sample, what would you recommend waiting on? If the usage is tucked away in an example, the README should have some way to discover it. Where would you look in the README if you were searching for this solution?


Your comment also hit on something not covered in the examples: how to wait for one of two signals. This is just using asyncio.wait(..., return_when=FIRST_COMPLETED) but it's not a form people may be used to using, especially just coming to asyncio.

Actually, waiting on first of RPL_ENDOFMOTD, ERR_NOMOTD might be a good way to check off both goals. It could also include how to drop the other Future with `Future.cancel().

thebigmunch commented 7 years ago

Yes, I the first part of my comment was in regards to jiujitsu's solution; all of the current examples just send 'JOIN' immediately after 'NICK' and 'USER' commands.

Honestly, the README is far too long in my opinion. Much of it should just be left for the docs. Project-related stuff like installation, contributing, etc is great in the README. The single 'Getting Started' section with a simple example is fine, too. And that should probably have a wait added to it. A long, crowded README is more likely to get skipped over/ignored. You can then just link to the examples directory or docs (if adding the examples to the docs).

As for what to wait on, message of the day is definitely a fine choice. I honestly don't know what most clients do, but I think that sounds good.

numberoverzero commented 7 years ago

Thanks again for the feedback. Examples now await on MOTD. I've also trimmed the README down and cleaned up the docs a bit. If you don't mind taking a look, I'll add you to the PR.

Edit: @thebigmunch created #33 for the doc changes. Unless you have comments, I'll merge + release tonight or tomorrow.

meshy commented 7 years ago

There is an interesting parallel between d5425da and https://github.com/gawel/irc3/issues/14.

That issue on irc3 lists waiting for MOTD as a bug because it causes errors when connecting to bouncers. Perhaps it's worth adding a third wait to cover that circumstance? I don't personally use a bouncer, so I'm not exactly sure what to recommend, but PRIVMSG springs to mind. Perhaps there is something more appropriate. I hope that's helpful :)

meshy commented 7 years ago

Just saw your edit and #33. Would you like me to move my comment to there?

numberoverzero commented 7 years ago

Please