import { Event, nip04 } from 'nostr-tools' import { SimplePool } from 'nostr-tools' import { decryptArticleContent, type DecryptionKey } from './articleEncryption' import { getPrimaryRelaySync } from './config' function createPrivateMessageFilters(eventId: string, publicKey: string, authorPubkey: string) { return [ { kinds: [4], // Encrypted direct messages '#p': [publicKey], '#e': [eventId], // Filter by event ID to find relevant private messages authors: [authorPubkey], // Filter by author of the original article limit: 10, // Limit to recent messages }, ] } function decryptContent(privateKey: string, event: Event): Promise { return Promise.resolve(nip04.decrypt(privateKey, event.pubkey, event.content)).then((decrypted) => decrypted ? decrypted : null ) } /** * Get private content for an article (encrypted message from author) * This function now returns the decryption key instead of the full content */ export function getPrivateContent( pool: SimplePool, eventId: string, authorPubkey: string, privateKey: string, publicKey: string ): Promise { if (!privateKey || !pool || !publicKey) { throw new Error('Private key not set or pool not initialized') } return new Promise((resolve) => { let resolved = false const relayUrl = getPrimaryRelaySync() const sub = pool.sub([relayUrl], createPrivateMessageFilters(eventId, publicKey, authorPubkey)) const finalize = (result: string | null) => { if (resolved) { return } resolved = true sub.unsub() resolve(result) } sub.on('event', (event: Event) => { void decryptContent(privateKey, event) .then((content) => { if (content) { finalize(content) } }) .catch((e) => { console.error('Error decrypting content:', e) }) }) sub.on('eose', () => finalize(null)) setTimeout(() => finalize(null), 5000) }) } /** * Get decryption key for an article from private messages * Returns the decryption key and IV if found */ function parseDecryptionKey(decryptedContent: string): DecryptionKey | null { try { const keyData = JSON.parse(decryptedContent) as DecryptionKey if (keyData.key && keyData.iv) { return keyData } } catch { // If parsing fails, it might be old format (full content) } return null } function handleDecryptionKeyEvent( event: Event, recipientPrivateKey: string, finalize: (result: DecryptionKey | null) => void ): void { void decryptContent(recipientPrivateKey, event) .then((decryptedContent) => { if (decryptedContent) { const keyData = parseDecryptionKey(decryptedContent) if (keyData) { finalize(keyData) } } }) .catch((e) => { console.error('Error decrypting decryption key:', e) }) } export async function getDecryptionKey( pool: SimplePool, eventId: string, authorPubkey: string, recipientPrivateKey: string, recipientPublicKey: string ): Promise { if (!recipientPrivateKey || !pool || !recipientPublicKey) { throw new Error('Private key not set or pool not initialized') } return new Promise((resolve) => { let resolved = false const relayUrl = getPrimaryRelaySync() const sub = pool.sub([relayUrl], createPrivateMessageFilters(eventId, recipientPublicKey, authorPubkey)) const finalize = (result: DecryptionKey | null) => { if (resolved) { return } resolved = true sub.unsub() resolve(result) } sub.on('event', (event: Event) => { handleDecryptionKeyEvent(event, recipientPrivateKey, finalize) }) sub.on('eose', () => finalize(null)) setTimeout(() => finalize(null), 5000) }) } /** * Decrypt article content using the decryption key from private message */ export async function decryptArticleContentWithKey( encryptedContent: string, decryptionKey: DecryptionKey ): Promise { return decryptArticleContent(encryptedContent, decryptionKey.key, decryptionKey.iv) }