如何让 Discord 机器人异步等待多条消息的反应?

2024-04-09

tl;dr 我的机器人如何异步等待多条消息的反应?


我正在向我的 Discord 机器人添加石头剪刀布 (rps) 命令。用户可以通过输入调用命令.rps以及一个可选参数,指定要玩的用户。

.rps @TrebledJ

被调用时,机器人将直接向调用它的用户和目标用户(通过参数)发送消息 (DM)。然后这两个用户react使用 ✊、???? 或 ✌️ 发送给他们的 DM。

现在我正在尝试让它异步工作。具体来说,机器人将向两个用户发送 DM(异步)并等待他们的反应(异步)。分步场景:

Scenario (Asynchronous):
1. User A sends ".rps @User_B"
2. Bot DMs User A and B.
3. User A and B react to their DMs.
4. Bot processes reactions and outputs winner.

(See also: Note 1)

由于目标是监听等待多个消息的反应,因此我尝试创建两个单独的线程/池。以下是三种尝试:

  • multiprocessing.pool.ThreadPool
  • multiprocessing.Pool
  • concurrent.futures.ProcessPoolExecutor

不幸的是,这三件事都没有成功。 (也许我实施了一些不正确的事情?)

以下代码显示了命令功能(rps),一个辅助函数(rps_dm_helper),以及三次(不成功的)尝试。这些尝试都使用不同的辅助函数,但底层逻辑是相同的。为了方便起见,第一次尝试没有被注释。

import asyncio
import discord
from discord.ext import commands
import random
import os

from multiprocessing.pool import ThreadPool           # Attempt 1
# from multiprocessing import Pool                      # Attempt 2
# from concurrent.futures import ProcessPoolExecutor    # Attempt 3


bot = commands.Bot(command_prefix='.')
emojis = ['✊', '????', '✌']


# Attempt 1 & 2
async def rps_dm_helper(player: discord.User, opponent: discord.User):
    if player.bot:
        return random.choice(emojis)

    message = await player.send(f"Playing Rock-Paper-Scissors with {opponent}. React with your choice.")

    for e in emojis:
        await message.add_reaction(e)

    try:
        reaction, _ = await bot.wait_for('reaction_add',
                                         check=lambda r, u: r.emoji in emojis and r.message.id == message.id and u == player,
                                         timeout=60)
    except asyncio.TimeoutError:
        return None

    return reaction.emoji

# # Attempt 3
# def rps_dm_helper(tpl: (discord.User, discord.User)):
#     player, opponent = tpl
#
#     if player.bot:
#         return random.choice(emojis)
#
#     async def rps_dm_helper_impl():
#         message = await player.send(f"Playing Rock-Paper-Scissors with {opponent}. React with your choice.")
#
#         for e in emojis:
#             await message.add_reaction(e)
#
#         try:
#             reaction, _ = await bot.wait_for('reaction_add',
#                                              check=lambda r, u: r.emoji in emojis and r.message.id == message.id and u == player,
#                                              timeout=60)
#         except asyncio.TimeoutError:
#             return None
#
#         return reaction.emoji
#
#     return asyncio.run(rps_dm_helper_impl())


@bot.command()
async def rps(ctx, opponent: discord.User = None):
    """
    Play rock-paper-scissors!
    """

    if opponent is None:
        opponent = bot.user

    # Attempt 1: multiprocessing.pool.ThreadPool
    pool = ThreadPool(processes=2)
    author_result = pool.apply_async(asyncio.run, args=(rps_dm_helper(ctx.author, opponent),))
    opponent_result = pool.apply_async(asyncio.run, args=(rps_dm_helper(opponent, ctx.author),))
    author_emoji = author_result.get()
    opponent_emoji = opponent_result.get()

    # # Attempt 2: multiprocessing.Pool
    # pool = Pool(processes=2)
    # author_result = pool.apply_async(rps_dm_helper, args=(ctx.author, opponent))
    # opponent_result = pool.apply_async(rps_dm_helper, args=(opponent, ctx.author))
    # author_emoji = author_result.get()
    # opponent_emoji = opponent_result.get()

    # # Attempt 3: concurrent.futures.ProcessPoolExecutor
    # with ProcessPoolExecutor() as exc:
    #     author_emoji, opponent_emoji = list(exc.map(rps_dm_helper, [(ctx.author, opponent), (opponent, ctx.author)]))

    ### -- END ATTEMPTS

    if author_emoji is None:
        await ctx.send(f"```diff\n- RPS: {ctx.author} timed out\n```")
        return

    if opponent_emoji is None:
        await ctx.send(f"```diff\n- RPS: {opponent} timed out\n```")
        return

    author_idx = emojis.index(author_emoji)
    opponent_idx = emojis.index(opponent_emoji)

    if author_idx == opponent_idx:
        winner = None
    elif author_idx == (opponent_idx + 1) % 3:
        winner = ctx.author
    else:
        winner = opponent

    # send to main channel
    await ctx.send([f'{winner} won!', 'Tie'][winner is None])


