Python asyncio 与 Slack 机器人

Posted

技术标签:

【中文标题】Python asyncio 与 Slack 机器人【英文标题】:Python asyncio with Slack bot 【发布时间】:2016-08-15 13:15:34 【问题描述】:

我正在尝试使用 asyncio 制作一个简单的 Slack 机器人,主要使用示例 here 用于 asyncio 部分,here 用于 Slack 机器人部分。

这两个示例都可以单独运行,但是当我将它们放在一起时,我的循环似乎没有循环:它通过一次然后死掉。如果info 是一个长度等于 1 的列表,当在聊天室中输入一条消息时会发生这种情况,其中包含机器人,则应该触发协程,但它永远不会触发。 (协程现在要做的就是打印消息,如果消息包含“/time”,它会让机器人打印被问到的聊天室中的时间)。键盘中断也不行,每次都要关闭命令提示符。

这是我的代码:

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

我认为是循环部分被破坏了,因为它似乎永远不会到达协程。所以也许问这个问题的一个更简短的方法是我的 try: 语句是什么,它防止它像我遵循的 asyncio 示例中那样循环? sc.rtm_connect() 有什么不喜欢的地方吗?

我是 asyncio 的新手,所以我可能在做一些愚蠢的事情。这甚至是尝试解决此问题的最佳方法吗?最终我希望机器人做一些需要很长时间才能计算的事情,我希望它在那段时间保持响应,所以我认为我需要使用 asyncio 或各种线程,但我愿意更好的建议。

非常感谢, 亚历克斯

【问题讨论】:

我担心这个问题可能过于宽泛。有没有什么方法可以问一个更具体的问题,或者问一系列构成这个问题的问题? 很确定被破坏的部分是循环,因为它甚至从不调用协程。我想一个简短的问题是sc.rtm_connect() 调用是否有一些东西可以防止 asyncio 循环对象像正常一样循环? 【参考方案1】:

我将其更改为以下内容,并且有效:

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

不完全确定为什么会修复它,但我更改的关键是将 sc.rtm_connect() 调用放入协程并使其成为 x = sc.rtm_connect()。最后,我还从自身调用 listen() 函数,这似乎是让它永远循环的原因,因为如果我把它拿出来,机器人不会响应。我不知道这是否是这种事情应该设置的方式,但它似乎在处理早期命令时继续接受命令,我的闲聊看起来像这样:

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

请注意,它不会错过我的任何/time 请求,如果它不异步执行这些操作,它会丢失。此外,如果有人试图复制这个,你会注意到,如果你输入“/”,slack 会打开内置的命令菜单。我通过在前面输入一个空格来解决这个问题。

感谢您的帮助,如果您知道更好的方法,请告诉我。这似乎不是一个非常优雅的解决方案,并且在我使用 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'

我猜这意味着它没有很好地关闭 websocket。不管怎样,这只是一个烦恼,至少主要问题已经解决了。

亚历克斯

【讨论】:

【参考方案2】:

在协程中进行阻塞 IO 调用违背了使用 asyncio 的目的(例如info = sc.rtm_read())。如果您别无选择,请使用loop.run_in_executor 在不同的线程中运行阻塞调用。不过要小心,可能需要一些额外的锁定。

但是,您似乎可以使用一些基于 asyncio 的 slack 客户端库来代替:

slacker-asyncio - slacker 的分支,基于 aiohttp butterfield - 基于 slacker 和 websockets

编辑:Butterfield 使用 Slack 实时消息 API。它甚至提供了一个echo bot example,看起来非常像您想要实现的目标:

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)

【讨论】:

啊,我明白了,虽然它不会错过命令,但个别循环会阻止其他循环被执行,对吗?感谢您提供所有信息,我会尽力解决。 我不认为 Slacker 使用实时方法,对吧?我认为这意味着我无法制作一个机器人来响应发送给它的不同消息? 谢谢,我会去巴特菲尔德。我有点担心它似乎不是很发达,而且自去年年初以来也没有太多的工作。

以上是关于Python asyncio 与 Slack 机器人的主要内容,如果未能解决你的问题,请参考以下文章

Slack 机器人可以与另一个机器人交互并在频道中触发它的某些功能吗?

Slack 聊天机器人连接到 Bubble 数据库

asyncio:等待来自其他线程的事件

使用 Slack Bot 提及访客?

Python 3 urllib Discord 与 Slack Bot

深究Python中的asyncio库-asyncio简介与关键字