mail page imap connection mime 5 bis rest 16 login page 22
This commit is contained in:
parent
37fad90280
commit
450121ff23
@ -1,7 +1,7 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import Imap from 'imap';
|
||||
import nodemailer from 'nodemailer';
|
||||
import { parseEmailHeaders } from '@/lib/email-parser';
|
||||
import { parseEmailHeaders, decodeEmailBody } from '@/lib/email-parser';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
interface StoredCredentials {
|
||||
@ -153,30 +153,73 @@ export async function GET() {
|
||||
|
||||
const fetchPromise = (imap: Imap, seqno: number): Promise<ImapMessage> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const f = imap.fetch(seqno, { bodies: ['HEADER', 'TEXT'] });
|
||||
const f = imap.fetch(seqno, {
|
||||
bodies: ['HEADER', 'TEXT'],
|
||||
struct: true,
|
||||
markSeen: false
|
||||
});
|
||||
|
||||
f.on('message', (msg: ImapMessage) => {
|
||||
resolve(msg);
|
||||
});
|
||||
f.once('error', reject);
|
||||
|
||||
f.once('error', (err: Error) => {
|
||||
console.error('Fetch error:', err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const processMessage = (msg: ImapMessage): Promise<any> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let body = '';
|
||||
msg.body['TEXT'].on('data', (chunk: Buffer) => {
|
||||
body += chunk.toString('utf8');
|
||||
let headerContent = '';
|
||||
let bodyContent = '';
|
||||
let headersParsed = false;
|
||||
let bodyParsed = false;
|
||||
|
||||
// Process headers
|
||||
msg.body['HEADER']?.on('data', (chunk: Buffer) => {
|
||||
headerContent += chunk.toString('utf8');
|
||||
});
|
||||
msg.body['TEXT'].on('end', () => {
|
||||
const headers = parseEmailHeaders(body);
|
||||
resolve({
|
||||
uid: msg.attributes.uid,
|
||||
flags: msg.attributes.flags,
|
||||
size: msg.attributes.size,
|
||||
...headers
|
||||
});
|
||||
|
||||
// Process body
|
||||
msg.body['TEXT']?.on('data', (chunk: Buffer) => {
|
||||
bodyContent += chunk.toString('utf8');
|
||||
});
|
||||
msg.body['TEXT'].on('error', reject);
|
||||
|
||||
const tryResolve = () => {
|
||||
if (headersParsed && bodyParsed) {
|
||||
try {
|
||||
const headers = parseEmailHeaders(headerContent);
|
||||
const contentType = headerContent.match(/Content-Type:\s*([^;\r\n]+)/i)?.[1] || 'text/plain';
|
||||
const decodedBody = decodeEmailBody(bodyContent, contentType);
|
||||
|
||||
resolve({
|
||||
uid: msg.attributes.uid,
|
||||
flags: msg.attributes.flags,
|
||||
size: msg.attributes.size,
|
||||
body: decodedBody,
|
||||
...headers
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error processing message:', error);
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
msg.body['HEADER']?.on('end', () => {
|
||||
headersParsed = true;
|
||||
tryResolve();
|
||||
});
|
||||
|
||||
msg.body['TEXT']?.on('end', () => {
|
||||
bodyParsed = true;
|
||||
tryResolve();
|
||||
});
|
||||
|
||||
msg.body['HEADER']?.on('error', reject);
|
||||
msg.body['TEXT']?.on('error', reject);
|
||||
});
|
||||
};
|
||||
|
||||
@ -197,16 +240,23 @@ export async function GET() {
|
||||
return {
|
||||
id: msg.uid.toString(),
|
||||
from: msg.from,
|
||||
subject: msg.subject,
|
||||
date: msg.date,
|
||||
subject: msg.subject || '(No subject)',
|
||||
date: new Date(msg.date),
|
||||
read: !msg.flags?.includes('\\Unseen'),
|
||||
starred: msg.flags?.includes('\\Flagged') || false,
|
||||
body: msg.body,
|
||||
to: msg.to // add this if available
|
||||
body: msg.body || '',
|
||||
to: msg.to
|
||||
};
|
||||
});
|
||||
|
||||
return NextResponse.json(emails);
|
||||
return NextResponse.json({
|
||||
emails,
|
||||
mailUrl: process.env.NEXTCLOUD_URL ? `${process.env.NEXTCLOUD_URL}/apps/mail/` : null
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching emails:', error);
|
||||
const status = error instanceof Error && error.message.includes('Invalid login')
|
||||
|
||||
@ -1,23 +1,93 @@
|
||||
export function parseEmailHeaders(emailBody: string): { from: string; subject: string; date: string } {
|
||||
interface EmailHeaders {
|
||||
from: string;
|
||||
subject: string;
|
||||
date: string;
|
||||
to?: string;
|
||||
}
|
||||
|
||||
export function parseEmailHeaders(headerContent: string): EmailHeaders {
|
||||
const headers: { [key: string]: string } = {};
|
||||
|
||||
// Split the email body into lines
|
||||
const lines = emailBody.split('\r\n');
|
||||
|
||||
// Parse headers
|
||||
for (const line of lines) {
|
||||
if (line.trim() === '') break; // Stop at the first empty line (end of headers)
|
||||
let currentHeader = '';
|
||||
let currentValue = '';
|
||||
|
||||
// Split the header content into lines
|
||||
const lines = headerContent.split(/\r?\n/);
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
const match = line.match(/^([^:]+):\s*(.+)$/);
|
||||
// If line starts with whitespace, it's a continuation of the previous header
|
||||
if (/^\s+/.test(line)) {
|
||||
currentValue += ' ' + line.trim();
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have a current header being processed, save it
|
||||
if (currentHeader && currentValue) {
|
||||
headers[currentHeader.toLowerCase()] = currentValue.trim();
|
||||
}
|
||||
|
||||
// Start processing new header
|
||||
const match = line.match(/^([^:]+):\s*(.*)$/);
|
||||
if (match) {
|
||||
const [, key, value] = match;
|
||||
headers[key.toLowerCase()] = value;
|
||||
currentHeader = match[1];
|
||||
currentValue = match[2];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save the last header
|
||||
if (currentHeader && currentValue) {
|
||||
headers[currentHeader.toLowerCase()] = currentValue.trim();
|
||||
}
|
||||
|
||||
return {
|
||||
from: headers['from'] || '',
|
||||
subject: headers['subject'] || '',
|
||||
date: headers['date'] || new Date().toISOString()
|
||||
date: headers['date'] || new Date().toISOString(),
|
||||
to: headers['to']
|
||||
};
|
||||
}
|
||||
|
||||
export function decodeEmailBody(content: string, contentType: string): string {
|
||||
try {
|
||||
// Remove email client-specific markers
|
||||
content = content.replace(/\r\n/g, '\n')
|
||||
.replace(/=\n/g, '')
|
||||
.replace(/=3D/g, '=')
|
||||
.replace(/=09/g, '\t');
|
||||
|
||||
// If it's HTML content
|
||||
if (contentType.includes('text/html')) {
|
||||
return extractTextFromHtml(content);
|
||||
}
|
||||
|
||||
return content;
|
||||
} catch (error) {
|
||||
console.error('Error decoding email body:', error);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
function extractTextFromHtml(html: string): string {
|
||||
// Remove scripts and style tags
|
||||
html = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
|
||||
|
||||
// Convert <br> and <p> to newlines
|
||||
html = html.replace(/<br[^>]*>/gi, '\n')
|
||||
.replace(/<p[^>]*>/gi, '\n')
|
||||
.replace(/<\/p>/gi, '\n');
|
||||
|
||||
// Remove all other HTML tags
|
||||
html = html.replace(/<[^>]+>/g, '');
|
||||
|
||||
// Decode HTML entities
|
||||
html = html.replace(/ /g, ' ')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
|
||||
// Clean up whitespace
|
||||
return html.replace(/\n\s*\n/g, '\n\n').trim();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user