import { getNdkContext, activeInboxRelays } from "../ndk.ts"; import { get } from "svelte/store"; import type { NDKEvent } from "@nostr-dev-kit/ndk"; import type NDK from "@nostr-dev-kit/ndk"; import { userStore } from "../stores/userStore.ts"; import { nip19 } from "nostr-tools"; import { npubCache } from "./npubCache.ts"; /** * NIP-51 List kinds for user lists * @see https://github.com/nostr-protocol/nips/blob/master/51.md */ export const NIP51_LIST_KINDS = { FOLLOWS: 3, // Follow list MUTED: 10000, // Mute list PINNED: 10001, // Pin list RELAYS: 10002, // Relay list PEOPLE: 30000, // Categorized people list BOOKMARKS: 30001, // Categorized bookmark list COMMUNITIES: 34550, // Community definition STARTER_PACKS: 39089, // Starter packs MEDIA_STARTER_PACKS: 39092, // Media starter packs } as const; /** * Get all list kinds that contain people (npubs) */ export const PEOPLE_LIST_KINDS = [ NIP51_LIST_KINDS.FOLLOWS, NIP51_LIST_KINDS.PEOPLE, NIP51_LIST_KINDS.STARTER_PACKS, NIP51_LIST_KINDS.MEDIA_STARTER_PACKS, ] as const; /** * Interface for a user list event */ export interface UserListEvent { event: NDKEvent; kind: number; pubkeys: string[]; listName?: string; listDescription?: string; } /** * Fetch all user lists for a given pubkey * @param pubkey - The pubkey to fetch lists for * @param listKinds - Array of list kinds to fetch (defaults to all people list kinds) * @returns Promise that resolves to an array of UserListEvent objects */ export async function fetchUserLists( pubkey: string, listKinds: number[] = [...PEOPLE_LIST_KINDS], ndk?: NDK ): Promise { const ndkInstance = ndk || getNdkContext(); if (!ndkInstance) { console.warn("fetchUserLists: No NDK instance available"); return []; } console.log(`fetchUserLists: Fetching lists for ${pubkey}, kinds:`, listKinds); try { const events = await ndkInstance.fetchEvents({ kinds: listKinds, authors: [pubkey], }); const userLists: UserListEvent[] = []; for (const event of events) { const pubkeys: string[] = []; // Extract pubkeys from p-tags event.tags.forEach(tag => { if (tag[0] === 'p' && tag[1]) { pubkeys.push(tag[1]); } }); // Extract list metadata from content if available let listName: string | undefined; let listDescription: string | undefined; if (event.content) { try { const content = JSON.parse(event.content); listName = content.name || content.title; listDescription = content.description; } catch { // Content is not JSON, ignore } } // Get list name from d-tag if available (for addressable lists) if (!listName && event.kind >= 30000 && event.kind < 40000) { const dTag = event.getMatchingTags('d')[0]?.[1]; if (dTag) { listName = dTag; } } userLists.push({ event, kind: event.kind, pubkeys, listName, listDescription, }); } console.log(`fetchUserLists: Found ${userLists.length} lists with ${userLists.reduce((sum, list) => sum + list.pubkeys.length, 0)} total pubkeys`); return userLists; } catch (error) { console.error("fetchUserLists: Error fetching user lists:", error); return []; } } /** * Fetch the current user's lists * @param listKinds - Array of list kinds to fetch (defaults to all people list kinds) * @param ndk - Optional NDK instance (if not provided, will use getNdkContext) * @returns Promise that resolves to an array of UserListEvent objects */ export async function fetchCurrentUserLists( listKinds: number[] = [...PEOPLE_LIST_KINDS], ndk?: NDK ): Promise { const userState = get(userStore); if (!userState.signedIn || !userState.pubkey) { console.warn("fetchCurrentUserLists: No active user found in userStore"); return []; } console.log("fetchCurrentUserLists: Found user pubkey:", userState.pubkey); return fetchUserLists(userState.pubkey, listKinds, ndk); } /** * Get all pubkeys from user lists * @param userLists - Array of UserListEvent objects * @returns Set of unique pubkeys */ export function getPubkeysFromUserLists(userLists: UserListEvent[]): Set { const pubkeys = new Set(); userLists.forEach(list => { list.pubkeys.forEach(pubkey => { pubkeys.add(pubkey); }); }); return pubkeys; } /** * Get pubkeys from a specific list kind * @param userLists - Array of UserListEvent objects * @param kind - The list kind to filter by * @returns Set of unique pubkeys from the specified list kind */ export function getPubkeysFromListKind(userLists: UserListEvent[], kind: number): Set { const pubkeys = new Set(); userLists.forEach(list => { if (list.kind === kind) { list.pubkeys.forEach(pubkey => { pubkeys.add(pubkey); }); } }); return pubkeys; } /** * Check if a pubkey is in any of the user's lists * @param pubkey - The pubkey to check * @param userLists - Array of UserListEvent objects * @returns True if the pubkey is in any list */ export function isPubkeyInUserLists(pubkey: string, userLists: UserListEvent[]): boolean { const result = userLists.some(list => list.pubkeys.includes(pubkey)); console.log(`isPubkeyInUserLists: Checking ${pubkey} against ${userLists.length} lists, result: ${result}`); if (result) { console.log(`isPubkeyInUserLists: Found ${pubkey} in lists:`, userLists.filter(list => list.pubkeys.includes(pubkey)).map(list => ({ kind: list.kind, name: list.listName }))); } return result; } /** * Get the list kinds that contain a specific pubkey * @param pubkey - The pubkey to check * @param userLists - Array of UserListEvent objects * @returns Array of list kinds that contain the pubkey */ export function getListKindsForPubkey(pubkey: string, userLists: UserListEvent[]): number[] { return userLists .filter(list => list.pubkeys.includes(pubkey)) .map(list => list.kind); } /** * Update profile cache when new follows are discovered * This ensures follows are always cached and prioritized * @param pubkeys - Array of pubkeys to cache profiles for */ export async function updateProfileCacheForPubkeys(pubkeys: string[], ndk?: NDK): Promise { if (pubkeys.length === 0) return; try { console.log(`Updating profile cache for ${pubkeys.length} pubkeys`); const ndkInstance = ndk || getNdkContext(); if (!ndkInstance) { console.warn("updateProfileCacheForPubkeys: No NDK instance available"); return; } // Fetch profiles for all pubkeys in batches const batchSize = 20; for (let i = 0; i < pubkeys.length; i += batchSize) { const batch = pubkeys.slice(i, i + batchSize); try { const events = await ndkInstance.fetchEvents({ kinds: [0], authors: batch, }); // Cache each profile for (const event of events) { if (event.content) { try { const profileData = JSON.parse(event.content); const npub = nip19.npubEncode(event.pubkey); npubCache.set(npub, profileData); console.log(`Cached profile for: ${npub}`); } catch (e) { console.warn("Failed to parse profile data:", e); } } } } catch (error) { console.warn("Failed to fetch batch of profiles:", error); } } console.log("Profile cache update completed"); } catch (error) { console.warn("Failed to update profile cache:", error); } }