handle email mime types (fixes #5), plus fix email display in letter view page

This commit is contained in:
Danny Morabito 2024-11-28 23:53:42 +01:00
parent 8b4dba62e1
commit b5bb27e1b2
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
2 changed files with 70 additions and 4 deletions

View file

@ -2,6 +2,66 @@ import NDK, { NDKEvent, type NDKEventId, NDKKind, type NDKUser } from '@nostr-de
import { isValidNip05 } from '$lib/utils.svelte'; import { isValidNip05 } from '$lib/utils.svelte';
import { TokenInfoWithMailSubscriptionDuration } from '@arx/utils'; import { TokenInfoWithMailSubscriptionDuration } from '@arx/utils';
function parseTextPlainContent(mimeMessage: string) {
const boundary = mimeMessage.split('\n')[0];
// --7408fffb1a494ae39c2dde2c60878849
// Content-Type: text/plain
// Content-Transfer-Encoding: 7bit
//
// test
// --7408fffb1a494ae39c2dde2c60878849
// Content-Type: text/html
// Content-Transfer-Encoding: 7bit
const boundaries = mimeMessage.split(boundary + '\n');
boundaries.shift();
const bodies = [];
for (const body of boundaries) {
const headers: Record<string, string> = {};
const lines = body.split('\r\n');
let isLookingForHeaders = true;
let content = '';
for (const line of lines) {
if (line.trim() === '') {
isLookingForHeaders = false;
content += line + '\n';
} else {
if (!isLookingForHeaders) {
content += line + '\n';
continue;
}
const [key, value] = line.split(': ');
headers[key.toLowerCase()] = value;
}
}
bodies.push({ headers, content: content.trim() });
}
if (bodies.length === 0) return mimeMessage;
let chosenBody = bodies.find((body) => body.headers['Content-Type'] === 'text/plain');
if (!chosenBody) chosenBody = bodies[0];
const encoding = chosenBody.headers['content-transfer-encoding'];
if (!encoding) return chosenBody.content; // assume plain text
switch (encoding.toLowerCase()) {
case '7bit':
return chosenBody.content;
case 'base64':
return atob(chosenBody.content.replaceAll('\n', ''));
case 'quoted-printable':
return chosenBody.content
.replace(/=\r\n/g, '')
.replace(/=([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(parseInt(p1, 16)));
default:
return `Cannot decode ${encoding} encoded message. This is a bug, please report it.`;
}
}
export class Letter { export class Letter {
id: NDKEventId; id: NDKEventId;
subject: string; subject: string;
@ -28,13 +88,19 @@ export class Letter {
): Promise<Letter> { ): Promise<Letter> {
if (decryptedMessage.kind !== NDKKind.Article) throw new Error('Not a letter'); if (decryptedMessage.kind !== NDKKind.Article) throw new Error('Not a letter');
let rawContent = decryptedMessage.content;
let content = '';
let email = decryptedMessage.tags.find((t) => t[0] === 'email:from')?.[1];
if (rawContent.startsWith('--') && email) content = parseTextPlainContent(rawContent);
else content = rawContent;
const letter = new Letter(); const letter = new Letter();
letter.id = encryptedMessage.id; letter.id = encryptedMessage.id;
letter.subject = decryptedMessage.tags.find((t) => t[0] === 'subject')?.[1] ?? 'No subject'; letter.subject = decryptedMessage.tags.find((t) => t[0] === 'subject')?.[1] ?? 'No subject';
letter.from = decryptedMessage.author; letter.from = decryptedMessage.author;
letter.content = decryptedMessage.content; letter.content = content;
letter.date = new Date(decryptedMessage.created_at! * 1000); letter.date = new Date(decryptedMessage.created_at! * 1000);
letter.emailAddress = decryptedMessage.tags.find((t) => t[0] === 'email:from')?.[1]; letter.emailAddress = email;
let stampToken = decryptedMessage.tags.find((t) => t[0] === 'stamp')?.[1]; let stampToken = decryptedMessage.tags.find((t) => t[0] === 'stamp')?.[1];
if (stampToken) { if (stampToken) {
try { try {

View file

@ -70,13 +70,13 @@
<div class="sender-recipient"> <div class="sender-recipient">
<div class="user-row"> <div class="user-row">
<span class="label">From:</span> <span class="label">From:</span>
<NostrIdentifier user={letter.from.npub} /> <NostrIdentifier user={letter.from.npub} emailAddress={letter.emailAddress} />
</div> </div>
<div class="user-row"> <div class="user-row">
<span class="label">To:</span> <span class="label">To:</span>
<div class="recipients"> <div class="recipients">
{#each letter.recipients as recipient} {#each letter.recipients as recipient}
<NostrIdentifier user={recipient.npub} /> <NostrIdentifier user={recipient.npub} emailAddress={recipient.emailAddress} />
{/each} {/each}
</div> </div>
</div> </div>