You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

discord.js createReactionCollector事件重复触发两次问题排查

Discord.js 反应分页 collect 事件单次触发两次问题修复

问题表现

  • 参考discord.js-pagination实现的表情反应分页功能基本可用,但用户每次点击表情,collect事件会连续触发2次
  • 已排查确认sendPaginationMessage方法仅执行一次,不存在重复初始化收集器的问题

原实现代码:

public sendPaginationMessage = async (
    message: Message,
    pages: MessageEmbed[],
    emojiList: (GuildEmoji | string)[] = ['⏪', '⏩'],
    timeout = 120000
) => {

    const channelId = message?.channelId

    if (!message || !channelId) {
        console.error('Channel is inaccessible.')
        return
    }

    if (!pages) {
        console.error('Pages are not given.')
        return
    }

    if (emojiList.length !== 2) {
        console.error('Need two emojis.')
        return
    }

    pages[0].footer = {text: `Page 1 / ${pages.length}`}
    const channel = await this.getChannelById(channelId) as TextChannel

    if (!channel) {
        return console.error(
            `Channel ${channelId} introuvable dans: `,
            JSON.stringify(this.discordClient.channels.cache, null, 4)
        )
    }

    const currentPage: Message = await channel.send({embeds: [pages[0]]} as MessageOptions)
    for (const emoji of emojiList) await currentPage.react(emoji)

    const filter = (reaction: MessageReaction, user: User) => {
        const emojiName = reaction.emoji.name
        return !!emojiName && message.author.id === user.id && emojiList.includes(emojiName) && !user.bot
    }

    let page = 0

    const collector = currentPage.createReactionCollector({filter, time: timeout})

    collector.on('collect', reaction => {

        reaction.users.remove(message.author)

        // Log 2 times ¯\_(ツ)_/¯
        console.log('reaction', reaction)

        const emojiName = reaction.emoji.name

        const previous = emojiName === emojiList[0]

        if (previous && page <= 0 || !previous && page + 1 >= pages.length) return

        page = previous ? --page : ++page
        pages[page].footer = {text: `Page ${page + 1} / ${pages.length}`}
        currentPage.edit({embeds: [pages[page]]} as MessageOptions)
    })

    collector.on('end', () => {
        currentPage.reactions.removeAll().catch()
    })

    return currentPage
}

根因

  1. collect回调中执行reaction.users.remove()是异步操作,discord.js部分版本在更新reaction用户缓存时,会重复触发一次collect事件
  2. 开启REACTION/MESSAGE类型Partials时,收集器会同时接收网关实时事件和缓存补发的同一条反应事件,造成重复触发
  3. 原filter中emojiList.includes(emojiName)的判断不兼容自定义GuildEmoji场景,会出现匹配逻辑异常

修复方案

  1. 加处理锁避免重复执行:在收集器外层加布尔锁,同一时间只处理一次翻页逻辑,同时把异步的移除反应、编辑消息操作加上await,避免状态错乱
  2. 优化表情匹配逻辑:兼容默认unicode表情和自定义服务器表情
  3. 调整收集器配置:关闭dispose选项,避免反应移除事件干扰收集逻辑

修复后的核心代码段:

// ...省略前面的参数校验、发消息、加初始反应逻辑
const filter = (reaction: MessageReaction, user: User) => {
  // 同时匹配自定义表情ID和默认表情名称
  const emojiMatched = emojiList.some(emoji => {
    return typeof emoji === 'string' 
      ? emoji === reaction.emoji.name 
      : emoji.id === reaction.emoji.id
  })
  return emojiMatched && message.author.id === user.id && !user.bot
}

let page = 0
// 加处理锁
let isProcessing = false

const collector = currentPage.createReactionCollector({
  filter, 
  time: timeout,
  dispose: false // 关闭反应移除事件的收集
})

collector.on('collect', async (reaction, user) => {
  // 重复触发直接拦截
  if (isProcessing) return
  isProcessing = true

  try {
    // await等待移除反应完成
    await reaction.users.remove(user.id)
    console.log('reaction', reaction)

    const emojiName = reaction.emoji.name
    const previous = emojiName === emojiList[0]

    if ((previous && page <= 0) || (!previous && page + 1 >= pages.length)) {
      return
    }

    page = previous ? --page : ++page
    pages[page].footer = {text: `Page ${page + 1} / ${pages.length}`}
    await currentPage.edit({embeds: [pages[page]]} as MessageOptions)
  } finally {
    // 无论成功失败都释放锁
    isProcessing = false
  }
})

collector.on('end', () => {
  currentPage.reactions.removeAll().catch()
})
// ...

内容的提问来源于stack exchange,提问作者Dyslopia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 20:45:52