简体   繁体   English

与Slack bot的Python asyncio

[英]Python asyncio with Slack bot

I'm trying to make a simple Slack bot using asyncio, largely using the example here for the asyncio part and here for the Slack bot part. 我正在尝试使用asyncio制作一个简单的Slack机器人,主要使用此处的示例用于asyncio部分, 此处用于Slack机器人部分。

Both the examples work on their own, but when I put them together it seems my loop doesn't loop: it goes through once and then dies. 这两个例子都是自己工作的,但是当我把它们放在一起时,似乎我的循环没有循环:它经过一次然后死掉。 If info is a list of length equal to 1, which happens when a message is typed in a chat room with the bot in it, the coroutine is supposed to be triggered, but it never is. 如果info是一个长度等于1的列表,当在其中包含机器人的聊天室中键入消息时,会发生协程,但它永远不会被触发。 (All the coroutine is trying to do right now is print the message, and if the message contains "/time", it gets the bot to print the time in the chat room it was asked in). (所有协同程序现在都在尝试打印消息,如果消息包含“/ time”,它会让机器人在其询问的聊天室中打印时间)。 Keyboard interrupt also doesn't work, I have to close the command prompt every time. 键盘中断也不起作用,我每次都要关闭命令提示符。

Here is my code: 这是我的代码:

import asyncio
from slackclient import SlackClient
import time, datetime as dt

token = "MY TOKEN"
sc = SlackClient(token)

@asyncio.coroutine
def read_text(info):
    if 'text' in info[0]:
        print(info[0]['text'])
        if r'/time' in info[0]['text']:
            print(info)
            resp = 'The time is ' + dt.datetime.strftime(dt.datetime.now(),'%H:%M:%S')
            print(resp)
            chan = info[0]['channel']
            sc.rtm_send_message(chan, resp)


loop = asyncio.get_event_loop()
try:
    sc.rtm_connect()
    info = sc.rtm_read()
    if len(info) == 1:
        asyncio.async(read_text(info))
    loop.run_forever()

except KeyboardInterrupt:
    pass
finally:
    print('step: loop.close()')
    loop.close()

I think it's the loop part that's broken, since it never seems to get to the coroutine. 我认为这是循环部分被打破,因为它似乎永远不会到达协程。 So maybe a shorter way of asking this question is what is it about my try: statement that prevents it from looping like in the asyncio example I followed? 所以也许一个更简单的问这个问题的方法是什么是我的try:语句阻止它像我在后面的asyncio示例中循环? Is there something about sc.rtm_connect() that it doesn't like? sc.rtm_connect()有什么不喜欢的吗?

I'm new to asyncio, so I'm probably doing something stupid. 我是asyncio的新手,所以我可能做了一些愚蠢的事情。 Is this even the best way to try and go about this? 这是尝试和解决这个问题的最好方法吗? Ultimately I want the bot to do some things that take quite a while to compute, and I'd like it to remain responsive in that time, so I think I need to use asyncio or threads in some variety, but I'm open to better suggestions. 最终我希望机器人做一些需要花费很长时间才能完成计算的东西,并且我希望它在那个时候保持响应,所以我认为我需要使用asyncio或某种程度的线程,但我愿意更好的建议。

Thanks a lot, Alex 非常感谢,Alex

I changed it to the following and it worked: 我将其更改为以下内容并且有效:

import asyncio
from slackclient import SlackClient
import time, datetime as dt

token = "MY TOKEN"    
sc = SlackClient(token)

@asyncio.coroutine
def listen():
    yield from asyncio.sleep(1)
    x = sc.rtm_connect()
    info = sc.rtm_read()
    if len(info) == 1:
        if 'text' in info[0]:
            print(info[0]['text'])
            if r'/time' in info[0]['text']:
                print(info)
                resp = 'The time is ' + dt.datetime.strftime(dt.datetime.now(),'%H:%M:%S')
                print(resp)
                chan = info[0]['channel']
                sc.rtm_send_message(chan, resp)

    asyncio.async(listen())


loop = asyncio.get_event_loop()
try:
    asyncio.async(listen())
    loop.run_forever()

except KeyboardInterrupt:
    pass
finally:
    print('step: loop.close()')
    loop.close()

