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

关于whatsapp-web.js中消息送达可靠验证方案的技术问询

WhatsApp-Web.js 中消息送达可靠验证方案的技术问询

你好!针对你提到的「用ACK跟踪消息送达,但脚本无错退出却实际未送达」的问题,以及关于是否需要用message事件做强验证的疑问,我结合你的现有代码给出以下分析和优化方案:


一、现有ACK方案的问题与优化

你当前通过message_ack事件跟踪消息的ACK状态(ACK>=1代表已送达)的思路是正确的,但出现脚本提前退出却未捕获到ACK的情况,大概率是两个原因:

  1. Node.js进程在ACK事件触发前就因无pending异步操作而自动退出;
  2. 网络波动或极端情况导致ACK事件未被捕获,且没有超时兜底逻辑。

优化后的完整代码(补全超时、进程保持、容错)

const { Client, LocalAuth } = require('whatsapp-web.js');
const GROUP_NAME = '你的群名称'; // 替换为实际群名

let shutdownPlanned = false;
const ACK_TIMEOUT = 30000; // 30秒超时阈值
const messagesToTrack = [];

// 保持Node.js进程活跃,避免提前退出
process.stdin.resume();

function safeShutdown() {
    if (shutdownPlanned) return;
    shutdownPlanned = true;
    console.log('启动安全退出流程...');
    client.destroy()
        .then(() => {
            process.stdin.pause();
            process.exit(0);
        })
        .catch(err => {
            console.error('退出时发生错误:', err);
            process.exit(1);
        });
}

function checkMessagesDelivery() {
    // 检查所有消息是否已送达(ACK>=1)或已超时
    const allProcessed = messagesToTrack.every(msg => msg.ack >= 1 || msg.isTimeout);
    const failedMessages = messagesToTrack.filter(msg => msg.isTimeout && msg.ack < 1);

    if (allProcessed) {
        if (failedMessages.length > 0) {
            console.error(`⚠️  有${failedMessages.length}条消息超时未送达:`);
            failedMessages.forEach(msg => console.error(`- 消息ID: ${msg.id.id}`));
        } else {
            console.log('✅ 所有消息均已成功送达(ACK>=1)');
        }
        setTimeout(safeShutdown, 5000); // 延迟5秒退出,确保所有日志输出
    }
}

// 为每条消息添加超时跟踪
function trackMessageWithTimeout(msg) {
    const trackedMsg = { ...msg, ack: 0 };
    messagesToTrack.push(trackedMsg);

    // 超时兜底:30秒内未收到有效ACK则标记为失败
    setTimeout(() => {
        const idx = messagesToTrack.findIndex(m => m.id.id === trackedMsg.id.id);
        if (idx !== -1 && messagesToTrack[idx].ack < 1) {
            messagesToTrack[idx].isTimeout = true;
            console.log(`⏰ 消息 ${trackedMsg.id.id} 超时未收到ACK,标记为送达失败`);
            checkMessagesDelivery();
        }
    }, ACK_TIMEOUT);
}

const client = new Client({
    authStrategy: new LocalAuth({
        clientId: 'forecast-bot',
        dataPath: './.wwebjs_auth',
    }),
    puppeteer: {
        headless: false,
        args: ['--no-sandbox', '--disable-setuid-sandbox']
    },
    webVersionCache: { type: 'none' },
    restartOnAuthFail: true
});

client.on('message_ack', (msg, ack) => {
    const idx = messagesToTrack.findIndex(m => m.id?.id === msg.id?.id);
    if (idx !== -1) {
        messagesToTrack[idx].ack = ack;
        console.log(`📩 消息 ${msg.id.id} ACK更新为: ${ack}(0=发送中,1=已送达,2=已读,3=已播放)`);
        checkMessagesDelivery();
    }
});

client.on('ready', async () => {
    console.log('🤖 客户端已就绪');
    try {
        const chats = await client.getChats();
        const group = chats.find(c => c.isGroup && c.name.toLowerCase().includes(GROUP_NAME.toLowerCase()));
        
        if (!group) {
            console.error('❌ 未找到目标群:', GROUP_NAME);
            return safeShutdown();
        }

        const textContent = loadText(); // 假设你的loadText()函数已定义
        const textMsg = await client.sendMessage(group.id._serialized, textContent);
        
        // 校验消息是否成功发送(是否返回有效ID)
        if (!textMsg?.id?.id) {
            console.error('❌ 消息发送失败,未获取到有效消息ID');
            return safeShutdown();
        }

        trackMessageWithTimeout(textMsg);
        console.log('📤 消息已发出,ID:', textMsg.id.id);

    } catch (e) {
        console.error('❌ 就绪/发送消息时出错:', e);
        safeShutdown();
    }
});

client.initialize();

// 处理未捕获的异常,避免进程意外退出
process.on('uncaughtException', (err) => {
    console.error('未捕获的异常:', err);
    safeShutdown();
});

process.on('unhandledRejection', (reason, promise) => {
    console.error('未处理的Promise拒绝:', reason);
    safeShutdown();
});

二、是否需要用message事件做强验证?

这取决于你对「消息真的被接收」的定义:

  • 如果仅需确认消息已被WhatsApp服务器转发并送达目标设备,那么ACK>=1的状态已经足够(WhatsApp官方定义:ACK=1代表消息已到达对方设备),优化后的ACK跟踪方案完全能满足需求。
  • 如果需要绝对确认消息已被目标用户/群接收并可被读取,可以结合以下两种方式:
    1. 拉取群消息历史校验:发送后延迟一段时间,主动拉取群的最新消息,检查自己发送的消息是否存在(代码示例如下);
    2. 触发被动回复:如果是针对特定用户,可以让对方回复特定内容,通过message事件捕获回复来确认(适合一对一场景,群场景不适用)。

群消息历史校验代码示例(添加在sendMessage之后)

// 延迟10秒后检查群消息历史(给服务器同步时间)
setTimeout(async () => {
    try {
        const groupMessages = await group.fetchMessages({ limit: 15 }); // 拉取最新15条消息
        const sentMsgExists = groupMessages.some(msg => msg.fromMe && msg.id.id === textMsg.id.id);
        
        if (sentMsgExists) {
            console.log('✅ 群消息历史中已找到发送的消息,确认已送达');
        } else {
            console.error('❌ 群消息历史中未找到发送的消息,可能未送达');
        }
    } catch (e) {
        console.error('❌ 检查群消息历史失败:', e);
    }
}, 10000);

三、关键注意事项

  1. 进程活跃保持:添加process.stdin.resume()是避免Node.js进程提前退出的核心,确保能捕获到异步的message_ack事件;
  2. 超时机制:30秒的超时阈值可以根据你的网络环境调整,避免无限等待ACK;
  3. 异常处理:添加uncaughtExceptionunhandledRejection的监听,避免进程因未处理的错误意外退出。

希望这些方案能帮你解决问题,如果还有其他疑问,随时补充细节哦!

火山引擎 最新活动