You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Global-sales/wai-server/core/baileys/index.js

337 lines
12 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

const {
makeWASocket,
Browsers,
DisconnectReason,
fetchLatestBaileysVersion,
makeCacheableSignalKeyStore,
makeInMemoryStore,
useMultiFileAuthState,
downloadMediaMessage,
isJidUser
} = require('@whiskeysockets/baileys');
const { writeFile } = require('fs/promises');
const waEmitter = require('../emitter');
const serverConfig = require('../../config').server;
const { encodeJid, decodeJid, formatStatus, formatTimestamp } = require('./helper');
const generateId = require('../../utils/generateId.util');
const NodeCache = require('node-cache');
const P = require('pino');
const createWhatsApp = async phone => {
let qrCode = null;
const channelId = generateId();
const whatsAppNo = phone;
// 缓存 msgId-externalId过期时间为 5 分钟
const externalIdCache = new NodeCache({ stdTTL: 60*5 });
const logger = P({ timestamp: () => `,"time":"${new Date().toJSON()}"` }, P.destination('./logs/wa-logs-' + phone + '_' + channelId + '.txt'));
logger.level = 'trace';
const msgRetryCounterCache = new NodeCache();
const storeFilename = './baileys_auth_info/baileys_store_' + phone + '_' + channelId + '.json'
const store = makeInMemoryStore({ logger });
store?.readFromFile(storeFilename);
// save every 10s
setInterval(() => {
store?.writeToFile(storeFilename);
}, 10_000);
const { state, saveCreds } = await useMultiFileAuthState('./baileys_auth_info/' + phone);
// fetch latest version of WA Web
const { version, isLatest } = await fetchLatestBaileysVersion();
const waVersion = version.join('.') + ', ' + (isLatest ? 'latest' : 'out');
const handleMessagesUpsert = async upsert => {
console.info('messages.upsert: ', JSON.stringify(upsert, undefined, 2));
if (upsert.type === 'notify') {
for (const msg of upsert.messages) {
// 没有类型的消息,先忽略
if (!msg.message) {
continue;
}
const messageType = Object.keys(msg.message)[0];
console.log('messageType', messageType);
// TODO: 待处理图片消息
const fromWhatsAppNo = decodeJid(msg.key.remoteJid);
if (msg.message?.conversation || msg.message?.extendedTextMessage?.text) {
const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text;
const externalId = externalIdCache.get(msg.key.id);
if (msg.key.fromMe) {
waEmitter.emit('message:updated', {
id: msg.key.id,
externalId,
status: formatStatus(msg.status),
direction: 'outbound',
from: whatsAppNo,
to: fromWhatsAppNo,
type: 'text',
text: {
body: text,
},
conversation: {
type: isJidUser(msg.key.remoteJid) ? 'individual' : 'group',
},
customerProfile: {
id: decodeJid(msg.key.participant),
name: msg.pushName,
},
whatsAppNo,
fromMe: msg.key.fromMe,
eventSource: serverConfig.name + '.messages.upsert.notify',
updateTime: formatTimestamp(msg.messageTimestamp),
});
} else {
waEmitter.emit('message:received', {
id: msg.key.id,
externalId,
status: '',
direction: 'inbound',
from: fromWhatsAppNo,
to: whatsAppNo,
type: 'text',
text: {
body: text,
},
conversation: {
type: isJidUser(msg.key.remoteJid) ? 'individual' : 'group',
},
customerProfile: {
id: decodeJid(msg.key.participant),
name: msg.pushName,
},
whatsAppNo,
fromMe: msg.key.fromMe,
eventSource: serverConfig.name + '.messages.upsert.notify',
createTime: formatTimestamp(msg.messageTimestamp),
});
}
}
}
} else if (upsert.type === 'append') {
for (const msg of upsert.messages) {
if (msg.message?.conversation || msg.message?.extendedTextMessage?.text) {
const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text;
const fromWhatsAppNo = decodeJid(msg.key.remoteJid);
const externalId = externalIdCache.get(msg.key.id);
if (msg.key.fromMe) {
waEmitter.emit('message:updated', {
id: msg.key.id,
externalId,
status: formatStatus(msg.status),
direction: 'outbound',
from: whatsAppNo,
to: fromWhatsAppNo,
type: 'text',
text: {
body: text,
},
conversation: {
type: isJidUser(msg.key.remoteJid) ? 'individual' : 'group',
},
customerProfile: {
id: decodeJid(msg.participant),
name: msg.pushName,
},
whatsAppNo,
fromMe: msg.key.fromMe,
eventSource: serverConfig.name + '.messages.upsert.append',
updateTime: formatTimestamp(msg.messageTimestamp),
});
}
}
}
}
}
const handleMessagesUpdate = async messageUpdate => {
console.info('messages.update: ', JSON.stringify(messageUpdate, undefined, 2));
for (const msg of messageUpdate) {
// 没有明确标识状态的更新,忽略
const ignore = msg.update === undefined || msg.update.status === undefined;
if (ignore) continue;
const externalId = externalIdCache.get(msg.key.id);
waEmitter.emit('message:updated', {
id: msg.key.id,
externalId,
status: formatStatus(msg.update?.status),
direction: msg.key.fromMe ? 'outbound' : 'inbound',
from: msg.key.fromMe ? whatsAppNo : decodeJid(msg.key.remoteJid),
to: msg.key.fromMe ? decodeJid(msg.key.remoteJid) : whatsAppNo,
conversation: {
type: isJidUser(msg.key.remoteJid) ? 'individual' : 'group',
},
customerProfile: {
id: decodeJid(msg.key.participant),
name: msg.pushName,
},
whatsAppNo,
fromMe: msg.key.fromMe,
eventSource: serverConfig.name + '.messages.updated',
updateTime: formatTimestamp(new Date().getTime() / 1000),
});
}
}
const handleCredsUpdate = async () => {
await saveCreds();
}
const start = () => {
const waSocket = makeWASocket({
version,
logger,
auth: {
creds: state.creds,
keys: makeCacheableSignalKeyStore(state.keys, logger),
},
// connectTimeoutMs: 1000*60*10,
// defaultQueryTimeoutMs: 1000*60*1,
// keepAliveIntervalMs: 1000*60*60,
//retryRequestDelayMs: 1000*25,
// https://github.com/WhiskeySockets/Baileys/blob/31bc8ab/src/Utils/generics.ts#L21
// https://github.com/WhiskeySockets/Baileys/blob/31bc8ab4e2c825c0d774875701ed07e20d05bdb6/WAProto/WAProto.proto
browser: Browsers.baileys('WEAR_OS'),//Browsers.macOS('SAFARI'),//Browsers.ubuntu('IOS_PHONE'),//Browsers.baileys('WEAR_OS'),//
msgRetryCounterCache,
generateHighQualityLinkPreview: false,
syncFullHistory: false,
});
store?.bind(waSocket.ev);
const sendTextMessageHandler = (event) => {
const { to: number, externalId, content } = event;
const jid = encodeJid(number);
waSocket.sendMessage(
jid, { text: content }
).then(msg => {
externalIdCache.set(msg.key.id, externalId)
}).catch(ex => {
waEmitter.emit('message:updated', {
id: generateId(),
externalId,
status: 'failed',
direction: 'outbound',
from: whatsAppNo,
to: number,
error: `发送文本消息出错 ` + ex,
eventSource: serverConfig.name + '.sendMessage.catch',
updateTime: formatTimestamp(new Date().getTime() / 1000),
});
});
}
waSocket.ev.on('connection.update', async update => {
const { connection, lastDisconnect, qr } = update;
if (connection === 'close') {
waEmitter.off('request.' + whatsAppNo + '.send.text', sendTextMessageHandler);
if((lastDisconnect?.error)?.output?.statusCode !== DisconnectReason.loggedOut) {
start();
} else {
waEmitter.emit('connection:close', {
whatsAppNo, channelId,
eventSource: serverConfig.name + '.connection.update.close',
status: 'offline',
});
}
} else if (connection === 'open') {
waEmitter.emit('connection:open', {
status: 'open', whatsAppNo, channelId,
eventSource: serverConfig.name + '.connection.update.open',
});
waEmitter.on('request.' + whatsAppNo + '.send.text', sendTextMessageHandler);
// 考虑迁移到 connection.update 事件之外,测试看是否能发送消息?
// waEmitter.on('request.' + whatsAppNo + '.send.text', event => {
// const { to: number, externalId, content } = event;
// const jid = encodeJid(number);
// waSocket.sendMessage(
// jid, { text: content }
// ).then(msg => {
// externalIdCache.set(msg.key.id, externalId)
// }).catch(ex => {
// waEmitter.emit('message:updated', {
// id: generateId(),
// externalId,
// status: 'failed',
// direction: 'outbound',
// from: whatsAppNo,
// to: number,
// error: `发送文本消息出错 ` + ex,
// eventSource: serverConfig.name + '.sendMessage.catch',
// updateTime: formatTimestamp(new Date().getTime() / 1000),
// });
// });
// });
waEmitter.on('request.' + whatsAppNo + '.send.image', event => {
const {to: number, externalId, imageUrl} = event;
const jid = encodeJid(number);
waSocket.sendMessage(
jid, {image: { url: imageUrl }}
).then(msg => {
externalIdCache.set(msg.key.id, externalId);
}).catch(ex => {
waEmitter.emit('message:updated', {
id: generateId(),
externalId,
status: 'failed',
direction: 'outbound',
from: whatsAppNo,
to: number,
error: `发送图片消息出错 ` + ex,
eventSource: serverConfig.name + '.sendMessage.catch',
updateTime: formatTimestamp(new Date().getTime() / 1000),
});
});
});
} else if (qr !== undefined) {
// WebSocket 创建成功等待扫码,如果没有扫码会更新 qr
// 第一次一分钟,后面是 20 秒更新一次
if (qrCode === null) {
qrCode = qr;
waEmitter.emit('creds:update', {
id: generateId(),
qr, whatsAppNo,
server:serverConfig.name,
eventSource: 'creds.update',
createTime: formatTimestamp(new Date().getTime() / 1000),
});
} else {
// 第一次二维码时效后退出,不需要等待更新二维码
waSocket.logout(() => '二维码已过期');
}
}
});
waSocket.ev.on('creds.update', handleCredsUpdate);
waSocket.ev.on('messages.upsert', handleMessagesUpsert);
waSocket.ev.on('messages.update', handleMessagesUpdate);
};
return {
createTimestamp: Date.now(),
status: 'offline',
version: waVersion,
channelId: channelId,
phone: phone,
start,
};
};
module.exports = {
createWhatsApp,
};