handle email mime types (fixes #5), plus fix email display in letter view page
This commit is contained in:
parent
8b4dba62e1
commit
b5bb27e1b2
2 changed files with 70 additions and 4 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue