Add multiple account support to gmail-agent
This commit is contained in:
parent
4fbfdf13e7
commit
91d19afa7b
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 <account-name>");
|
||||
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}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 <account-name>" 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 <account-name>" 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: <your summary if yes, otherwise leave blank>
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue