UNPKG

@nostr-dev-kit/ndk

Version:

NDK - Nostr Development Kit. Includes AI Guardrails to catch common mistakes during development.

235 lines (199 loc) 7.14 kB
import type { NDK } from "../../../ndk/index.js"; import type { NDKRelaySet } from "../../../relay/sets/index.js"; import type { NDKSigner } from "../../../signers/index.js"; import type { Hexpubkey, NDKUser } from "../../../user/index.js"; import { isValidPubkey } from "../../../utils/validation.js"; import type { NDKTag, NostrEvent } from "../.."; import { NDKEvent } from "../.."; import { NDKKind } from ".."; import { NDKSimpleGroupMemberList } from "./member-list.js"; import { NDKSimpleGroupMetadata } from "./metadata.js"; /** * Represents a NIP-29 group. * @catergory Kind Wrapper */ export class NDKSimpleGroup { readonly ndk: NDK; public groupId: string; readonly relaySet: NDKRelaySet; private fetchingMetadata: Promise<void> | undefined; public metadata: NDKSimpleGroupMetadata | undefined; public memberList: NDKSimpleGroupMemberList | undefined; public adminList: NDKSimpleGroupMemberList | undefined; constructor(ndk: NDK, relaySet: NDKRelaySet, groupId?: string) { this.ndk = ndk; this.groupId = groupId ?? randomId(24); this.relaySet = relaySet; } get id(): string { return this.groupId; } public relayUrls(): string[] { return this.relaySet?.relayUrls; } get name(): string | undefined { return this.metadata?.name; } get about(): string | undefined { return this.metadata?.about; } get picture(): string | undefined { return this.metadata?.picture; } get members(): Hexpubkey[] | [] { return this.memberList?.members ?? []; } get admins(): Hexpubkey[] | [] { return this.adminList?.members ?? []; } async getMetadata(): Promise<NDKSimpleGroupMetadata> { await this.ensureMetadataEvent(); return this.metadata!; } /** * Creates the group by publishing a kind:9007 event. * @param signer * @returns */ async createGroup(signer?: NDKSigner) { signer ??= this.ndk.signer!; if (!signer) throw new Error("No signer available"); const user = await signer.user(); if (!user) throw new Error("No user available"); const event = new NDKEvent(this.ndk); event.kind = NDKKind.GroupAdminCreateGroup; event.tags.push(["h", this.groupId]); await event.sign(signer); return event.publish(this.relaySet); } async setMetadata({ name, about, picture }: { name?: string; about?: string; picture?: string }) { const event = new NDKEvent(this.ndk); event.kind = NDKKind.GroupAdminEditMetadata; event.tags.push(["h", this.groupId]); if (name) event.tags.push(["name", name]); if (about) event.tags.push(["about", about]); if (picture) event.tags.push(["picture", picture]); await event.sign(); return event.publish(this.relaySet); } /** * Adds a user to the group using a kind:9000 event * @param user user to add * @param opts options */ async addUser(user: NDKUser): Promise<NDKEvent> { const addUserEvent = NDKSimpleGroup.generateAddUserEvent(user.pubkey, this.groupId); addUserEvent.ndk = this.ndk; return addUserEvent; } async getMemberListEvent(): Promise<NDKEvent | null> { const memberList = await this.ndk.fetchEvent( { kinds: [NDKKind.GroupMembers], "#d": [this.groupId], }, undefined, this.relaySet, ); if (!memberList) return null; return NDKSimpleGroupMemberList.from(memberList); } /** * Gets a list of users that belong to this group */ async getMembers(): Promise<NDKUser[]> { const members: NDKUser[] = []; const memberPubkeys = new Set<Hexpubkey>(); const memberListEvent = await this.getMemberListEvent(); if (!memberListEvent) return []; for (const pTag of memberListEvent.getMatchingTags("p")) { const pubkey = pTag[1]; if (!pubkey || !isValidPubkey(pubkey)) continue; if (memberPubkeys.has(pubkey)) continue; memberPubkeys.add(pubkey); try { members.push(this.ndk.getUser({ pubkey })); } catch {} } return members; } /** * Generates an event that lists the members of a group. * @param groupId * @returns */ static generateUserListEvent(groupId: string) { const event = new NDKEvent(undefined, { kind: NDKKind.GroupMembers, tags: [ ["h", groupId], ["alt", "Group Member List"], ], } as NostrEvent); return event; } /** * Generates an event that adds a user to a group. * @param userPubkey pubkey of the user to add * @param groupId group to add the user to * @returns */ static generateAddUserEvent(userPubkey: string, groupId: string) { const event = new NDKEvent(undefined, { kind: NDKKind.GroupAdminAddUser, tags: [["h", groupId]], } as NostrEvent); event.tags.push(["p", userPubkey]); return event; } public async requestToJoin(_pubkey: Hexpubkey, content?: string) { const event = new NDKEvent(this.ndk, { kind: NDKKind.GroupAdminRequestJoin, content: content ?? "", tags: [["h", this.groupId]], } as NostrEvent); return event.publish(this.relaySet); } /** * Makes sure that a metadata event exists locally */ async ensureMetadataEvent(): Promise<void> { if (this.metadata) return; if (this.fetchingMetadata) return this.fetchingMetadata; this.fetchingMetadata = this.ndk .fetchEvent( { kinds: [NDKKind.GroupMetadata], "#d": [this.groupId], }, undefined, this.relaySet, ) .then((event) => { if (event) { this.metadata = NDKSimpleGroupMetadata.from(event); } else { this.metadata = new NDKSimpleGroupMetadata(this.ndk); this.metadata.dTag = this.groupId; } }) .finally(() => { this.fetchingMetadata = undefined; }) .catch(() => { throw new Error(`Failed to fetch metadata for group ${this.groupId}`); }); return this.fetchingMetadata; } } // Remove a p tag of a user const _untagUser = (pubkey: Hexpubkey) => (tag: NDKTag) => !(tag[0] === "p" && tag[1] === pubkey); function randomId(length: number) { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const charsLength = chars.length; let result = ""; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * charsLength)); } return result; }