《Electron + WhatsApp 群聊记账机器人开发实战:自动查账/撤回/清账,附企业级完整源码》
electron + vue3的WhatsApp群聊记账机器人
·
「群内AA制记账混乱?这款Electron机器人自动监听WhatsApp消息,支持复杂数学计算、操作撤回、定时清理,源码即插即用!」
本文完整解析基于 Electron + Node.js + TypeScript 开发的WhatsApp群聊记账机器人,包含以下核心功能:
-
✅ 消息监听与数学表达式实时计算(支持
+ - * /
和复杂公式) -
✅ 操作历史记录与一键撤回(防止误操作)
-
✅ 定时自动清理旧数据(可配置周期)
-
✅ 管理员权限系统(Web界面动态管理)
-
✅ 本地加密存储 + 多平台兼容(Windows/macOS/Linux)
👉 付费解锁完整企业级源码(已用于海外财务系统)+ 防封号指南 + 二次开发教程 -
-
-
目录(部分隐藏,付费后解锁)
一、项目架构解析(免费试读)
-
1、技术栈:Electron 主进程 vs 渲染进程设计
-
2、数据流:消息监听 → 本金计算 → 日志记录 → 界面展示
-
3、安全方案:
SQLite
本地加密 + 防多开检测
主要功能代码
const { Client, LocalAuth } = require('whatsapp-web.js');
const qrcode = require('qrcode-terminal');
const math = require('mathjs');
const fs = require('fs');
const path = require('path');
const { EventEmitter } = require('events');
console.log('正在初始化 WhatsApp 客户端...');
// ========== 动态路径配置(通过外部初始化) ==========
let DATA_DIR;
let CAPITAL_DATA_PATH;
let LOG_DIR;
let mainWindow = null; // 主窗口引用(由主进程设置)
let client; // 将 client 提升到模块作用域
// ==================== 初始化函数 ====================
// ==================== 初始化函数 ====================
function init(config) {
DATA_DIR = config.dataDir;
CAPITAL_DATA_PATH = path.join(DATA_DIR, 'capital.json');
LOG_DIR = path.join(DATA_DIR, 'logs');
// 创建数据目录
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true });
}
// 初始化 WhatsApp 客户端
client = new Client({
authStrategy: new LocalAuth({ dataPath: DATA_DIR }),
puppeteer: {
headless: false,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage'
]
}
});
// 绑定事件监听器
bindClientEvents();
}
// ==================== 本金管理系统 ====================
//const CAPITAL_DATA_PATH = path.join(DATA_DIR, 'capital.json');
// 添加管理员ID列表
// const ADMIN_IDS = [
// '2348144171939', // 管理员ID
// // 可以添加更多管理员ID
// '2347016857486'
// ];
class CapitalManager {
static getData() {
try {
return JSON.parse(fs.readFileSync(CAPITAL_DATA_PATH, 'utf8')) || {};
} catch {
return {};
}
}
static saveData(data) {
fs.writeFileSync(CAPITAL_DATA_PATH, JSON.stringify(data, null, 2));
}
//获取群组资金信息
static getCapital(groupId) {
const data = this.getData();
//初始化新群组
if (!data[groupId]) {
data[groupId] = {
capital: 0,
history: [] // 新增历史记录
};
this.saveData(data);
}
return data[groupId];
}
//更新资金信息
static updateCapital(groupId, newAmount, operation) {
const data = this.getData();
const groupData = data[groupId] || { capital: 0, history: [] };
// 添加历史记录
groupData.history.push({
timestamp: new Date().toISOString(),
oldValue: groupData.capital,
newValue: newAmount,
operation: operation
});
groupData.capital = parseFloat(newAmount.toFixed(4));
data[groupId] = groupData;
this.saveData(data);
return true;
}
//撤回
static undoLastOperation(groupId) {
const data = this.getData();
const groupData = data[groupId];
if (!groupData || groupData.history.length === 0) {
return null;
}
// 获取最后一条记录
const lastOperation = groupData.history.pop();
// 恢复上一状态
if (groupData.history.length > 0) {
groupData.capital = groupData.history[groupData.history.length - 1].newValue;
} else {
groupData.capital = 0;
}
this.saveData(data);
return lastOperation;
}
//查账
static getHistory(groupId) {
const data = this.getData();
if (!data[groupId]) {
// 如果群组不存在,初始化一个新的群组数据
data[groupId] = {
capital: 0,
history: []
};
this.saveData(data);
}
return data[groupId].history;
}
//清账
static clearCapital(groupId) {
const data = this.getData();
if (!data[groupId]) {
// 如果群组不存在,初始化一个新的群组
data[groupId] = {
capital: 0,
history: []
};
} else {
// 只重置本金和清空历史记录,保留群组记录
data[groupId].capital = 0;
data[groupId].history = [];
}
this.saveData(data);
return true;
}
}
// ==================== 日志系统 ====================
const getLogDate = () => new Date().toISOString().split('T')[0];
class Logger {
static write(logData) {
if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true });
const logPath = path.join(LOG_DIR, `${getLogDate()}.log`);
// 写入文件
const logEntry = JSON.stringify({
timestamp: new Date().toISOString(),
...logData
}) + '\n';
fs.appendFileSync(logPath, logEntry);
// 发送到界面(通过主进程转发)
if (mainWindow) {
const logText = `${new Date().toISOString()} [${logData.type}] ${logData.event || logData.action}`;
mainWindow.webContents.send('log-update', logText);
}
}
static system(event, details) {
this.write({ type: 'SYSTEM', event, details });
}
static operation(groupId, action, user, capitalChange) {
this.write({ type: 'OPERATION', groupId, action, user, ...capitalChange });
}
}
// ==================== WhatsApp 客户端核心逻辑 ====================
function bindClientEvents() {
// ====== 验证 client 是否为 EventEmitter 实例 ======
if (!(client instanceof EventEmitter)) {
throw new Error('client 未正确初始化为 EventEmitter');
}
// ==================== 事件监听器 ====================
client.on('loading_screen', (percent, message) => {
console.log('加载进度:', percent, '%', message);
});
client.on('qr', qr => {
const qr_svg = require('qr-image').svg(qr, { size: 10 });
fs.writeFileSync('whatsapp-qr.svg', qr_svg);
console.log('二维码已保存为 whatsapp-qr.svg');
qrcode.generate(qr, { small: true });
});
client.on('authenticated', () => {
console.log('WhatsApp 认证成功!');
});
client.on('ready', async () => {
Logger.system('CLIENT_READY', { status: 'online' });
console.log('WhatsApp 客户端已准备就绪!');
if (!fs.existsSync('data')) fs.mkdirSync('data');
});
// ==================== 核心消息处理 ====================
client.on('message', async (message) => {
try {
// 过滤条件:自己发送的消息、非群组消息、非文本消息
if (message.fromMe ||
!(await message.getChat()).id._serialized.endsWith('@g.us') ||
message.hasMedia ||
message.type !== 'chat'
) return;
const jsonData = JSON.parse(fs.readFileSync(CAPITAL_DATA_PATH, 'utf8')) || {};
const ADMIN_USERNAME = jsonData._adminIds
const chat = await message.getChat();
await chat.sendStateTyping(); // 可选:显示正在输入状态
// 获取用户信息
const contact = await message.getContact();
const userInfo = {
id: contact.id.user,
name: contact.pushname,
};
// console.log(jsonData);
// console.log(ADMIN_USERNAME);
// console.log(userInfo);
// 检查是否为群组消息
const isGroup = chat.id._serialized.endsWith('@g.us');
if (!isGroup) return;
const groupId = chat.id._serialized;
const text = message.body.trim();
if (!ADMIN_USERNAME.includes(userInfo.name)) {
return message.reply('❌ 仅指定管理员可执行操作');
}
// 撤回命令
if (text === '/撤回') {
const lastOperation = CapitalManager.undoLastOperation(groupId);
if (!lastOperation) {
return chat.sendMessage('❌ 没有可撤回的操作记录');
}
// 记录撤回操作日志
Logger.operation(groupId, 'UNDO', userInfo, {
before: lastOperation.oldValue,
after: lastOperation.newValue,
operation: lastOperation.operation
});
return chat.sendMessage(
`⏪ 已撤回最后一次操作\n` +
`操作时间: ${new Date(lastOperation.timestamp).toLocaleString()}\n` +
`原操作: ${lastOperation.operation}\n` +
`恢复金额: ${lastOperation.oldValue}`
);
}
// 查账命令
if (text === '/查账') {
console.log('收到查账命令');
try {
const history = CapitalManager.getHistory(groupId);
//console.log('获取到历史记录:', history);
// 获取当前余额
const currentBalance = CapitalManager.getCapital(groupId).capital;
if (history.length === 0) {
//console.log('无历史记录');
return chat.sendMessage(`📜 暂无操作记录\n当前余额: ${currentBalance}`);
}
// 记录查账操作日志
Logger.operation(groupId, 'QUERY', userInfo, {
recordCount: history.length,
currentCapital: currentBalance
});
// 格式化历史记录
const historyText = history.map((record, index) => {
const date = new Date(record.timestamp).toLocaleString();
return `[${index + 1}] ${date}\n` +
`操作: ${record.operation}\n` +
`变化: ${record.oldValue} → ${record.newValue}`;
}).join('\n\n');
const message = `📜 操作历史记录(最近${history.length}条):\n` +
`当前余额: ${currentBalance}\n\n${historyText}`;
console.log('准备发送消息:', message);
return chat.sendMessage(message);
} catch (error) {
console.error('查账出错:', error);
return chat.sendMessage('❌ 查询失败: ' + error.message);
}
}
// 清账命令处理
if (text === '/清账') {
// 验证管理员权限
// console.log("消息用户id:",contact.id.user);
// console.log("管理员id:",ADMIN_IDS);
// console.log("是否有权限:",ADMIN_IDS.includes(contact.id.user));
// if (!ADMIN_IDS.includes(contact.id.user)) {
// return message.reply('❌ 仅指定管理员可执行此操作');
// }
// 检查是否有历史记录
const history = CapitalManager.getHistory(groupId);
if (history.length === 0) {
return message.reply(
'❌ 无需清账\n' +
'当前本金: 0\n' +
'历史记录为空'
);
}
const success = CapitalManager.clearCapital(groupId);
// 记录清账操作日志
Logger.operation(groupId, 'CLEAR', userInfo, {
before: CapitalManager.getCapital(groupId).capital,
after: 0
});
return message.reply(
'🔄 清账成功\n' +
'当前本金: 0\n' +
'历史记录已全部清除'
);
}
// 匹配数学表达式(支持复杂运算)
const mathExpression = text.match(/^([+\-*/])([\d().+\-*/]+)/);
if (!mathExpression) return;
const [_, operator, expression] = mathExpression;
const groupdata = CapitalManager.getCapital(groupId);
if(!groupdata){
chat.sendMessage("无资金信息")
return
}
//console.log("更新前:",groupdata);
try {
// 构造完整表达式
const currentValue = groupdata.capital
const fullExpression = `(${currentValue})${operator}(${expression})`;
// 安全计算
const result = math.evaluate(fullExpression);
const formattedResult = parseFloat(result.toFixed(4));
// 更新本金
CapitalManager.updateCapital(groupId, formattedResult, fullExpression);
const groupDataUpdate = CapitalManager.getCapital(groupId);
if (!groupDataUpdate) return;
//console.log("更新后:",groupDataUpdate);
// 发送格式化响应
chat.sendMessage(
`🔢 计算成功\n` +
`当前本金:${groupDataUpdate.capital}\n`+
`原值: ${currentValue}\n` +
`算式: ${fullExpression}\n` +
`新值: ${formattedResult}`
);
// 记录计账操作日志
Logger.operation(groupId, 'CALCULATION', userInfo, {
before: currentValue,
after: formattedResult,
expression: fullExpression
});
} catch (error) {
chat.sendMessage(`❌ 计算错误: ${error.message}`);
console.log(`❌ 计算错误: ${error.message}`);
}
} catch (error) {
console.error('处理消息时出错:', error);
}
});
// ==================== 其他系统事件 ====================
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
client.on('disconnected', async (reason) => {
Logger.system('CLIENT_DISCONNECTED', { reason });
console.log('断开连接,原因:', reason);
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
console.log(`尝试重新连接 (${reconnectAttempts}/${maxReconnectAttempts})...`);
await client.initialize().catch(console.error);
}
});
process.on('unhandledRejection', (reason) => {
console.error('未处理的 Promise 拒绝:', reason);
});
}
function startBot() {
if (!client) {
throw new Error('Client 未初始化,请先调用 init()');
}
client.initialize().catch(err => {
console.error('初始化失败:', err);
});
}
// 暴露方法供主进程设置窗口引用
module.exports = {
setMainWindow: (window) => {
mainWindow = window;
}
};
// ==================== 模块导出 ====================
module.exports = {
init,
startBot,
setMainWindow: (window) => {
mainWindow = window;
}
};
付费用户专享权益
-
获取完整源码包
-
包含Electron打包配置(
electron-builder
) -
预编译版本(开箱即用)
-

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)