提问者:小点点

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


我希望我的Discord bot发送一条消息,然后在人们反应时编辑它(例如创建一个列表,点击右箭头或左箭头将编辑消息并显示列表的下一个/上一个部分)。


共2个答案

匿名用户

有3种方法可以对消息反应作出反应:

  1. 使用函数AwaitReactions(基于承诺)
  2. 使用ReactionCollector
  3. 使用MessageReactionAdd事件

MessageReactionAdd是链接到客户端的事件:

每当将反应添加到缓存的消息时发出。

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

如果向缓存的消息(旧消息)添加了反应,则不会激发MessageReactionAdd。在discord.js指南中有一个用于侦听旧消息的指南,其中给出了此警告

本节描述了如何使用一些未文档化的API将不受支持的功能添加到discord.js中,因此您应该非常小心地执行这里的任何操作。这里的任何内容都可以随时更改,恕不另行通知,并且可能会破坏您的机器人中的其他功能。

AwaitReactions是基于promise的,只有在解决promise时(添加了X个反应后,Y秒后,等等),它才会返回所有添加反应的集合。没有一个特定的支持来处理每一个添加的反应。您可以将您的函数放在filter函数中,以获得添加的每个反应,但这是一个小黑客,并不是有意的。但是,ReactionCollector有一个Collection事件。

您希望编辑由您的机器人发送的消息(因为您不能编辑其他用户的消息)。因此ReactionCollectorAwaitReactions

如果您希望在满足某个条件(X个人已投票,Y个反应已添加,15分钟后,。。。)后编辑消息(例如:投票,您将允许用户在15分钟内投票),则可以同时使用AwaitReactionReactionCollector

但是,如果您想根据特定的反应编辑消息(例如,当对箭头表情的反应时),则必须使用ReactionCollector

如果消息没有缓存,您可以使用MessageReactionAdd,但它会更复杂,因为您基本上将不得不重写一个emoji收集器,但对于每个emoji。

注意:如果bot重新启动,ReactionCollectorAwaitReaction将被删除,而MessageReactionAdd将照常工作(但是您将丢失您声明的变量,因此如果您存储了要侦听的消息,它们也将消失)。

你需要不同的东西:

  • 将触发功能的emoji列表(您可以选择对每个emoji作出反应)
  • 停止侦听邮件反应的条件(如果您要侦听所有带有MessageReactionAdd
  • 的邮件,则不适用
  • 获取消息并编辑它的函数
  • 一个过滤器函数,它将返回一个布尔值:true我想对这个emoji做出反应,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返回未定义,并将返回值与未定义值进行比较,而不是将索引用于布尔条件。

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],而不像上面的函数那样设置时间戳,因为尝试在未定义的情况下执行.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));
}

匿名用户

应OP的要求撰写此答案

由于这是一件非常常见的事情,所以我编写了一个库来帮助完成这件事情:discord-dynamic-messages注意,discord-dynamic-messages是一个仅有打字脚本的库。

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

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