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

342 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 fs = require('fs');
const path = require('path');
const { writeFile } = require('fs/promises');
const waEmitter = require('../emitter');
const serverConfig = require('../../config').server;
const { encodeJid, decodeJid, formatStatus, formatTimestamp, getFileExtension, uint8ArrayToBase64 } = 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 + '.json'
const store = makeInMemoryStore({ logger });
store?.readFromFile(storeFilename);
// save every 10s
setInterval(() => {
store?.writeToFile(storeFilename);
}, 10_000);
const authStateFolder = './baileys_auth_info/' + phone;
const { state, saveCreds } = await useMultiFileAuthState(authStateFolder);
// fetch latest version of WA Web
const { version, isLatest } = await fetchLatestBaileysVersion();
const waVersion = version.join('.') + ', ' + (isLatest ? 'latest' : 'out');
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.macOS('SAFARI'),//Browsers.macOS('SAFARI'),//Browsers.ubuntu('IOS_PHONE'),//Browsers.baileys('WEAR_OS'),//
msgRetryCounterCache,
generateHighQualityLinkPreview: false,
syncFullHistory: false,
});
store?.bind(waSocket.ev);
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];
const remoteNo = decodeJid(msg.key.remoteJid);
const externalId = externalIdCache.get(msg.key.id);
if (msg.message?.conversation || msg.message?.extendedTextMessage?.text) {
const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text;
if (msg.key.fromMe) {
waEmitter.emit('message:updated', {
id: msg.key.id,
externalId,
status: formatStatus(msg.status),
direction: 'outbound',
from: whatsAppNo,
to: remoteNo,
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: remoteNo,
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 (messageType === 'imageMessage') {
console.info('image message: ', msg)
const imageMessage = msg.message.imageMessage;
const fileExtension = getFileExtension(imageMessage.mimetype);
const imageBuffer = await downloadMediaMessage(
msg, 'buffer', {}, { logger, reuploadRequest: waSocket.updateMediaMessage, },
);
const imageFilename = './temp/image_' + whatsAppNo + '_' + msg.key.id + fileExtension;
await writeFile(imageFilename, imageBuffer);
if (msg.key.fromMe) {
waEmitter.emit('message:updated', {
id: msg.key.id,
externalId,
status: formatStatus(msg.status),
direction: 'outbound',
from: whatsAppNo,
to: remoteNo,
type: 'image',
image: {
mimetype: imageMessage.mimetype,
sha256: uint8ArrayToBase64(imageMessage.fileSha256),
caption: '', // 暂时为空
filename: imageFilename,
link_original: imageMessage.url,
},
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 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 sendMessageHandler = (event) => {
const { to: number, externalId, ...content } = event;
const jid = encodeJid(number);
waSocket.sendMessage(
jid, 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,
whatsAppNo,
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.message', sendMessageHandler);
if((lastDisconnect?.error)?.output?.statusCode !== DisconnectReason.loggedOut) {
start();
} else {
// logout 异步删除验证目录
fs.rm(authStateFolder, { recursive: true, force: true }, (err) => {
if (err) {
return console.error(`Error deleting directory: ${err.message}`);
}
console.log('Directory deleted successfully: ', authStateFolder);
});
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.message', sendMessageHandler);
} 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,
};