如何根据 Discord.js 中的反应编辑消息(创建列表和切换页面)

Posted

技术标签:

【中文标题】如何根据 Discord.js 中的反应编辑消息(创建列表和切换页面)【英文标题】:How to edit message according to reaction in Discord.js (create a list and switch page) 【发布时间】:2019-12-10 23:32:30 【问题描述】:

我希望我的 Discord 机器人发送一条消息,然后在人们做出反应时对其进行编辑(例如创建一个列表,然后单击向右或向左箭头将编辑消息并显示列表的下一个/上一个部分)。

示例: 反应前:

反应后:

【问题讨论】:

【参考方案1】:

如何处理消息反应?

对消息反应有 3 种反应方式:

    使用函数awaitReactions(基于promise) 使用ReactionCollector 使用messageReactionAdd 事件

区别:

messageReactionAdd 是一个链接到Client 的事件:

每当响应添加到缓存消息时发出。

虽然ReactionCollectorawaitReactions 链接到特定消息,但如果将反应添加到另一条消息,则不会执行任何操作。

messageReactionAdd 在缓存消息(旧消息)中添加响应时不会被触发。 Discord.js 指南中有一个guide for listening on old messages,其中给出了此警告

本节描述了如何使用一些未记录的 API 将不受支持的功能添加到 discord.js,因此您应该非常谨慎地遵循此处的任何内容。此处的任何内容都可能随时更改,恕不另行通知,并且可能会破坏您机器人中的其他功能。

awaitReactions 是基于 Promise 的,它只会在 Promise 解决后返回所有添加的反应的集合(在添加 X 个反应后,在 Y 秒后等)。没有特定的支持来处理每个添加的反应。您可以将您的函数放在 filter 函数中以添加每个反应,但这是一个无意的小技巧。但是,ReactionCollector 有一个 collect 事件。

那么,我用什么?

您想要编辑您的机器人发送的消息(因为您无法编辑其他用户的消息)。所以ReactionCollectorawaitReactions

如果您想在满足特定条件后编辑消息(X 人已投票,已添加 Y 反应,15 分钟后,...)(例如:投票,您将允许用户投票在 15 分钟内),您可以同时使用 awaitReactionsReactionCollector

但是,如果您想根据特定反应(如示例中对箭头表情符号做出反应)编辑消息,则必须使用ReactionCollector

如果消息没有被缓存,你可以使用messageReactionAdd,但它会更复杂,因为你基本上必须重写一个表情符号收集器,但对于每个表情符号。

注意:如果机器人重新启动,ReactionCollectorawaitReactions 将被删除,而 messageReactionAdd 将照常工作(但你会丢失你声明的变量,所以如果你已经存储了你想要的消息听,它们也会消失)。

怎么办?

你需要不同的东西:

将触发功能的表情符号列表(您可以选择对每个表情符号做出反应) 停止收听消息反应的条件(如果您想收听带有messageReactionAdd的每条消息,则不适用 获取消息并对其进行编辑的函数 一个返回布尔值的过滤函数:true我想对这个表情符号做出反应,false我不想做出反应。此功能将基于表情符号列表,但也可以过滤用户反应或您需要的任何其他条件 编辑消息的逻辑。例如:对于一个列表,它将基于结果的数量、当前索引和添加的反应

示例:用户列表

表情符号列表:

const emojiNext = '➡'; // unicode emoji are identified by the emoji itself
const emojiPrevious = '⬅';
const reactionArrow = [emojiPrevious, emojiNext];

停止条件

const time = 60000; // time limit: 1 min

编辑功能

这里的功能很简单,消息是预先生成的(时间戳和页脚除外)。

const first = () => new Discord.MessageEmbed()
      .setAuthor('TOTO', "https://i.imgur.com/ezC66kZ.png")
      .setColor('#AAA')
      .setTitle('First')
      .setDescription('First');

const second = () => new Discord.MessageEmbed()
      .setAuthor('TOTO', "https://i.imgur.com/ezC66kZ.png")
      .setColor('#548')
      .setTitle('Second')
      .setDescription('Second');

const third = () => new Discord.MessageEmbed()
      .setAuthor('TOTO', "https://i.imgur.com/ezC66kZ.png")
      .setColor('#35D')
      .setTitle('Third')
      .setDescription('Third');

const list = [first, second, third];

function getList(i) 
return list[i]().setTimestamp().setFooter(`Page $i+1`); // i+1 because we start at 0

过滤功能

function filter(reaction, user)
  return (!user.bot) && (reactionArrow.includes(reaction.emoji.name)); // check if the emoji is inside the list of emojis, and if the user is not a bot

逻辑

请注意,我在这里使用 list.length 来避免进入 list[list.length] 及其他范围。如果您没有硬编码列表,则应在参数中传递限制。 如果索引无效,您还可以让 getList 返回 undefined,而不是将索引用于布尔条件,而是将返回的值与 undefined 进行比较。

function onCollect(emoji, message, i, getList) 
  if ((emoji.name === emojiPrevious) && (i > 0)) 
    message.edit(getList(--i));
   else if ((emoji.name === emojiNext) && (i < list.length-1)) 
    message.edit(getList(++i));
  
  return i;

这是另一个 getList 函数的另一个逻辑,例如它只返回 list[i],而不是像上面那样设置时间戳,因为尝试在 undefined 上执行 .setTimestamp 会引发错误。

  if (emoji.name === emojiPrevious) 
    const embed = getList(i-1);
    if (embed !== undefined) 
      message.edit(embed);
      i--;
    
   else if (emoji.name === emojiNext) 
    const embed = getList(i+1);
    if (embed !== undefined) 
      message.edit(embed);
      i++;
    
  
  return i;

构建构造函数

例子同题,用箭头功能编辑消息。

我们将使用收集器:

function createCollectorMessage(message, getList) 
  let i = 0;
  const collector = message.createReactionCollector(filter,  time );
  collector.on('collect', r => 
    i = onCollect(r.emoji, message, i, getList);
  );
  collector.on('end', collected => message.clearReactions());

它需要我们想听的信息。你也可以给它一个内容列表 // 消息 // 一个数据库 // 任何必要的东西。

发送消息并添加收集器

function sendList(channel, getList)
  channel.send(getList(0))
    .then(msg => msg.react(emojiPrevious))
    .then(msgReaction => msgReaction.message.react(emojiNext))
    .then(msgReaction => createCollectorMessage(msgReaction.message, getList));

【讨论】:

【参考方案2】:

应 OP 的要求编写此答案

由于这是一件很常见的事情,我编写了一个库来帮助解决这个问题:discord-dynamic-messages 请注意,discord-dynamic-messages 是一个仅限 typescript 的库。

这是使用动态消息解决问题的方法。

定义分页信息

import  RichEmbed  from 'discord.js';
import  DynamicMessage, OnReaction  from 'discord-dynamic-messages';

const clamp = (num, min, max) => Math.min(Math.max(num, min), max);

export class PaginationMessage extends DynamicMessage 
  constructor(private embeds: Array<() => RichEmbed>, private embedIndex = 0) 
    super();
  

  @OnReaction(':arrow_left:')
  public previousEmbed() 
    this.embedIndex = clamp(this.embedIndex - 1, 0, this.embeds.length - 1);
  

  @OnReaction(':arrow_right:')
  public nextEmbed() 
    this.embedIndex = clamp(this.embedIndex + 1, 0, this.embeds.length - 1);
  

  public render() 
    return this.embeds[this.embedIndex]()
      .setTimestamp()
      .setFooter(`Page $this.embedIndex + 1`);
  


使用您定义的分页消息

import  Client, RichEmbed  from 'discord.js';
import  PaginationMessage  from '...'

const first = () => new RichEmbed()
  .setAuthor('TOTO', 'https://i.imgur.com/ezC66kZ.png')
  .setColor('#AAA')
  .setTitle('First')
  .setDescription('First');

const second = () => new RichEmbed()
  .setAuthor('TOTO', 'https://i.imgur.com/ezC66kZ.png')
  .setColor('#548')
  .setTitle('Second')
  .setDescription('Second');

const third = () => new RichEmbed()
  .setAuthor('TOTO', 'https://i.imgur.com/ezC66kZ.png')
  .setColor('#35D')
  .setTitle('Third')
  .setDescription('Third');

const pages = [first, second, third];

const client = new Client();
client.on('ready', () => 
  client.on('message', (message) => 
    new PaginationMessage(pages).sendTo(message.channel);
  );
);
client.login(discord_secret);

【讨论】:

【参考方案3】:

您可以像这样使用awaitReactions() 方法。

const  MessageEmbed  = require('discord.js');

const messageEmbed1 = new MessageEmbed()
//Add the methods you want (customize the embed to how you want)

const messageEmbed2 = new MessageEmbed()
//Add the methods you want (customize the embed to how you want)

const msg = await message.channel.send(messageEmbed1);
msg.react("️️⬅️");
msg.react("➡️");

let react;
react = await msg.awaitReactions(
    (reaction, user) => reaction.emoji.name === '➡️' && user.id === message.author.id,
     max: 1, time: Infinity, errors: ['time'] 
);

if (react && react.first()) msg.edit(messageEmbed2);

let react2;
react2 = await msg.awaitReactions(
    (reaction, user) => reaction.emoji.name === '⬅️' && user.id === message.author.id,
     max: 1, time: Infinity, errors: ['time'] 
);

if (react2 && react2.first()) msg.edit(messageEmbed1);

【讨论】:

这根本行不通。多重原因/第一个无限不是时间可接受的论据。其次,您给出了一个“错误”参数,但没有发现任何错误。但最大的问题是你处理反应的方式。它只允许按特定顺序翻每一页一次。它“全局”地回答了这个问题,但是由于您是通过等待承诺来完成的,因此使其以更通用的方式工作会更加复杂。使这更通用将更有可能需要删除一半的逻辑以在同一过滤器中包含不同的表情符号,然后根据一个稍后采取行动 此外,您不会等待第一个反应,这意味着如果 API 比网关快,如果过滤器不排除它,您的机器人将触发反应,请参阅***.com/questions/57207625跨度>

以上是关于如何根据 Discord.js 中的反应编辑消息(创建列表和切换页面)的主要内容,如果未能解决你的问题,请参考以下文章

反应后 Discord.js 编辑消息

有没有办法对使用 discord.js 发送的每条消息做出反应

Discord.js:如何发送消息并从中收集反应

Discord Bot 读取对设置消息的反应

(discord.js) 如何提及对机器人消息做出反应的用户?

Discord JS - 如何对同一个嵌入做出多次反应?