Not entirely sure why that fixes it, but the key things I changed were putting the sc.rtm_connect() call in the coroutine and making it x = sc.rtm_connect() . 不完全确定为什么要修复它,但我改变的关键事情是将sc.rtm_connect()调用放在协程中并使其成为x = sc.rtm_connect() I also call the listen() function from itself at the end, which appears to be what makes it loop forever, since the bot doesn't respond if I take it out. 我也在最后调用了listen()函数,这似乎是让它永远循环的原因,因为如果我把它拿出来,机器人不响应。 I don't know if this is the way this sort of thing is supposed to be set up, but it does appear to continue to accept commands while it's processing earlier commands, my slack chat looks like this: 我不知道这是不是应该设置这种东西的方式,但它似乎在处理早期命令时继续接受命令,我的松弛聊天看起来像这样:

me [12:21 AM] 
/time

[12:21] 
/time

[12:21] 
/time

[12:21] 
/time

testbotBOT [12:21 AM] 
The time is 00:21:11

[12:21] 
The time is 00:21:14

[12:21] 
The time is 00:21:16

[12:21] 
The time is 00:21:19

Note that it doesn't miss any of my /time requests, which it would if it weren't doing this stuff asynchronously. 请注意,它不会错过任何我/time请求,如果它不是异步执行此操作,它会发生这种情况。 Also, if anyone is trying to replicate this you'll notice that slack brings up the built in command menu if you type "/". 此外,如果有人试图复制这个,你会注意到如果你键入“/”,松弛会打开内置命令菜单。 I got around this by typing a space in front. 我通过在前面键入一个空格来解决这个问题。

Thanks for the help, please let me know if you know of a better way of doing this. 感谢您的帮助,如果您知道更好的方法,请告诉我。 It doesn't seem to be a very elegant solution, and the bot can't be restarted after I use the a cntrl-c keyboard interrupt to end it - it says 它似乎不是一个非常优雅的解决方案,并且我使用cntrl-c键盘中断结束后无法重启机器人 - 它说

Task exception was never retrieved
future: <Task finished coro=<listen() done, defined at asynctest3.py:8> exception=AttributeError("'NoneType' object has no attribute 'recv'",)>
Traceback (most recent call last):
  File "C:\Users\Dell-F5\AppData\Local\Programs\Python\Python35-32\Lib\asyncio\tasks.py", line 239, in _step
    result = coro.send(None)
  File "asynctest3.py", line 13, in listen
    info = sc.rtm_read()
  File "C:\Users\Dell-F5\Envs\sbot\lib\site-packages\slackclient\_client.py", line 39, in rtm_read
    json_data = self.server.websocket_safe_read()
  File "C:\Users\Dell-F5\Envs\sbot\lib\site-packages\slackclient\_server.py", line 110, in websocket_safe_read
    data += "{0}\n".format(self.websocket.recv())
AttributeError: 'NoneType' object has no attribute 'recv'

Which I guess means it's not closing the websockets nicely. 我猜这意味着它没有很好地关闭websockets。 Anyway, that's just an annoyance, at least the main problem is fixed. 无论如何,这只是一个烦恼,至少主要问题是固定的。

Alex 亚历克斯

Making blocking IO calls inside a coroutine defeat the very purpose of using asyncio (eg info = sc.rtm_read() ). 在协程内部进行阻塞IO调用会破坏使用asyncio的目的(例如, info = sc.rtm_read() )。 If you don't have a choice, use loop.run_in_executor to run the blocking call in a different thread. 如果您没有选择,请使用loop.run_in_executor在其他线程中运行阻塞调用。 Careful though, some extra locking might be needed. 但是要小心,可能需要一些额外的锁定。

However, it seems there's a few asyncio-based slack client libraries you could use instead: 但是,似乎有一些基于asyncio的松弛客户端库可以使用:


EDIT: Butterfield uses the Slack real-time messaging API. 编辑: Butterfield使用Slack实时消息传递API。 It even provides an echo bot example that looks very much like what you're trying to achieve: 它甚至提供了一个echo bot示例 ,看起来非常像您要实现的目标:

import asyncio
from butterfield import Bot

@asyncio.coroutine
def echo(bot, message):
    yield from bot.post(
        message['channel'],
        message['text']
    )

bot = Bot('slack-bot-key')
bot.listen(echo)
butterfield.run(bot)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM