Add leads handlers, WhatsApp integration, and test files
This commit is contained in:
176
apps/whatsapp-sync/src/whatsapp-leads.js
Normal file
176
apps/whatsapp-sync/src/whatsapp-leads.js
Normal file
@@ -0,0 +1,176 @@
|
||||
const { Client, LocalAuth } = require('whatsapp-web.js');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
|
||||
function normalizePhone(phone) {
|
||||
if (!phone) return '';
|
||||
phone = phone.replace(/[^\d+]/g, '');
|
||||
if (!phone.startsWith('+')) {
|
||||
if (phone.length === 10) {
|
||||
return '+1' + phone;
|
||||
} else if (phone.length === 11) {
|
||||
return '+' + phone;
|
||||
}
|
||||
}
|
||||
return phone;
|
||||
}
|
||||
|
||||
function isGroupChat(msg) {
|
||||
return msg.key?.remoteJid?.includes('@g');
|
||||
}
|
||||
|
||||
module.exports = { normalizePhone, isGroupChat };
|
||||
|
||||
var clientID, goEndpoint, internalSecret;
|
||||
|
||||
function logEvent(event, data) {
|
||||
console.log(JSON.stringify({
|
||||
timestamp: Date.now(),
|
||||
event,
|
||||
client_id: clientID,
|
||||
...data,
|
||||
}));
|
||||
}
|
||||
|
||||
function postCustomer(data) {
|
||||
const postData = JSON.stringify(data);
|
||||
const url = new URL('/customers', goEndpoint);
|
||||
|
||||
const options = {
|
||||
hostname: url.hostname,
|
||||
port: url.port || 8080,
|
||||
path: url.pathname,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(postData),
|
||||
'X-Internal-Secret': internalSecret,
|
||||
},
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let body = '';
|
||||
res.on('data', chunk => body += chunk);
|
||||
res.on('end', () => {
|
||||
logEvent('customer_synced', { phone: data.phone, success: res.statusCode === 200 });
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (err) => {
|
||||
logEvent('error', { message: err.message });
|
||||
});
|
||||
|
||||
req.write(postData);
|
||||
req.end();
|
||||
}
|
||||
|
||||
async function syncChat(client, chat) {
|
||||
const messages = await chat.fetchMessages({ limit: 100 });
|
||||
|
||||
for (const msg of messages) {
|
||||
if (!msg.fromMe && msg.type === 'chat') {
|
||||
const phone = normalizePhone(msg.from);
|
||||
if (phone) {
|
||||
postCustomer({
|
||||
client_id: parseInt(clientID),
|
||||
name: phone,
|
||||
phone: phone,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
clientID = args.find(a => a.startsWith('--client-id='))?.split('=')[1] || args[0];
|
||||
goEndpoint = args.find(a => a.startsWith('--go-endpoint='))?.split('=')[1] || 'http://localhost:8080';
|
||||
internalSecret = args.find(a => a.startsWith('--secret='))?.split('=')[1] || 'internal-secret';
|
||||
const authDir = process.env.WA_AUTH_DIR || path.join('.wwebjs_auth', clientID);
|
||||
|
||||
if (!fs.existsSync(authDir)) {
|
||||
fs.mkdirSync(authDir, { recursive: true });
|
||||
}
|
||||
|
||||
logEvent('starting', { auth_dir: authDir });
|
||||
|
||||
const client = new Client({
|
||||
authStrategy: new LocalAuth({
|
||||
dataPath: authDir,
|
||||
}),
|
||||
puppeteer: {
|
||||
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/chromium',
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||
},
|
||||
});
|
||||
|
||||
client.on('qr', (qr) => {
|
||||
logEvent('qr', { qr });
|
||||
});
|
||||
|
||||
client.on('ready', async () => {
|
||||
logEvent('ready', { timestamp: Date.now() });
|
||||
|
||||
const chats = await client.getChats();
|
||||
for (const chat of chats) {
|
||||
const isGroup = chat.id.server === 'g';
|
||||
if (!isGroup) {
|
||||
await syncChat(client, chat);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.on('message', async (msg) => {
|
||||
if (msg.fromMe) return;
|
||||
|
||||
const isGroup = msg.key.remoteJid?.includes('@g');
|
||||
if (isGroup) {
|
||||
logEvent('group_skipped', { from: msg.from });
|
||||
return;
|
||||
}
|
||||
|
||||
const phone = normalizePhone(msg.from);
|
||||
if (phone) {
|
||||
postCustomer({
|
||||
client_id: parseInt(clientID),
|
||||
name: phone,
|
||||
phone: phone,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
client.on('disconnected', (reason) => {
|
||||
logEvent('disconnected', { reason: String(reason) });
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
client.on('auth_failure', (err) => {
|
||||
logEvent('auth_failure', { error: String(err), stack: err?.stack });
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
client.on('authenticated', () => {
|
||||
logEvent('authenticated', { timestamp: Date.now() });
|
||||
});
|
||||
|
||||
client.on('authed', () => {
|
||||
logEvent('authed', { timestamp: Date.now() });
|
||||
});
|
||||
|
||||
client.on('error', (err) => {
|
||||
logEvent('client_error', { error: String(err), stack: err?.stack });
|
||||
});
|
||||
|
||||
try {
|
||||
await client.initialize();
|
||||
} catch (err) {
|
||||
logEvent('error', { message: err.message });
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
50
apps/whatsapp-sync/tests/whatsapp-leads.test.js
Normal file
50
apps/whatsapp-sync/tests/whatsapp-leads.test.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
describe('whatsapp-leads.js module', () => {
|
||||
const scriptPath = path.join(__dirname, '../src/whatsapp-leads.js');
|
||||
|
||||
test('script file exists', () => {
|
||||
expect(fs.existsSync(scriptPath)).toBe(true);
|
||||
});
|
||||
|
||||
test('exports normalizePhone function', () => {
|
||||
const script = require(scriptPath);
|
||||
expect(typeof script.normalizePhone).toBe('function');
|
||||
});
|
||||
|
||||
test('exports isGroupChat function', () => {
|
||||
const script = require(scriptPath);
|
||||
expect(typeof script.isGroupChat).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhatsApp message filtering', () => {
|
||||
const scriptPath = path.join(__dirname, '../src/whatsapp-leads.js');
|
||||
|
||||
test('filters out group messages (only 1:1 chats)', () => {
|
||||
const groupMessage = {
|
||||
key: { remoteJid: '1234567890@g group' },
|
||||
message: { conversation: 'Group message' },
|
||||
};
|
||||
|
||||
const personalMessage = {
|
||||
key: { remoteJid: '1234567890@c.us' },
|
||||
message: { conversation: 'Personal message' },
|
||||
};
|
||||
|
||||
expect(isGroupChat(groupMessage)).toBe(true);
|
||||
expect(isGroupChat(personalMessage)).toBe(false);
|
||||
});
|
||||
|
||||
test('normalizes phone numbers', () => {
|
||||
const script = require(scriptPath);
|
||||
expect(script.normalizePhone('+5521987654321')).toBe('+5521987654321');
|
||||
expect(script.normalizePhone('21987654321')).toBe('+21987654321');
|
||||
expect(script.normalizePhone('')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
function isGroupChat(message) {
|
||||
return message.key.remoteJid?.includes('@g');
|
||||
}
|
||||
Reference in New Issue
Block a user