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
|
mountPath: /app/gmail-agent/credentials.json
|
||||||
subPath: credentials.json
|
subPath: credentials.json
|
||||||
readOnly: true
|
readOnly: true
|
||||||
- name: token-volume
|
- name: tokens-volume
|
||||||
mountPath: /app/gmail-agent/token.json
|
mountPath: /app/gmail-agent/tokens
|
||||||
subPath: token.json
|
|
||||||
readOnly: true
|
readOnly: true
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|
@ -70,9 +69,6 @@ spec:
|
||||||
items:
|
items:
|
||||||
- key: credentials.json
|
- key: credentials.json
|
||||||
path: credentials.json
|
path: credentials.json
|
||||||
- name: token-volume
|
- name: tokens-volume
|
||||||
secret:
|
secret:
|
||||||
secretName: gmail-agent-auth
|
secretName: gmail-agent-tokens
|
||||||
items:
|
|
||||||
- key: token.json
|
|
||||||
path: token.json
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,24 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
const readline = require('readline');
|
const readline = require('readline');
|
||||||
const { google } = require('googleapis');
|
const { google } = require('googleapis');
|
||||||
|
|
||||||
// If modifying these scopes, delete token.json.
|
// If modifying these scopes, delete token.json.
|
||||||
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.modify'];
|
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) => {
|
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);
|
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) => {
|
fs.readFile(TOKEN_PATH, (err, token) => {
|
||||||
if (err) return callback(oAuth2Client);
|
if (err) return callback(oAuth2Client);
|
||||||
oAuth2Client.setCredentials(JSON.parse(token));
|
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',
|
access_type: 'offline',
|
||||||
scope: SCOPES,
|
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({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout,
|
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();
|
rl.close();
|
||||||
oAuth2Client.getToken(code, (err, token) => {
|
oAuth2Client.getToken(code, (err, token) => {
|
||||||
if (err) return console.error('Error retrieving access token', err);
|
if (err) return console.error('Error retrieving access token', err);
|
||||||
oAuth2Client.setCredentials(token);
|
oAuth2Client.setCredentials(token);
|
||||||
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
|
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
|
||||||
if (err) return console.error(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 axios = require('axios');
|
||||||
const { google } = require('googleapis');
|
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 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 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 WHATSAPP_TO = process.env.WHATSAPP_TO || '+14085505485';
|
||||||
const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'qwen2.5:0.5b';
|
const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'qwen2.5:0.5b';
|
||||||
|
|
||||||
let oAuth2Client;
|
let baseOAuth2Client;
|
||||||
|
let accounts = [];
|
||||||
|
|
||||||
function loadAuth() {
|
function loadAuth() {
|
||||||
if (!fs.existsSync(CREDENTIALS_PATH)) {
|
if (!fs.existsSync(CREDENTIALS_PATH)) {
|
||||||
console.error('Missing credentials.json. Please download it from Google Cloud Console and place it in the project directory.');
|
console.error('Missing credentials.json. Please download it from Google Cloud Console and place it in the project directory.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
if (!fs.existsSync(TOKEN_PATH)) {
|
if (!fs.existsSync(TOKENS_DIR)) {
|
||||||
console.error('Missing token.json. Please run "node auth.js" to generate it.');
|
console.error('Missing tokens directory. Please run "node auth.js <account-name>" to generate an account token.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const creds = JSON.parse(fs.readFileSync(CREDENTIALS_PATH));
|
const creds = JSON.parse(fs.readFileSync(CREDENTIALS_PATH));
|
||||||
const {client_secret, client_id, redirect_uris} = creds.installed || creds.web;
|
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');
|
baseOAuth2Client = { client_id, client_secret, redirect_uri: redirect_uris[0] || 'urn:ietf:wg:oauth:2.0:oob' };
|
||||||
oAuth2Client.setCredentials(JSON.parse(fs.readFileSync(TOKEN_PATH)));
|
|
||||||
|
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() {
|
async function getUnreadEmails(account) {
|
||||||
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
|
const gmail = google.gmail({ version: 'v1', auth: account.client });
|
||||||
const res = await gmail.users.messages.list({
|
const res = await gmail.users.messages.list({
|
||||||
userId: 'me',
|
userId: 'me',
|
||||||
q: 'is:unread',
|
q: 'is:unread',
|
||||||
|
|
@ -57,16 +75,16 @@ async function getUnreadEmails() {
|
||||||
body = Buffer.from(fullMsg.data.payload.body.data, 'base64').toString('utf8');
|
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;
|
return emails;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function markAsRead(msgId) {
|
async function markAsRead(email) {
|
||||||
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
|
const gmail = google.gmail({ version: 'v1', auth: email.client });
|
||||||
await gmail.users.messages.modify({
|
await gmail.users.messages.modify({
|
||||||
userId: 'me',
|
userId: 'me',
|
||||||
id: msgId,
|
id: email.id,
|
||||||
requestBody: { removeLabelIds: ['UNREAD'] }
|
requestBody: { removeLabelIds: ['UNREAD'] }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +126,7 @@ SUMMARY: <your summary if yes, otherwise leave blank>
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendWhatsAppNotification(email, 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 {
|
try {
|
||||||
await axios.post(WHATSAPP_GATEWAY_URL, {
|
await axios.post(WHATSAPP_GATEWAY_URL, {
|
||||||
|
|
@ -117,32 +135,35 @@ async function sendWhatsAppNotification(email, summary) {
|
||||||
}, {
|
}, {
|
||||||
headers: { 'x-app-name': 'Gmail Agent' }
|
headers: { 'x-app-name': 'Gmail Agent' }
|
||||||
});
|
});
|
||||||
console.log(`Notification sent for: ${email.subject}`);
|
console.log(`Notification sent for: [${email.accountName}] ${email.subject}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('WhatsApp Gateway Error:', err.message);
|
console.error('WhatsApp Gateway Error:', err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processEmails() {
|
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 {
|
try {
|
||||||
const emails = await getUnreadEmails();
|
for (const account of accounts) {
|
||||||
if (emails.length === 0) {
|
console.log(`Checking account: ${account.name}...`);
|
||||||
console.log('No new emails.');
|
const emails = await getUnreadEmails(account);
|
||||||
return;
|
if (emails.length === 0) {
|
||||||
}
|
console.log(`No new emails for ${account.name}.`);
|
||||||
|
continue;
|
||||||
console.log(`Found ${emails.length} unread emails.`);
|
}
|
||||||
|
|
||||||
for (const email of emails) {
|
console.log(`Found ${emails.length} unread emails for ${account.name}.`);
|
||||||
const analysis = await analyzeWithOllama(email);
|
|
||||||
if (analysis.isImportant) {
|
for (const email of emails) {
|
||||||
await sendWhatsAppNotification(email, analysis.summary);
|
const analysis = await analyzeWithOllama(email);
|
||||||
} else {
|
if (analysis.isImportant) {
|
||||||
console.log(`Skipped non-important email: ${email.subject}`);
|
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) {
|
} catch (err) {
|
||||||
console.error('Error processing emails:', err.message);
|
console.error('Error processing emails:', err.message);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue