199 lines
6.9 KiB
JavaScript
199 lines
6.9 KiB
JavaScript
import Imap from 'node-imap';
|
|
import { simpleParser } from 'mailparser';
|
|
import { GoogleGenAI } from '@google/genai';
|
|
import prisma from '../database.js';
|
|
|
|
// --- Environment Variables ---
|
|
const { GOOGLE_API_KEY } = process.env; // Added
|
|
|
|
// --- AI Setup ---
|
|
const ai = GOOGLE_API_KEY ? new GoogleGenAI({
|
|
apiKey: GOOGLE_API_KEY,
|
|
}) : null; // Added
|
|
|
|
export async function fetchAndFormatEmails() {
|
|
return new Promise((resolve, reject) => {
|
|
const imapConfig = {
|
|
user: process.env.OUTLOOK_EMAIL_ADDRESS,
|
|
password: process.env.OUTLOOK_APP_PASSWORD,
|
|
host: 'outlook.office365.com',
|
|
port: 993,
|
|
tls: true,
|
|
tlsOptions: { rejectUnauthorized: false } // Adjust as needed for your environment
|
|
};
|
|
|
|
const imap = new Imap(imapConfig);
|
|
const emailsJson = [];
|
|
|
|
function openInbox(cb) {
|
|
// Note: IMAP uses '/' as hierarchy separator, adjust if your server uses something else
|
|
imap.openBox('SLSNotifications/Reports/Backups', false, cb);
|
|
}
|
|
|
|
imap.once('ready', () => {
|
|
openInbox((err, box) => {
|
|
if (err) {
|
|
imap.end();
|
|
return reject(new Error(`Error opening mailbox: ${err.message}`));
|
|
}
|
|
|
|
const yesterday = new Date();
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
const searchCriteria = [['SINCE', yesterday.toISOString().split('T')[0]]]; // Search since midnight yesterday
|
|
const fetchOptions = { bodies: ['HEADER.FIELDS (SUBJECT DATE)', 'TEXT'], struct: true };
|
|
|
|
imap.search(searchCriteria, (searchErr, results) => {
|
|
if (searchErr) {
|
|
imap.end();
|
|
return reject(new Error(`Error searching emails: ${searchErr.message}`));
|
|
}
|
|
|
|
if (results.length === 0) {
|
|
console.log('No emails found from the last 24 hours.');
|
|
imap.end();
|
|
return resolve([]);
|
|
}
|
|
|
|
const f = imap.fetch(results, fetchOptions);
|
|
let processedCount = 0;
|
|
|
|
f.on('message', (msg, seqno) => {
|
|
let header = '';
|
|
let body = '';
|
|
|
|
msg.on('body', (stream, info) => {
|
|
let buffer = '';
|
|
stream.on('data', (chunk) => {
|
|
buffer += chunk.toString('utf8');
|
|
});
|
|
stream.once('end', () => {
|
|
if (info.which === 'TEXT') {
|
|
body = buffer;
|
|
} else {
|
|
// Assuming HEADER.FIELDS (SUBJECT DATE) comes as one chunk
|
|
header = buffer;
|
|
}
|
|
});
|
|
});
|
|
|
|
msg.once('attributes', (attrs) => {
|
|
// Attributes might contain date if not fetched via header
|
|
});
|
|
|
|
msg.once('end', async () => {
|
|
try {
|
|
// Use mailparser to handle potential encoding issues and structure
|
|
const mail = await simpleParser(`Subject: ${header.match(/Subject: (.*)/i)?.[1] || ''}\nDate: ${header.match(/Date: (.*)/i)?.[1] || ''}\n\n${body}`);
|
|
emailsJson.push({
|
|
title: mail.subject || 'No Subject',
|
|
time: mail.date ? mail.date.toISOString() : 'No Date',
|
|
body: mail.text || mail.html || 'No Body Content' // Prefer text, fallback to html, then empty
|
|
});
|
|
} catch (parseErr) {
|
|
console.error(`Error parsing email seqno ${seqno}:`, parseErr);
|
|
// Decide if you want to reject or just skip this email
|
|
}
|
|
|
|
processedCount++;
|
|
if (processedCount === results.length) {
|
|
// This check might be slightly inaccurate if errors occur,
|
|
// but it's a common pattern. Consider refining with promises.
|
|
}
|
|
});
|
|
});
|
|
|
|
f.once('error', (fetchErr) => {
|
|
console.error('Fetch error: ' + fetchErr);
|
|
// Don't reject here immediately, might still get some emails
|
|
});
|
|
|
|
f.once('end', () => {
|
|
console.log('Done fetching all messages!');
|
|
imap.end();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
imap.once('error', (err) => {
|
|
reject(new Error(`IMAP Connection Error: ${err.message}`));
|
|
});
|
|
|
|
imap.once('end', () => {
|
|
console.log('IMAP Connection ended.');
|
|
resolve(emailsJson); // Resolve with the collected emails
|
|
});
|
|
|
|
imap.connect();
|
|
});
|
|
}
|
|
|
|
// --- Email Summary Logic (New Function) ---
|
|
export async function generateAndStoreEmailSummary() {
|
|
console.log('Attempting to generate and store Email summary...');
|
|
if (!ai) {
|
|
console.error('Google AI API key not configured. Skipping email summary generation.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Get the prompt from the database settings using Prisma
|
|
const setting = await prisma.setting.findUnique({
|
|
where: { key: 'emailPrompt' }, // Use 'emailPrompt' as the key
|
|
select: { value: true }
|
|
});
|
|
const promptTemplate = setting?.value;
|
|
|
|
if (!promptTemplate) {
|
|
console.error('Email prompt not found in database settings (key: emailPrompt). Skipping summary generation.');
|
|
return;
|
|
}
|
|
|
|
const emails = await fetchAndFormatEmails();
|
|
|
|
let summaryText;
|
|
if (emails.length === 0) {
|
|
summaryText = "No relevant emails found in the last 24 hours.";
|
|
console.log('No recent emails found for summary.');
|
|
} else {
|
|
console.log(`Found ${emails.length} recent emails. Generating summary...`);
|
|
// Replace placeholder in the prompt template
|
|
// Ensure your prompt template uses $EMAIL_DATA
|
|
let prompt = promptTemplate.replaceAll("$EMAIL_DATA", JSON.stringify(emails, null, 2));
|
|
|
|
// Call the AI model (adjust model name and config as needed)
|
|
const response = await ai.models.generateContent({
|
|
"model": "gemini-2.5-preview-04-17",
|
|
"contents": prompt,
|
|
config: {
|
|
temperature: 0 // Adjust temperature as needed
|
|
}
|
|
});
|
|
|
|
summaryText = response.text;
|
|
console.log('Email summary generated successfully by AI.');
|
|
}
|
|
|
|
// Store the summary in the database using Prisma upsert
|
|
const today = new Date();
|
|
today.setUTCHours(0, 0, 0, 0); // Use UTC start of day for consistency
|
|
|
|
await prisma.emailSummary.upsert({
|
|
where: { summaryDate: today },
|
|
update: {
|
|
summaryText: summaryText,
|
|
// generatedAt is updated automatically by @default(now())
|
|
},
|
|
create: {
|
|
summaryDate: today,
|
|
summaryText: summaryText,
|
|
},
|
|
});
|
|
console.log(`Email summary for ${today.toISOString().split('T')[0]} stored/updated in the database.`);
|
|
|
|
} catch (error) {
|
|
console.error("Error during Email summary generation/storage:", error);
|
|
// Re-throw or handle as appropriate for your application
|
|
throw error;
|
|
}
|
|
}
|