import * as csp from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts"; import { NostrAccountContext, NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts"; import { ConnectionPool } from "../lib/nostr-ts/relay.ts"; import { prepareNostrImageEvent, Tag } from "../nostr.ts"; import { PublicKey } from "../lib/nostr-ts/key.ts"; import { prepareEncryptedNostrEvent, prepareNormalNostrEvent } from "../lib/nostr-ts/event.ts"; export async function sendDMandImages(args: { sender: NostrAccountContext; receiverPublicKey: PublicKey; message: string; files: Blob[]; lamport_timestamp: number; pool: ConnectionPool; tags: Tag[]; }) { const { tags, sender, receiverPublicKey, message, files, lamport_timestamp, pool } = args; console.log("sendDMandImages", message, files); const eventsToSend: NostrEvent[] = []; if (message.trim().length !== 0) { // build the nostr event const nostrEvent = await prepareEncryptedNostrEvent( sender, { encryptKey: receiverPublicKey, kind: NostrKind.DIRECT_MESSAGE, tags: [ ["p", receiverPublicKey.hex], ["lamport", String(lamport_timestamp)], ...tags, ], content: message, }, ); if (nostrEvent instanceof Error) { return nostrEvent; } eventsToSend.push(nostrEvent); } for (let blob of files) { const imgEvent = await prepareNostrImageEvent( sender, receiverPublicKey, blob, NostrKind.DIRECT_MESSAGE, tags, ); if (imgEvent instanceof Error) { return imgEvent; } let [fileEvent, _] = imgEvent; // for (const event of fileEvents) { eventsToSend.push(fileEvent); // } } // send the event for (const event of eventsToSend) { const err = await pool.sendEvent(event); if (err instanceof Error) { return err; } } return eventsToSend; } export async function sendSocialPost(args: { sender: NostrAccountContext; message: string; lamport_timestamp: number; pool: ConnectionPool; tags: Tag[]; }) { const { sender, message, lamport_timestamp, pool, tags } = args; console.log("sendSocialPost", message); const event = await prepareNormalNostrEvent(sender, NostrKind.TEXT_NOTE, [ ["lamport", String(lamport_timestamp)], ...tags, ], message); const err = await pool.sendEvent(event); if (err instanceof Error) { return err; } return event; } export function getAllEncryptedMessagesOf( publicKey: PublicKey, relay: ConnectionPool, ) { const stream1 = getAllEncryptedMessagesSendBy( publicKey, relay, ); const stream2 = getAllEncryptedMessagesReceivedBy( publicKey, relay, ); return merge(stream1, stream2); } async function* getAllEncryptedMessagesSendBy( publicKey: PublicKey, relay: ConnectionPool, ) { let resp = await relay.newSub( `getAllEncryptedMessagesSendBy`, { authors: [publicKey.hex], kinds: [4], }, ); if (resp instanceof Error) { throw resp; } for await (const nostrMessage of resp.chan) { yield nostrMessage; } } async function* getAllEncryptedMessagesReceivedBy( publicKey: PublicKey, relay: ConnectionPool, ) { let resp = await relay.newSub( `getAllEncryptedMessagesReceivedBy`, { kinds: [4], "#p": [publicKey.hex], }, ); if (resp instanceof Error) { throw resp; } for await (const nostrMessage of resp.chan) { yield nostrMessage; } } function merge(...iters: AsyncIterable[]) { let merged = csp.chan(); async function coroutine( source: AsyncIterable, destination: csp.Channel, ) { for await (let ele of source) { if (destination.closed()) { return; } let err = await destination.put(ele); if (err instanceof csp.PutToClosedChannelError) { // this means the merged channel was not closed when // line 319 is called, // but during waiting time of line 319, no consumer pops it and it was closed. // This is normal semantics of channels // so that it's fine to not throw it up to the call stack // but then this ele has already been popped from the iter, // it will be lost. throw new Error("destination channel should not be closed"); } } } for (let iter of iters) { coroutine(iter, merged); } return merged; }