agentic-os/gmail-agent/index.js

168 lines
6.0 KiB
JavaScript

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 TOKEN_PATH = path.join(__dirname, 'token.json');
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 oAuth2Client;
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(TOKEN_PATH)) {
console.error('Missing token.json. Please run "node auth.js" to generate it.');
process.exit(1);
}
const creds = JSON.parse(fs.readFileSync(CREDENTIALS_PATH));
const {client_secret, client_id, redirect_uris} = creds.installed || creds.web;
oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0] || 'urn:ietf:wg:oauth:2.0:oob');
oAuth2Client.setCredentials(JSON.parse(fs.readFileSync(TOKEN_PATH)));
}
async function getUnreadEmails() {
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
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) });
}
return emails;
}
async function markAsRead(msgId) {
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
await gmail.users.messages.modify({
userId: 'me',
id: msgId,
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: <your summary if yes, otherwise leave blank>
`;
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 = `📧 *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.subject}`);
} catch (err) {
console.error('WhatsApp Gateway Error:', err.message);
}
}
async function processEmails() {
console.log(`[${new Date().toISOString()}] Checking for new emails...`);
try {
const emails = await getUnreadEmails();
if (emails.length === 0) {
console.log('No new emails.');
return;
}
console.log(`Found ${emails.length} unread emails.`);
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 (or add a label, but reading it is safest to prevent loops)
await markAsRead(email.id);
}
} 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);
}