feat: initial setup with WhatsApp CRM apps and sync services
This commit is contained in:
10
apps/whatsapp-reader/.gitignore
vendored
Normal file
10
apps/whatsapp-reader/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
node_modules/
|
||||
.wwebjs_auth/
|
||||
.wwebjs_cache/
|
||||
data/
|
||||
*.db
|
||||
*.sqlite
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
.DS_Store
|
||||
137
apps/whatsapp-reader/index.js
Normal file
137
apps/whatsapp-reader/index.js
Normal file
@@ -0,0 +1,137 @@
|
||||
const { Client, LocalAuth } = require('whatsapp-web.js');
|
||||
const qrcode = require('qrcode-terminal');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BASE_DIR = path.join(__dirname);
|
||||
const DATA_DIR = path.join(BASE_DIR, 'data');
|
||||
const MESSAGES_FILE = path.join(DATA_DIR, 'messages.jsonl');
|
||||
|
||||
console.log('Data directory:', DATA_DIR);
|
||||
|
||||
const client = new Client({
|
||||
authStrategy: new LocalAuth(),
|
||||
puppeteer: {
|
||||
executablePath: '/usr/sbin/chromium',
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Creating data dir:', DATA_DIR);
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
console.log('Data dir created');
|
||||
|
||||
function formatMessage(msg, chat) {
|
||||
const phoneNumber = msg.from ? msg.from.split('@')[0] : '';
|
||||
return {
|
||||
id: msg.id._serialized,
|
||||
chatId: chat.id._serialized,
|
||||
chatName: chat.name,
|
||||
from: phoneNumber,
|
||||
fromName: msg.author || phoneNumber,
|
||||
body: msg.body || '',
|
||||
timestamp: msg.timestamp,
|
||||
type: msg.type,
|
||||
hasMedia: msg.hasMedia,
|
||||
isGroup: chat.isGroup,
|
||||
isMe: msg.fromMe
|
||||
};
|
||||
}
|
||||
|
||||
// NOTE: Limited to top 5 messages for testing - remove/comment this block to collect all messages
|
||||
// CHANGES:
|
||||
// - Added messageCount to track messages
|
||||
// - Added MAX_MESSAGES = 5 limit
|
||||
// - Added MAX_MESSAGES_REACHED check
|
||||
// - Modified saveMessage to count and stop at limit
|
||||
let messageCount = 0;
|
||||
const MAX_MESSAGES = 5;
|
||||
const MAX_MESSAGES_REACHED = () => {
|
||||
if (messageCount >= MAX_MESSAGES) {
|
||||
console.log(`\n📝 Reached limit of ${MAX_MESSAGES} messages - stopping collection`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
function saveMessage(msgData) {
|
||||
// DEBUG: Limited message collection for testing
|
||||
messageCount++;
|
||||
console.log(` (${messageCount}/${MAX_MESSAGES} messages collected)`);
|
||||
|
||||
if (MAX_MESSAGES_REACHED()) return; // Stop after reaching limit
|
||||
|
||||
const line = JSON.stringify(msgData) + '\n';
|
||||
fs.appendFileSync(MESSAGES_FILE, line);
|
||||
}
|
||||
|
||||
client.on('qr', (qr) => {
|
||||
qrcode.generate(qr, { small: true });
|
||||
});
|
||||
|
||||
client.on('ready', async () => {
|
||||
console.log('✅ Connected!');
|
||||
|
||||
const chats = await client.getChats();
|
||||
console.log(`\n📱 ${chats.length} conversations loaded:\n`);
|
||||
chats.forEach((chat, i) => {
|
||||
console.log(`[${i}] ${chat.name} (${chat.unreadCount} unread)`);
|
||||
});
|
||||
|
||||
for (const chat of chats) {
|
||||
const msgs = await chat.fetchMessages({ limit: 10 });
|
||||
for (const msg of msgs) {
|
||||
if (msg.body && msg.body.trim().length > 0) {
|
||||
const msgData = formatMessage(msg, chat);
|
||||
const contact = {
|
||||
phone: msgData.from,
|
||||
name: chat.name
|
||||
};
|
||||
try {
|
||||
const res = await fetch('http://localhost:3000/api/contacts', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(contact)
|
||||
});
|
||||
if (res.ok) {
|
||||
console.log(` 📇 Contact synced: ${chat.name}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// skip duplicates/errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.on('message', async (msg) => {
|
||||
const chat = await msg.getChat();
|
||||
const msgData = formatMessage(msg, chat);
|
||||
|
||||
console.log(`💬 [${chat.name}] ${msg.from}: ${msg.body.substring(0, 50)}`);
|
||||
|
||||
saveMessage(msgData);
|
||||
|
||||
// Only create contact if there's actual message content (not system notifications)
|
||||
if (msg.body && msg.body.trim().length > 0) {
|
||||
const contact = {
|
||||
phone: msgData.from,
|
||||
name: chat.name
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch('http://localhost:3000/api/contacts', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(contact)
|
||||
});
|
||||
if (res.ok) {
|
||||
console.log(` 📇 Contact created in CRM`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(' (CRM sync skipped)');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.initialize();
|
||||
2110
apps/whatsapp-reader/package-lock.json
generated
Normal file
2110
apps/whatsapp-reader/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
apps/whatsapp-reader/package.json
Normal file
17
apps/whatsapp-reader/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "whatsapp-reader",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"whatsapp-web.js": "^1.34.6"
|
||||
}
|
||||
}
|
||||
90
apps/whatsapp-reader/sync.js
Normal file
90
apps/whatsapp-reader/sync.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const { Client, LocalAuth } = require('whatsapp-web.js');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const DATA_DIR = './data';
|
||||
const MESSAGES_FILE = path.join(DATA_DIR, 'messages.jsonl');
|
||||
const CHATS_FILE = path.join(DATA_DIR, 'chats.json');
|
||||
const LIMIT_PER_CHAT = 50; // messages to fetch per chat per run
|
||||
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
|
||||
// Load already saved message IDs to avoid duplicates
|
||||
function loadSavedIds() {
|
||||
if (!fs.existsSync(MESSAGES_FILE)) return new Set();
|
||||
const lines = fs.readFileSync(MESSAGES_FILE, 'utf8').trim().split('\n').filter(Boolean);
|
||||
return new Set(lines.map(l => JSON.parse(l).id));
|
||||
}
|
||||
|
||||
// Append new messages to jsonl
|
||||
function saveMessages(messages) {
|
||||
const lines = messages.map(m => JSON.stringify(m)).join('\n') + '\n';
|
||||
fs.appendFileSync(MESSAGES_FILE, lines);
|
||||
}
|
||||
|
||||
// Save chat index
|
||||
function saveChats(chats) {
|
||||
fs.writeFileSync(CHATS_FILE, JSON.stringify(chats, null, 2));
|
||||
}
|
||||
|
||||
const client = new Client({
|
||||
authStrategy: new LocalAuth(),
|
||||
puppeteer: {
|
||||
executablePath: '/usr/sbin/chromium',
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
|
||||
}
|
||||
});
|
||||
|
||||
client.on('qr', (qr) => {
|
||||
// Only needed first time — after that LocalAuth handles it
|
||||
const qrcode = require('qrcode-terminal');
|
||||
qrcode.generate(qr, { small: true });
|
||||
});
|
||||
|
||||
client.on('ready', async () => {
|
||||
console.log('✅ Connected — starting sync...');
|
||||
|
||||
const savedIds = loadSavedIds();
|
||||
const chats = await client.getChats();
|
||||
const chatIndex = [];
|
||||
const newMessages = [];
|
||||
|
||||
for (const chat of chats) {
|
||||
console.log(`📥 Fetching: ${chat.name}`);
|
||||
chatIndex.push({
|
||||
id: chat.id._serialized,
|
||||
name: chat.name,
|
||||
isGroup: chat.isGroup,
|
||||
lastSync: new Date().toISOString()
|
||||
});
|
||||
|
||||
try {
|
||||
const messages = await chat.fetchMessages({ limit: LIMIT_PER_CHAT });
|
||||
for (const msg of messages) {
|
||||
if (savedIds.has(msg.id._serialized)) continue; // skip duplicates
|
||||
newMessages.push({
|
||||
id: msg.id._serialized,
|
||||
chat: chat.name,
|
||||
chatId: chat.id._serialized,
|
||||
from: msg.from,
|
||||
fromName: msg.author || msg.from,
|
||||
timestamp: msg.timestamp,
|
||||
date: new Date(msg.timestamp * 1000).toISOString(),
|
||||
body: msg.body,
|
||||
type: msg.type,
|
||||
isGroup: chat.isGroup,
|
||||
hasMedia: msg.hasMedia
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`❌ Failed on ${chat.name}:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
saveChats(chatIndex);
|
||||
saveMessages(newMessages);
|
||||
console.log(`✅ Sync done — ${newMessages.length} new messages saved.`);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
client.initialize();
|
||||
Reference in New Issue
Block a user