flockstr/lib/actions/create.ts
2023-10-31 10:26:17 -04:00

404 lines
9.9 KiB
TypeScript

import { nip19 } from "nostr-tools";
import NDK, {
NDKEvent,
type NDKPrivateKeySigner,
type NDKTag,
type NDKList,
type NostrEvent,
NDKUser,
} from "@nostr-dev-kit/ndk";
import { EventSchema } from "@/types";
import {
generateRandomString,
encryptMessage,
decryptMessage,
} from "@/lib/nostr";
import { unixTimeNowInSeconds } from "@/lib/nostr/dates";
import { getTagsValues } from "@/lib/nostr/utils";
import { log } from "@/lib/utils";
export async function createEvent(
ndk: NDK,
event: {
content: string;
kind: number;
tags: string[][];
},
) {
log("func", "createEvent");
try {
const pubkey = ndk.activeUser?.pubkey;
if (!pubkey) {
throw new Error("No public key provided!");
}
const eventToPublish = new NDKEvent(ndk, {
...event,
pubkey,
created_at: unixTimeNowInSeconds(),
} as NostrEvent);
await eventToPublish.sign();
await eventToPublish.publish();
return eventToPublish;
} catch (err) {
log("error", err);
alert("An error has occured");
return false;
}
}
export async function createEventHandler(
ndk: NDK,
event: {
content: string;
kind: number;
tags: string[][];
},
isPrivate?: boolean,
list?: NDKList,
delegateSigner?: NDKPrivateKeySigner,
) {
log("func", "createEventHandler");
const pubkey = ndk.activeUser?.pubkey;
if (!pubkey) {
throw new Error("No public key provided!");
}
const eventToPublish = new NDKEvent(ndk, {
...event,
tags: [...event.tags, ["client", "flockstr"]],
pubkey,
created_at: unixTimeNowInSeconds(),
} as NostrEvent);
await eventToPublish.sign();
let publishedEvent: NDKEvent | null = null;
// Check if is private event
if (isPrivate) {
log("info", "isPrivate");
const rawEventString = JSON.stringify(eventToPublish.rawEvent());
const passphrase = generateRandomString();
const encryptedRawEventString = await encryptMessage(
rawEventString,
passphrase,
);
const newEvent = new NDKEvent(ndk, {
content: encryptedRawEventString,
kind: 3745,
tags: [
["kind", event.kind.toString()],
["client", "flockstr"],
],
pubkey,
} as NostrEvent);
await newEvent.sign();
await newEvent.publish();
const messenger = delegateSigner ?? ndk.signer!;
const user = await messenger.user();
log("info", "Signer", user.toString());
if (list) {
// Send DMs to subscribers
const subscribers = getTagsValues("p", list.tags);
for (const subscriber of subscribers) {
const messageEvent = new NDKEvent(ndk, {
content: passphrase,
kind: 4,
tags: [
["p", subscriber],
["e", newEvent.id],
["client", "flockstr"],
],
pubkey: user.pubkey,
} as NostrEvent);
await messageEvent.encrypt(
new NDKUser({ hexpubkey: subscriber }),
messenger,
);
await messageEvent.sign(messenger);
await messageEvent.publish();
}
}
publishedEvent = newEvent;
} else {
await eventToPublish.publish();
publishedEvent = eventToPublish;
}
if (list) {
const tag = publishedEvent.tagReference();
if (!tag) return;
// Add event to list
await list.addItem(tag, undefined, false);
await list.sign();
await list.publish();
}
return true;
}
export async function createReaction(
ndk: NDK,
content: "+" | "-",
event: {
id: string;
pubkey: string;
},
) {
return createEvent(ndk, {
content,
kind: 7,
tags: [
["e", event.id],
["p", event.pubkey],
],
});
}
export async function deleteEvent(
ndk: NDK,
events: [["e", string] | ["a", `${number}:${string}:${string}`]],
reason?: string,
) {
return createEvent(ndk, {
kind: 5,
content: reason ?? "",
tags: events,
});
}
async function generateEvent(
ndk: NDK,
event: {
content: string;
kind: number;
tags: string[][];
},
delegateSigner?: NDKPrivateKeySigner,
): Promise<NDKTag | undefined> {
let _value = event.content.trim();
// if this a relay URL, nrelay-encode it
if (_value.startsWith("wss://")) {
_value = nip19.nrelayEncode(_value);
}
try {
const decode = nip19.decode(_value);
switch (decode.type) {
case "naddr":
case "note":
case "nevent":
// We were able to decode something that looks like an event
// fetch it
const _event = await ndk.fetchEvent(_value);
if (_event) {
// we were able to fetch it, let's return it
return _event.tagReference();
} else {
// TODO: Generate a NDKTag based on the values
return undefined;
}
case "nrelay":
return ["r", decode.data as string];
case "npub":
return ["p", decode.data as string];
case "nprofile":
const d = ["p", decode.data.pubkey];
if (decode.data.relays && decode.data.relays[0])
d.push(decode.data.relays[0]);
return d;
}
} catch (e) {
console.log("at catch", e);
const signer = delegateSigner ?? ndk.signer!;
const user = await signer.user();
const newEvent = new NDKEvent(ndk, {
content: _value || "",
kind: 1,
tags: [...event.tags, ["client", "flockstr"]],
pubkey: user.pubkey,
} as NostrEvent);
console.log("Event to create", newEvent);
await newEvent.sign(signer);
await newEvent.publish();
return newEvent.tagReference();
}
}
export async function createEventOnList(
ndk: NDK,
event: {
content: string;
kind: number;
tags: string[][];
},
list: NDKList,
delegateSigner?: NDKPrivateKeySigner,
) {
const tag = await generateEvent(ndk, event, delegateSigner);
if (!tag) return;
await list.addItem(tag, undefined, false);
await list.sign();
await list.publish();
return true;
}
const multipleTag = ["a", "p", "e"];
export async function updateList(
ndk: NDK,
list: NostrEvent,
newTags: [string, string][],
) {
let tags = list.tags;
for (const [key, value] of newTags) {
const index = tags.findIndex(([tK]) => tK === key);
if (index !== -1) {
// Replace old
if (multipleTag.includes(key)) {
if (value !== tags[index]?.[1]) {
tags.push([key, value]);
}
} else {
tags[index] = [key, value];
}
} else {
tags.push([key, value]);
}
}
console.log("updating list", tags);
return createEvent(ndk, {
...list,
kind: list.kind as number,
tags: tags.filter(([_, value]) => value !== undefined),
});
}
export async function unlockEvent(
ndk: NDK,
event: NostrEvent,
passphrase: string,
) {
const decrypedData = await decryptMessage(event.content, passphrase);
const hiddenEvent = EventSchema.parse(JSON.parse(decrypedData ?? ""));
// Create New public event
const publishedEvent = await new NDKEvent(ndk, hiddenEvent).publish();
await deleteEvent(ndk, [["e", event.id ?? ""]], "Content unlocked");
return publishedEvent;
}
export async function follow(
ndk: NDK,
currentUser: NDKUser,
pubkey: string,
unfollow?: boolean,
) {
const userContacts = await ndk.fetchEvent({
kinds: [3],
authors: [currentUser.pubkey],
});
if (!userContacts) return;
let newTags = userContacts.tags;
if (unfollow) {
newTags = newTags.filter(([t, k]) =>
t === "p" && k === pubkey ? false : true,
);
} else {
newTags.push(["p", pubkey]);
}
const newEvent = {
kind: 3,
...userContacts.rawEvent(),
tags: newTags,
};
const newContacts = await createEvent(ndk, newEvent);
return newContacts;
}
export async function createCalendarEvent(
ndk: NDK,
event: {
content: string;
kind: number;
tags: string[][];
},
isPrivate?: boolean,
list?: NDKList,
delegateSigner?: NDKPrivateKeySigner,
) {
log("func", "createEventHandler");
const pubkey = await window.nostr?.getPublicKey();
if (!pubkey || !window.nostr) {
throw new Error("No public key provided!");
}
const eventToPublish = new NDKEvent(ndk, {
...event,
tags: [...event.tags, ["client", "flockstr"]],
pubkey,
created_at: unixTimeNowInSeconds(),
} as NostrEvent);
await eventToPublish.sign();
let publishedEvent: NDKEvent | null = null;
// Check if is private event
if (isPrivate) {
log("info", "isPrivate");
const rawEventString = JSON.stringify(eventToPublish.rawEvent());
const passphrase = generateRandomString();
const encryptedRawEventString = await encryptMessage(
rawEventString,
passphrase,
);
const newEvent = new NDKEvent(ndk, {
content: encryptedRawEventString,
kind: 3745,
tags: [
["kind", event.kind.toString()],
["client", "flockstr"],
],
pubkey,
} as NostrEvent);
await newEvent.sign();
await newEvent.publish();
const messenger = delegateSigner ?? ndk.signer!;
const user = await messenger.user();
log("info", "Signer", user.toString());
if (list) {
// Send DMs to subscribers
const subscribers = getTagsValues("p", list.tags);
for (const subscriber of subscribers) {
const messageEvent = new NDKEvent(ndk, {
content: passphrase,
kind: 4,
tags: [
["p", subscriber],
["e", newEvent.id],
["client", "flockstr"],
],
pubkey: user.pubkey,
} as NostrEvent);
await messageEvent.encrypt(
new NDKUser({ hexpubkey: subscriber }),
messenger,
);
await messageEvent.sign(messenger);
await messageEvent.publish();
}
}
publishedEvent = newEvent;
} else {
await eventToPublish.publish();
publishedEvent = eventToPublish;
}
if (list) {
const tag = publishedEvent.tagReference();
if (!tag) return;
// Add event to list
await list.addItem(tag, undefined, false);
await list.sign();
await list.publish();
}
return true;
}