我希望我的Discord bot发送一条消息,然后在人们反应时编辑它(例如创建一个列表,点击右箭头或左箭头将编辑消息并显示列表的下一个/上一个部分)。
有3种方法可以对消息反应作出反应:
AwaitReactions
(基于承诺)ReactionCollector
MessageReactionAdd
事件MessageReactionAdd
是链接到客户端
的事件:
每当将反应添加到缓存的消息时发出。
而ReactionCollector
和AwaitReactions
链接到特定消息,如果将反应添加到另一条消息中,则不会执行任何操作。
如果向缓存的消息(旧消息)添加了反应,则不会激发MessageReactionAdd
。在discord.js指南中有一个用于侦听旧消息的指南,其中给出了此警告
本节描述了如何使用一些未文档化的API将不受支持的功能添加到discord.js中,因此您应该非常小心地执行这里的任何操作。这里的任何内容都可以随时更改,恕不另行通知,并且可能会破坏您的机器人中的其他功能。
AwaitReactions
是基于promise的,只有在解决promise时(添加了X个反应后,Y秒后,等等),它才会返回所有添加反应的集合。没有一个特定的支持来处理每一个添加的反应。您可以将您的函数放在filter
函数中,以获得添加的每个反应,但这是一个小黑客,并不是有意的。但是,ReactionCollector
有一个Collection
事件。
您希望编辑由您的机器人发送的消息(因为您不能编辑其他用户的消息)。因此ReactionCollector
或AwaitReactions
。
如果您希望在满足某个条件(X个人已投票,Y个反应已添加,15分钟后,。。。)后编辑消息(例如:投票,您将允许用户在15分钟内投票),则可以同时使用AwaitReaction
和ReactionCollector
。
但是,如果您想根据特定的反应编辑消息(例如,当对箭头表情的反应时),则必须使用ReactionCollector
。
如果消息没有缓存,您可以使用MessageReactionAdd
,但它会更复杂,因为您基本上将不得不重写一个emoji收集器,但对于每个emoji。
注意:如果bot重新启动,ReactionCollector
和AwaitReaction
将被删除,而MessageReactionAdd
将照常工作(但是您将丢失您声明的变量,因此如果您存储了要侦听的消息,它们也将消失)。
你需要不同的东西:
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);