bot.run(os.environ.get("BOT_TOKEN"))


Note

1 Contrast the asynchronous scenario to a non-asynchronous one:

Scenario (Non-Asynchronous):
1. User A sends ".rps @User_B"
2. Bot DMs User A.
3. User A reacts to his/her DM.
4. Bot DMs User B.
5. User B reacts to his/her DM.
6. Bot processes reactions and outputs winner.

这实现起来并不难:

...
@bot.command()
async def rps(ctx, opponent: discord.User = None):
    """
    Play rock-paper-scissors!
    """

    ...

    author_emoji = await rps_dm_helper(ctx.author, opponent)
    if author_emoji is None:
        await ctx.send(f"```diff\n- RPS: {ctx.author} timed out\n```")
        return
    
    opponent_emoji = await rps_dm_helper(opponent, ctx.author)
    if opponent_emoji is None:
        await ctx.send(f"```diff\n- RPS: {opponent} timed out\n```")
        return

    ...

但恕我直言,非异步会导致糟糕的用户体验。 :-)


你应该能够使用asyncio.gather https://docs.python.org/3/library/asyncio-task.html#asyncio.gather安排多个协程同时执行。等待中gather等待所有结果完成并以列表形式返回结果。

from asyncio import gather

@bot.command()
async def rps(ctx, opponent: discord.User = None):
    """
    Play rock-paper-scissors!
    """
    if opponent is None:
        opponent = bot.user
    author_helper = rps_dm_helper(ctx.author, opponent)  # Note no "await"
    opponent_helper = rps_dm_helper(opponent, ctx.author)
    author_emoji, opponent_emoji = await gather(author_helper, opponent_helper)
    ...
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何让 Discord 机器人异步等待多条消息的反应? 的相关文章

  • Python 3 os.urandom

    在哪里可以找到完整的教程或文档os urandom 我需要获得一个随机 int 来从 80 个字符的字符串中选择一个字符 如果你只需要一个随机整数 你可以使用random randint a b 来自随机模块 http docs pytho
  • Twisted 的 Deferred 和 JavaScript 中的 Promise 一样吗?

    我开始在一个需要异步编程的项目中使用 Twisted 并且文档非常好 所以我的问题是 Twisted 中的 Deferred 与 Javascript 中的 Promise 相同吗 如果不是 有什么区别 你的问题的答案是Yes and No
  • 如何在Python中流式传输和操作大数据文件

    我有一个相对较大 1 GB 的文本文件 我想通过跨类别求和来减小其大小 Geography AgeGroup Gender Race Count County1 1 M 1 12 County1 2 M 1 3 County1 2 M 2
  • 使用 pygame 显示 unicode 符号

    我检查了其他答案 但不明白为什么我的代码错误地显示 This is what I currently see https i stack imgur com 8tNIK png 这是关于文本渲染的相关代码 font pygame font
  • 用缺失的日期填充其他列 Nan Pandas DataFrame

    我实际上是从几个 Excel 文件中提取数据来监控我的每日卡路里摄入量 我设法使用列表理解来生成日期 我尝试使用合并或连接 但它不起作用 ValueError 您正在尝试合并对象和 float64 列 date list 2021 05 2
  • Pandas:GroupBy 到 DataFrame

    参考这个关于 groupby 到 dataframe 的非常流行的问题 https stackoverflow com questions 10373660 converting a pandas groupby object to dat
  • Python 中的流式传输管道

    我正在尝试使用 Python 将 vmstat 的输出转换为 CSV 文件 因此我使用类似的方法转换为 CSV 并将日期和时间添加为列 vmstat 5 python myscript py gt gt vmstat log 我遇到的问题是
  • Keras:如何保存模型或权重?

    如果这个问题看起来很简单 我很抱歉 但是阅读 Keras 保存和恢复帮助页面 https www tensorflow org beta tutorials keras save and restore models https www t
  • Gspread如何复制sheet

    在 Stackoverflow 上进行谷歌搜索和搜索后 我想我找不到有关如何复制现有工作表 现有模板工作表 并将其保存到另一个工作表中的指南 根据文档 有重复表 https gspread readthedocs io en latest
  • .pyx 文件出现未知文件类型错误

    我正在尝试构建一个包含 pyx 文件的 Python 包 pyregion 但在构建过程中出现错误 检查以下输出 python setup py build running build running build py creating b
  • 使用 python 绘制正值小提琴图

    我发现小提琴图信息丰富且有用 我使用 python 库 seaborn 然而 当应用于正值时 它们几乎总是在低端显示负值 我发现这确实具有误导性 尤其是在处理现实数据集时 在seaborn的官方文档中https seaborn pydata
  • Tensorflow 与 Keras 的兼容性

    我正在使用 Python 3 6 和 Tensorflow 2 0 并且有一些 Keras 代码 import keras from keras models import Sequential from keras layers impo
  • 使用 Pandas 计算 delta 列

    我有一个数据框 如下所示 Name Variable Field A 2 3 412 A 2 9 861 A 3 5 1703 B 3 5 1731 A 4 0 2609 B 4 0 2539 A 4 6 2821 B 4 6 2779 A
  • ANTLR 获取并拆分词法分析器内容

    首先 对我的英语感到抱歉 我还在学习 我为我的框架编写 Python 模块 用于解析 CSS 文件 我尝试了 regex ply python 词法分析器和解析器 但我发现自己在 ANTLR 中 第一次尝试 我需要解析 CSS 文件中的注释
  • 动态过滤 pandas 数据框

    我正在尝试使用三列的阈值来过滤 pandas 数据框 import pandas as pd df pd DataFrame A 6 2 10 5 3 B 2 5 3 2 6 C 5 2 1 8 2 df df loc df A gt 0
  • Elasticsearch 通过搜索返回拼音标记

    我用语音分析插件 https www elastic co guide en elasticsearch plugins current analysis phonetic html由于语音转换 从弹性搜索中进行一些字符串匹配 我的问题是
  • 在 Django 查询中使用 .extra(select={...}) 引入的值上使用 .aggregate() ?

    我正在尝试计算玩家每周玩游戏的次数 如下所示 player game objects extra select week WEEK games game date aggregate count Count week 但姜戈抱怨说 Fiel
  • Django Admin 中的反向内联

    我有以下 2 个型号 现在我需要将模型 A 内联到模型 B 的页面上 模型 py class A models Model name models CharField max length 50 class B models Model n
  • 如何与其他用户一起使用 pyenv?

    如何与其他用户一起使用 pyenv 例如 如果我在用户 test 的环境中安装了 pyenv 则当我以 test 身份登录时可以使用 pyenv 但是 当我以其他用户 例如 root 身份登录时如何使用 pyenv 即使你这么做了 我也会s
  • TKinter 中的禁用/启用按钮

    我正在尝试制作一个像开关一样的按钮 所以如果我单击禁用按钮 它将禁用 按钮 有效 如果我再次按下它 它将再次启用它 我尝试了 if else 之类的东西 但没有成功 这是一个例子 from tkinter import fenster Tk

