blowater/features/dm.ts
2023-10-09 17:56:14 +00:00

167 lines
4.8 KiB
TypeScript

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<T>(...iters: AsyncIterable<T>[]) {
let merged = csp.chan<T>();
async function coroutine<T>(
source: AsyncIterable<T>,
destination: csp.Channel<T>,
) {
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;
}