You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在机器人重启或崩溃后恢复Timeout?求Node.js工具或方法

解决Glitch托管Discord机器人重启后恢复Timeout的方案

我来给你梳理一下核心思路和具体实现——因为Glitch上的进程重启后,内存里的setTimeout肯定会丢失,所以关键是把每个抽奖(Giveaway)的定时任务信息持久化存储,等机器人重启后再重新计算剩余时间、恢复定时器。

核心思路

  • 把抽奖的关键信息(结束时间、消息ID、频道ID、奖品等)存在本地轻量数据库里
  • 机器人启动时,扫描所有未结束的抽奖,计算当前到结束时间的剩余时长,重新设置setTimeout
  • 抽奖结束后,执行你的逻辑并清理数据库里的对应记录

具体实现步骤

1. 选择轻量持久化工具

推荐用lowdb,它是基于JSON文件的轻量数据库,完全适配Glitch这种不需要复杂数据库的场景。先安装依赖:

npm install lowdb

2. 初始化数据库

创建一个db.js文件,用来管理数据库的读写操作:

const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync');

// 用FileSync适配器确保写入同步,适配Glitch环境
const adapter = new FileSync('giveaways.json');
const db = low(adapter);

// 初始化数据库默认结构
db.defaults({ giveaways: [] }).write();

module.exports = db;

3. 修改抽奖创建逻辑

当你发起抽奖时,不再直接调用setTimeout,而是先把抽奖信息存入数据库,再设置定时器:

const db = require('./db');
const ms = require('ms'); // 假设你已经安装了ms库处理时间格式

// 这是你创建抽奖的核心逻辑(替换你原来的setTimeout部分)
function createGiveaway(m, title, duration, room) {
  // 计算抽奖结束的时间戳(当前时间+持续时长)
  const endTimestamp = Date.now() + ms(duration);
  // 用抽奖消息的ID作为唯一标识
  const giveawayId = m.id;

  // 把抽奖信息存入数据库
  db.get('giveaways')
    .push({
      id: giveawayId,
      messageId: m.id,
      channelId: m.channel.id,
      roomChannelId: room.replace(/\D/g, ''),
      title: title,
      endTimestamp: endTimestamp
    })
    .write();

  // 设置定时器,到点执行结束逻辑
  setTimeout(() => {
    endGiveaway(giveawayId);
  }, ms(duration));
}

4. 封装抽奖结束逻辑

把你原来的结束逻辑抽成独立函数,从数据库读取数据执行:

const db = require('./db');

async function endGiveaway(giveawayId) {
  // 从数据库获取对应抽奖信息
  const giveaway = db.get('giveaways').find({ id: giveawayId }).value();
  if (!giveaway) return; // 已经处理过的抽奖直接跳过

  try {
    // 获取抽奖消息(这里适配Discord.js v12,根据你的版本调整API)
    const channel = await client.channels.fetch(giveaway.channelId);
    const m = await channel.messages.fetch(giveaway.messageId);

    // 你的原逻辑,变量从数据库的抽奖对象中获取
    let users = m.reactions.get("🎉").users;
    let gFilter = users.filter(u => !u.bot).random();
    
    const giveEmbed = {
      title: null,
      description: null,
      fields: [],
      timestamp: new Date(),
      footer: { text: `Ended At` }
    };

    if (!gFilter) {
      giveEmbed.fields = [{ 
        name: '🎊 Giveaway Ended! 🎊', 
        value: `**Winner :** No Winner\n**Prize :** ${giveaway.title}`, 
        inline: false 
      }];
      await m.edit('**🎉 GIVEAWAY ENDED 🎉 @everyone**', { embed: giveEmbed });
      
      const roomChannel = await client.channels.fetch(giveaway.roomChannelId);
      await roomChannel.send(`**The Giveaway Ended With No Winner 😦**`);
    } else {
      giveEmbed.fields = [{ 
        name: '🎊 Giveaway Ended! 🎊', 
        value: `**Winner :** ${gFilter}\n**Prize :** ${giveaway.title}`, 
        inline: false 
      }];
      await m.edit('**🎉 GIVEAWAY ENDED 🎉 @everyone**', { embed: giveEmbed });
      
      const roomChannel = await client.channels.fetch(giveaway.roomChannelId);
      await roomChannel.send(`**Congratulations ${gFilter} ! You Won The __${giveaway.title}__ !!! <a:crazy:526018890208116738>**`);
      
      await gFilter.send(`**Congratulations ${gFilter} ! You Won The __${giveaway.title}__ !!! <a:crazy:526018890208116738>\nAsk for Your Prize In <#593334823230242817> <a:RainbowParrot:597540860934225960>**`);
    }
  } catch (error) {
    console.error('处理抽奖结束时出错:', error);
  } finally {
    // 从数据库移除该抽奖记录,避免重启后重复处理
    db.get('giveaways').remove({ id: giveawayId }).write();
  }
}

5. 机器人启动时恢复未结束的抽奖

在你的机器人入口文件(比如index.js)里,添加启动后的恢复逻辑:

const db = require('./db');
const ms = require('ms');

// 机器人就绪后执行恢复逻辑
client.on('ready', async () => {
  console.log('机器人已上线,开始恢复未结束的抽奖...');
  
  const now = Date.now();
  // 筛选出还没到结束时间的抽奖
  const activeGiveaways = db.get('giveaways').filter(g => g.endTimestamp > now).value();

  for (const giveaway of activeGiveaways) {
    const remainingTime = giveaway.endTimestamp - now;
    if (remainingTime <= 0) {
      // 已经到结束时间,直接执行结束逻辑
      endGiveaway(giveaway.id);
      continue;
    }

    // 设置新的定时器
    setTimeout(() => {
      endGiveaway(giveaway.id);
    }, remainingTime);

    console.log(`恢复抽奖: ${giveaway.title},剩余时间: ${ms(remainingTime, { long: true })}`);
  }

  console.log(`共恢复 ${activeGiveaways.length} 个未结束的抽奖`);
});

注意事项

  • Glitch的文件系统是持久化的,但如果项目长时间休眠,可能会有轻微延迟,但lowdb生成的giveaways.json文件会被保留。
  • 确保你的Discord.js版本和代码中的API调用匹配(比如fetch方法在v12和v13+的写法有区别)。
  • 如果担心消息ID重复,可以用uuid库生成更唯一的抽奖ID。

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

火山引擎 最新活动