diff --git a/agents/k8s/gmail-agent-deployment.yaml b/agents/k8s/gmail-agent-deployment.yaml index 348abd9..26c74dd 100644 --- a/agents/k8s/gmail-agent-deployment.yaml +++ b/agents/k8s/gmail-agent-deployment.yaml @@ -50,9 +50,8 @@ spec: mountPath: /app/gmail-agent/credentials.json subPath: credentials.json readOnly: true - - name: token-volume - mountPath: /app/gmail-agent/token.json - subPath: token.json + - name: tokens-volume + mountPath: /app/gmail-agent/tokens readOnly: true resources: requests: @@ -70,9 +69,6 @@ spec: items: - key: credentials.json path: credentials.json - - name: token-volume + - name: tokens-volume secret: - secretName: gmail-agent-auth - items: - - key: token.json - path: token.json + secretName: gmail-agent-tokens diff --git a/gmail-agent/auth.js b/gmail-agent/auth.js index e56e0a9..b9d53fa 100644 --- a/gmail-agent/auth.js +++ b/gmail-agent/auth.js @@ -1,10 +1,24 @@ const fs = require('fs'); +const path = require('path'); const readline = require('readline'); const { google } = require('googleapis'); // If modifying these scopes, delete token.json. const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.modify']; -const TOKEN_PATH = 'token.json'; + +const accountName = process.argv[2]; +if (!accountName) { + console.log("Usage: node auth.js "); + console.log("Example: node auth.js work"); + process.exit(1); +} + +const TOKENS_DIR = path.join(__dirname, 'tokens'); +if (!fs.existsSync(TOKENS_DIR)) { + fs.mkdirSync(TOKENS_DIR); +} + +const TOKEN_PATH = path.join(TOKENS_DIR, `${accountName}.json`); fs.readFile('credentials.json', (err, content) => { if (err) return console.log('Error loading credentials.json file. Make sure you downloaded it from Google Cloud Console:', err); @@ -18,7 +32,7 @@ function authorize(credentials, callback) { fs.readFile(TOKEN_PATH, (err, token) => { if (err) return callback(oAuth2Client); oAuth2Client.setCredentials(JSON.parse(token)); - console.log("Token already exists and is valid. You don't need to authenticate again."); + console.log(`Token for account '${accountName}' already exists and is valid. You don't need to authenticate again.`); }); } @@ -27,19 +41,20 @@ function getNewToken(oAuth2Client) { access_type: 'offline', scope: SCOPES, }); - console.log('Authorize this app by visiting this url:', authUrl); + console.log(`\n=== Authorizing Account: ${accountName} ===\n`); + console.log('Authorize this app by visiting this url:\n', authUrl); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); - rl.question('Enter the code from that page here: ', (code) => { + rl.question('\nEnter the code from that page here: ', (code) => { rl.close(); oAuth2Client.getToken(code, (err, token) => { if (err) return console.error('Error retrieving access token', err); oAuth2Client.setCredentials(token); fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => { if (err) return console.error(err); - console.log('Token stored to', TOKEN_PATH); + console.log(`Token stored successfully to ${TOKEN_PATH}`); }); }); }); diff --git a/gmail-agent/index.js b/gmail-agent/index.js index 6397468..172170c 100644 --- a/gmail-agent/index.js +++ b/gmail-agent/index.js @@ -5,7 +5,7 @@ const cron = require('node-cron'); const axios = require('axios'); const { google } = require('googleapis'); -const TOKEN_PATH = path.join(__dirname, 'token.json'); +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'; @@ -13,25 +13,43 @@ const WHATSAPP_GATEWAY_URL = process.env.WHATSAPP_GATEWAY_URL || 'http://whatsap const WHATSAPP_TO = process.env.WHATSAPP_TO || '+14085505485'; const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'qwen2.5:0.5b'; -let oAuth2Client; +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(TOKEN_PATH)) { - console.error('Missing token.json. Please run "node auth.js" to generate it.'); + 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; - 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))); + 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() { - const gmail = google.gmail({ version: 'v1', auth: oAuth2Client }); +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', @@ -57,16 +75,16 @@ async function getUnreadEmails() { body = Buffer.from(fullMsg.data.payload.body.data, 'base64').toString('utf8'); } - emails.push({ id: msg.id, subject, from, body: body.substring(0, 1000) }); + emails.push({ id: msg.id, subject, from, body: body.substring(0, 1000), accountName: account.name, client: account.client }); } return emails; } -async function markAsRead(msgId) { - const gmail = google.gmail({ version: 'v1', auth: oAuth2Client }); +async function markAsRead(email) { + const gmail = google.gmail({ version: 'v1', auth: email.client }); await gmail.users.messages.modify({ userId: 'me', - id: msgId, + id: email.id, requestBody: { removeLabelIds: ['UNREAD'] } }); } @@ -108,7 +126,7 @@ SUMMARY: } async function sendWhatsAppNotification(email, summary) { - const message = `šŸ“§ *Important Email*\n*From:* ${email.from}\n*Subject:* ${email.subject}\n\nšŸ¤– *Summary:* ${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, { @@ -117,32 +135,35 @@ async function sendWhatsAppNotification(email, summary) { }, { headers: { 'x-app-name': 'Gmail Agent' } }); - console.log(`Notification sent for: ${email.subject}`); + 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...`); + console.log(`[${new Date().toISOString()}] Checking for new emails across ${accounts.length} accounts...`); 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}`); + 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); } - // 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);