require('dotenv').config(); const fs = require('fs'); const path = require('path'); const cron = require('node-cron'); const axios = require('axios'); const { google } = require('googleapis'); const TOKENS_DIR = path.join(__dirname, 'tokens'); const CREDENTIALS_PATH = path.join(__dirname, 'credentials.json'); const OLLAMA_URL = process.env.OLLAMA_URL || 'http://ollama.ai-agents.svc.cluster.local:11434/api/generate'; const WHATSAPP_GATEWAY_URL = process.env.WHATSAPP_GATEWAY_URL || 'http://whatsapp-gateway.ai-agents.svc.cluster.local:5001/api/send-message'; const WHATSAPP_TO = process.env.WHATSAPP_TO || '+14085505485'; const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'qwen2.5:0.5b'; let baseOAuth2Client; let accounts = []; function loadAuth() { if (!fs.existsSync(CREDENTIALS_PATH)) { console.error('Missing credentials.json. Please download it from Google Cloud Console and place it in the project directory.'); process.exit(1); } if (!fs.existsSync(TOKENS_DIR)) { console.error('Missing tokens directory. Please run "node auth.js " to generate an account token.'); process.exit(1); } const creds = JSON.parse(fs.readFileSync(CREDENTIALS_PATH)); const {client_secret, client_id, redirect_uris} = creds.installed || creds.web; baseOAuth2Client = { client_id, client_secret, redirect_uri: redirect_uris[0] || 'urn:ietf:wg:oauth:2.0:oob' }; const tokenFiles = fs.readdirSync(TOKENS_DIR).filter(file => file.endsWith('.json')); if (tokenFiles.length === 0) { console.error('No tokens found in tokens/. Please run "node auth.js " to generate one.'); process.exit(1); } for (const file of tokenFiles) { const accountName = file.replace('.json', ''); const tokenContent = JSON.parse(fs.readFileSync(path.join(TOKENS_DIR, file))); const client = new google.auth.OAuth2(baseOAuth2Client.client_id, baseOAuth2Client.client_secret, baseOAuth2Client.redirect_uri); client.setCredentials(tokenContent); accounts.push({ name: accountName, client }); console.log(`Loaded credentials for account: ${accountName}`); } } async function getUnreadEmails(account) { const gmail = google.gmail({ version: 'v1', auth: account.client }); const res = await gmail.users.messages.list({ userId: 'me', q: 'is:unread', maxResults: 10 }); const messages = res.data.messages || []; const emails = []; for (const msg of messages) { const fullMsg = await gmail.users.messages.get({ userId: 'me', id: msg.id, format: 'full' }); const headers = fullMsg.data.payload.headers; const subject = headers.find(h => h.name === 'Subject')?.value || 'No Subject'; const from = headers.find(h => h.name === 'From')?.value || 'Unknown'; let body = ''; if (fullMsg.data.payload.parts) { const part = fullMsg.data.payload.parts.find(p => p.mimeType === 'text/plain'); if (part && part.body.data) { body = Buffer.from(part.body.data, 'base64').toString('utf8'); } } else if (fullMsg.data.payload.body && fullMsg.data.payload.body.data) { body = Buffer.from(fullMsg.data.payload.body.data, 'base64').toString('utf8'); } emails.push({ id: msg.id, subject, from, body: body.substring(0, 1000), accountName: account.name, client: account.client }); } return emails; } async function markAsRead(email) { const gmail = google.gmail({ version: 'v1', auth: email.client }); await gmail.users.messages.modify({ userId: 'me', id: email.id, requestBody: { removeLabelIds: ['UNREAD'] } }); } async function analyzeWithOllama(email) { const prompt = `You are an AI assistant that determines if an email is important enough to notify the user immediately on WhatsApp. Newsletters, marketing, promotions, and generic updates are NOT important. Important things: Bills due, flight/travel updates, urgent requests from people, security alerts, direct personal messages. Email Subject: ${email.subject} Email From: ${email.from} Email Body Preview: ${email.body} First, determine if this is important (YES or NO). If YES, provide a 1-sentence summary of what the user needs to know. Output format exactly like this: IMPORTANT: YES/NO SUMMARY: `; try { const res = await axios.post(OLLAMA_URL, { model: OLLAMA_MODEL, prompt: prompt, stream: false }); const responseText = res.data.response || ''; const isImportant = responseText.includes('IMPORTANT: YES'); const summaryMatch = responseText.match(/SUMMARY:\s*(.*)/); const summary = summaryMatch ? summaryMatch[1].trim() : ''; return { isImportant, summary }; } catch (err) { console.error('Ollama Error:', err.message); return { isImportant: false, summary: '' }; } } async function sendWhatsAppNotification(email, summary) { const message = `šŸ“§ *[${email.accountName}] Important Email*\n*From:* ${email.from}\n*Subject:* ${email.subject}\n\nšŸ¤– *Summary:* ${summary}`; try { await axios.post(WHATSAPP_GATEWAY_URL, { to: WHATSAPP_TO, message: message }, { headers: { 'x-app-name': 'Gmail Agent' } }); console.log(`Notification sent for: [${email.accountName}] ${email.subject}`); } catch (err) { console.error('WhatsApp Gateway Error:', err.message); } } async function processEmails() { console.log(`[${new Date().toISOString()}] Checking for new emails across ${accounts.length} accounts...`); try { for (const account of accounts) { console.log(`Checking account: ${account.name}...`); const emails = await getUnreadEmails(account); if (emails.length === 0) { console.log(`No new emails for ${account.name}.`); continue; } console.log(`Found ${emails.length} unread emails for ${account.name}.`); for (const email of emails) { const analysis = await analyzeWithOllama(email); if (analysis.isImportant) { await sendWhatsAppNotification(email, analysis.summary); } else { console.log(`Skipped non-important email: ${email.subject}`); } // Mark as read so we don't process it again await markAsRead(email); } } } catch (err) { console.error('Error processing emails:', err.message); } } // Start Application try { loadAuth(); console.log('Gmail Agent started successfully.'); console.log(`Cron job scheduled to check every 15 minutes.`); // Check immediately on startup processEmails(); // Schedule every 15 minutes cron.schedule('*/15 * * * *', () => { processEmails(); }); } catch (e) { console.error('Failed to start:', e.message); }