mail page imap connection mime 5 bis rest 16 login page 22

This commit is contained in:
alma 2025-04-16 10:06:27 +02:00
parent 37fad90280
commit 450121ff23
2 changed files with 153 additions and 33 deletions

View File

@ -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')

View File

@ -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(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"');
// Clean up whitespace
return html.replace(/\n\s*\n/g, '\n\n').trim();
}