import { Event, nip04 } from 'nostr-tools' import { SimplePool } from 'nostr-tools' import { decryptArticleContent, type DecryptionKey } from './articleEncryption' import { getPrimaryRelaySync } from './config' import { createSubscription } from '@/types/nostr-tools-extended' function createPrivateMessageFilters(eventId: string, publicKey: string, authorPubkey: string): Array<{ kinds: number[] '#p': string[] '#e': string[] authors: string[] limit: number }> { 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( params: { pool: SimplePool eventId: string authorPubkey: string privateKey: string publicKey: string } ): Promise { if (!params.privateKey || !params.pool || !params.publicKey) { throw new Error('Private key not set or pool not initialized') } return new Promise((resolve) => { let resolved = false const relayUrl = getPrimaryRelaySync() const sub = createSubscription(params.pool, [relayUrl], createPrivateMessageFilters(params.eventId, params.publicKey, params.authorPubkey)) const finalize = (result: string | null): void => { if (resolved) { return } resolved = true sub.unsub() resolve(result) } sub.on('event', (event: Event): void => { void decryptContent(params.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( params: { pool: SimplePool eventId: string authorPubkey: string recipientPrivateKey: string recipientPublicKey: string } ): Promise { if (!params.recipientPrivateKey || !params.pool || !params.recipientPublicKey) { throw new Error('Private key not set or pool not initialized') } return new Promise((resolve) => { let resolved = false const relayUrl = getPrimaryRelaySync() const sub = createSubscription(params.pool, [relayUrl], createPrivateMessageFilters(params.eventId, params.recipientPublicKey, params.authorPubkey)) const finalize = (result: DecryptionKey | null): void => { if (resolved) { return } resolved = true sub.unsub() resolve(result) } sub.on('event', (event: Event): void => { handleDecryptionKeyEvent(event, params.recipientPrivateKey, finalize) }) sub.on('eose', (): void => { 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) }