随机推荐

  • Pyparsing - 匹配最外面的一组嵌套括号

    我正在尝试使用 pyparsing 构建一个解析器 该解析器将匹配任意嵌套的括号内的所有文本 如果我们考虑这样的字符串 A B C D E F G Random Middle text H I J 我想要的是解析器以返回两个匹配的方式进行匹
  • 地图在移动设备上显示错误(使用 JQuery mobile)

    我正在使用 jQuery mobile 并且必须显示一些地图 我使用的功能是每次单击特定链接时都会创建地图 但在生成第一个地图后 其他地图显示错误 这里有一个例子 第一张地图 其他地图 我使用这样的函数 function buildMap
  • 使用 EVAL、SCAN 和 DEL 的 Redis 通配符删除脚本返回“非确定性命令后不允许写入命令”

    因此 我正在寻求构建一个 lua 脚本 该脚本使用 SCAN 根据模式查找键并删除它们 原子地 我首先准备了以下脚本 local keys local done false local cursor 0 repeat local resul
  • gridview中如何合并两个单元格

    我在 gridview 中有一些数据 格式如下 A B 1 2 adeel 3 4 sml 现在我想将该行与 B 列下的空单元格合并 我该怎么做 您可以使用 layout columnSpan 或 layout rowSpan 根据需要使对
  • ExpandableListView、OnChildClickListener

    我有组列表 每个组内都有填充的子项目 我已经实现了searchview with filtered ressults and myExpandableListView 可以展开和折叠 问题是 我不知道如何处理 OnChildClickLis
  • 为什么数字类型不共享通用接口?

    我最近遇到了一个问题 我想要一个可以同时处理双精度和整数的函数 并且想知道为什么所有数字类型 包含算术运算符和比较 没有通用接口 它会让编写像这样的函数Math Min 存在无数的重载 方式更方便 引入额外的接口会是一个重大改变吗 Edit
  • 如何使用POI api读取java中的doc和docx文件

    我正在尝试读取 doc 和 docx 文件 这是代码 static String distination E static String docFileName Requirements docx public static void ma
  • 使用固定导航栏和锚标记跳转到部分的引导程序[重复]

    这个问题已经存在了 我正在尝试使用锚标记通过引导程序和固定在顶部的导航栏导航到网页的特定部分 问题是 当我单击锚链接时 它们无法正确滚动到该部分的开头 而是滚动到该部分的开头 因为页边距应用于正文 body margin top 60px
  • X.iOS Cycle7 似乎破坏了 SSL

    昨天我在 Beta 通道中将 XS 更新为 RC 版本 即 Cycle7 现在我在使用 iOS 连接到 HTTPS 连接时遇到问题 错误 Error SecureChannelFailure The authentication or de
  • 如何使用musicbrainz获取专辑图像

    我不知道我是否可以在这里问这样的问题 我的问题如下 我正在自己制作音乐播放器 我想下载那些没有专辑图像的歌曲的专辑图像 就像什么是在做 我自己做了一些搜索 我发现使用 MusicBrainz 我们可以下载图像 我查了一下它的API 但我不太
  • 使用 php 删除 xml 中标签值之间的空格

    我一直在搜索信息 当我将 PHP 代码导出到 XML 时 如何删除 PHP 代码留下的标记值之间的空格 我将详细解释 首先加载 XML 然后使用 xPath 对文件进行搜索 然后删除一些元素与某些品牌不匹配 最后我将其重新导出为新的 XML
  • numpy正半定警告

    在我正在编写的Python脚本中 我正在使用表达式模拟多元正态随机向量 np random multivariate normal np zeros dim obs y cov 我的脚本运行 但生成以下警告 RuntimeWarning c
  • html5/jquery后退按钮

    是否有一个库或一段代码可以使特定按钮充当正确的浏览器后退按钮 它将带您到之前加载的上一个页面 目前我只是指定href我假设的是之前加载的页面 但得出的结论是这不起作用 因为可以从不同的屏幕访问一个屏幕 是否有这样的示例 或者我是否需要创建自
  • Android:从另一个片段打开一个片段

    我是 Android 新手 这是我的第二个应用程序 我正在创建一个选项卡式活动 其中第一个片段具有用于创建新任务的表单 第二个片段具有所有已保存任务的列表 第三个片段将在从第二个片段的列表中选择时显示对任务的评论分段 第三个片段也应该像一个
  • 如何将 WSGI 中的首选编码设置为 UTF-8

    感觉这里有点疯狂 我已经使用 mod wsgi 设置了 Apache 但我无法使编码正常工作 我有 测试 mod wsgi 是否在守护进程模式下运行 read 格雷厄姆 邓普尔顿的博客文章 http blog dscpl com au 20
  • ReportLab 表的列跨越 PDF 页面上的所有行?

    我正在尝试按以下格式在 reportLab 中布局表格 该表是动态的并且可以有很多行 a b a a tTableStyle SPAN 1 0 1 1 如果表格适合一页 则工作正常 但如果表格分为几页 则崩溃 如果没有跨度 表格可以正常分割
  • 带有时态表的 Entity Framework Core 3.1 - 访问 SysStartTime 和 SysEndTime

    我已经基于 Microsoft SQL 文档创建了时态表使用默认历史表创建临时表 https learn microsoft com en us sql relational databases tables creating a syst
  • 将 Pylons 控制器作为单独的应用程序运行?

    我有一个 Pylons 应用程序 我想将一些逻辑移动到单独的批处理过程中 我一直在主应用程序下运行它进行测试 但它将在数据库中执行大量工作 我希望它是一个单独的进程 将在后台不断运行 主 pylons 应用程序会将作业提交到数据库中 新进程
  • 如何在 android 中构建支持旧 SDK 版本 (minSdkVersion) 的应用程序

    当通过向导创建新项目并给出错误时 那就太沮丧了 我只是使用 MinSdk 9 创建新项目以使应用程序在姜饼上运行 这给了我以下错误 Error Execution failed for task app processDebugManife
  • 如何让 Discord 机器人异步等待多条消息的反应?

    tl dr 我的机器人如何异步等待多条消息的反应 我正在向我的 Discord 机器人添加石头剪刀布 rps 命令 用户可以通过输入调用命令 rps以及一个可选参数 指定要玩的用户 rps TrebledJ 被调用时 机器人将直接向调用它的