Python asyncio 与 Slack 机器人
Python asyncio with Slack bot
我正在尝试使用 asyncio 制作一个简单的 Slack 机器人,主要使用示例 for the asyncio part and 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 或各种线程,但我愿意更好的建议。
非常感谢,
亚历克斯
我将其更改为以下内容并且有效:
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 会调出内置命令菜单。我通过在前面输入 space 来解决这个问题。
感谢您的帮助,如果您知道更好的方法,请告诉我。这似乎不是一个非常优雅的解决方案,在我使用 a 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'
我猜这意味着它没有很好地关闭 websockets。无论如何,这只是一个烦恼,至少主要问题已经解决了。
亚历克斯
在协程中进行阻塞式 IO 调用会破坏使用 asyncio 的目的(例如 info = sc.rtm_read()
)。如果您别无选择,请使用 loop.run_in_executor 到 运行 不同线程中的阻塞调用。不过要小心,可能需要一些额外的锁定。
但是,您似乎可以使用一些基于 asyncio 的 slack 客户端库:
- slacker-asyncio - fork of slacker, based on aiohttp
- butterfield - based on slacker and websockets
编辑:Butterfield uses the Slack real-time messaging API. It even provides an 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)
我正在尝试使用 asyncio 制作一个简单的 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 或各种线程,但我愿意更好的建议。
非常感谢, 亚历克斯
我将其更改为以下内容并且有效:
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 会调出内置命令菜单。我通过在前面输入 space 来解决这个问题。
感谢您的帮助,如果您知道更好的方法,请告诉我。这似乎不是一个非常优雅的解决方案,在我使用 a 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'
我猜这意味着它没有很好地关闭 websockets。无论如何,这只是一个烦恼,至少主要问题已经解决了。
亚历克斯
在协程中进行阻塞式 IO 调用会破坏使用 asyncio 的目的(例如 info = sc.rtm_read()
)。如果您别无选择,请使用 loop.run_in_executor 到 运行 不同线程中的阻塞调用。不过要小心,可能需要一些额外的锁定。
但是,您似乎可以使用一些基于 asyncio 的 slack 客户端库:
- slacker-asyncio - fork of slacker, based on aiohttp
- butterfield - based on slacker and websockets
编辑:Butterfield uses the Slack real-time messaging API. It even provides an 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)