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 { NextResponse } from 'next/server';
|
||||||
import Imap from 'imap';
|
import Imap from 'imap';
|
||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import { parseEmailHeaders } from '@/lib/email-parser';
|
import { parseEmailHeaders, decodeEmailBody } from '@/lib/email-parser';
|
||||||
import { cookies } from 'next/headers';
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
interface StoredCredentials {
|
interface StoredCredentials {
|
||||||
@ -153,30 +153,73 @@ export async function GET() {
|
|||||||
|
|
||||||
const fetchPromise = (imap: Imap, seqno: number): Promise<ImapMessage> => {
|
const fetchPromise = (imap: Imap, seqno: number): Promise<ImapMessage> => {
|
||||||
return new Promise((resolve, reject) => {
|
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) => {
|
f.on('message', (msg: ImapMessage) => {
|
||||||
resolve(msg);
|
resolve(msg);
|
||||||
});
|
});
|
||||||
f.once('error', reject);
|
|
||||||
|
f.once('error', (err: Error) => {
|
||||||
|
console.error('Fetch error:', err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const processMessage = (msg: ImapMessage): Promise<any> => {
|
const processMessage = (msg: ImapMessage): Promise<any> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let body = '';
|
let headerContent = '';
|
||||||
msg.body['TEXT'].on('data', (chunk: Buffer) => {
|
let bodyContent = '';
|
||||||
body += chunk.toString('utf8');
|
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);
|
// Process body
|
||||||
|
msg.body['TEXT']?.on('data', (chunk: Buffer) => {
|
||||||
|
bodyContent += chunk.toString('utf8');
|
||||||
|
});
|
||||||
|
|
||||||
|
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({
|
resolve({
|
||||||
uid: msg.attributes.uid,
|
uid: msg.attributes.uid,
|
||||||
flags: msg.attributes.flags,
|
flags: msg.attributes.flags,
|
||||||
size: msg.attributes.size,
|
size: msg.attributes.size,
|
||||||
|
body: decodedBody,
|
||||||
...headers
|
...headers
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing message:', error);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
msg.body['HEADER']?.on('end', () => {
|
||||||
|
headersParsed = true;
|
||||||
|
tryResolve();
|
||||||
});
|
});
|
||||||
msg.body['TEXT'].on('error', reject);
|
|
||||||
|
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 {
|
return {
|
||||||
id: msg.uid.toString(),
|
id: msg.uid.toString(),
|
||||||
from: msg.from,
|
from: msg.from,
|
||||||
subject: msg.subject,
|
subject: msg.subject || '(No subject)',
|
||||||
date: msg.date,
|
date: new Date(msg.date),
|
||||||
read: !msg.flags?.includes('\\Unseen'),
|
read: !msg.flags?.includes('\\Unseen'),
|
||||||
starred: msg.flags?.includes('\\Flagged') || false,
|
starred: msg.flags?.includes('\\Flagged') || false,
|
||||||
body: msg.body,
|
body: msg.body || '',
|
||||||
to: msg.to // add this if available
|
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) {
|
} catch (error) {
|
||||||
console.error('Error fetching emails:', error);
|
console.error('Error fetching emails:', error);
|
||||||
const status = error instanceof Error && error.message.includes('Invalid login')
|
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 {
|
||||||
const headers: { [key: string]: string } = {};
|
from: string;
|
||||||
|
subject: string;
|
||||||
// Split the email body into lines
|
date: string;
|
||||||
const lines = emailBody.split('\r\n');
|
to?: string;
|
||||||
|
|
||||||
// Parse headers
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.trim() === '') break; // Stop at the first empty line (end of headers)
|
|
||||||
|
|
||||||
const match = line.match(/^([^:]+):\s*(.+)$/);
|
|
||||||
if (match) {
|
|
||||||
const [, key, value] = match;
|
|
||||||
headers[key.toLowerCase()] = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseEmailHeaders(headerContent: string): EmailHeaders {
|
||||||
|
const headers: { [key: string]: string } = {};
|
||||||
|
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];
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
currentHeader = match[1];
|
||||||
|
currentValue = match[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the last header
|
||||||
|
if (currentHeader && currentValue) {
|
||||||
|
headers[currentHeader.toLowerCase()] = currentValue.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
from: headers['from'] || '',
|
from: headers['from'] || '',
|
||||||
subject: headers['subject'] || '',
|
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