import { nip19 } from "nostr-tools"; import { isValidHexEventId, isValidHexPubkey, normalizeHex, } from "./nostr-validation"; import { normalizeRelayURL } from "./relay-url"; // Define pointer types locally since they're not exported from nostr-tools export interface EventPointer { id: string; relays?: string[]; author?: string; } export interface AddressPointer { kind: number; pubkey: string; identifier: string; relays?: string[]; } export interface ParsedOpenCommand { pointer: EventPointer | AddressPointer; } /** * Parse OPEN command arguments into an event pointer * Supports: * - note1... (bech32 note) * - nevent1... (bech32 nevent with relay hints) * - naddr1... (bech32 naddr for addressable events) * - abc123... (64-char hex event ID) * - kind:pubkey:d-tag (address pointer format) */ export function parseOpenCommand(args: string[]): ParsedOpenCommand { const identifier = args[0]; if (!identifier) { throw new Error("Event identifier required"); } // Try bech32 decode first (note, nevent, naddr) if ( identifier.startsWith("note") || identifier.startsWith("nevent") || identifier.startsWith("naddr") ) { try { const decoded = nip19.decode(identifier); if (decoded.type === "note") { // note1... -> EventPointer with just ID return { pointer: { id: decoded.data, }, }; } if (decoded.type === "nevent") { // nevent1... -> EventPointer (already has id and optional relays) return { pointer: { ...decoded.data, relays: decoded.data.relays ?.map((url) => { try { return normalizeRelayURL(url); } catch (error) { console.warn( `Skipping invalid relay hint in nevent: ${url}`, error, ); return null; } }) .filter((url): url is string => url !== null), }, }; } if (decoded.type === "naddr") { // naddr1... -> AddressPointer (already has kind, pubkey, identifier) return { pointer: { ...decoded.data, relays: decoded.data.relays ?.map((url) => { try { return normalizeRelayURL(url); } catch (error) { console.warn( `Skipping invalid relay hint in naddr: ${url}`, error, ); return null; } }) .filter((url): url is string => url !== null), }, }; } } catch (error) { throw new Error(`Invalid bech32 identifier: ${error}`); } } // Check if it's a hex event ID if (isValidHexEventId(identifier)) { return { pointer: { id: normalizeHex(identifier), }, }; } // Check if it's an address format (kind:pubkey:d-tag) if (identifier.includes(":")) { const parts = identifier.split(":"); if (parts.length >= 2) { const kind = parseInt(parts[0], 10); const pubkey = parts[1]; const dTag = parts[2] || ""; if (isNaN(kind)) { throw new Error("Invalid address format: kind must be a number"); } if (!isValidHexPubkey(pubkey)) { throw new Error("Invalid address format: pubkey must be 64 hex chars"); } return { pointer: { kind, pubkey: normalizeHex(pubkey), identifier: dTag, }, }; } } throw new Error( "Invalid event identifier. Supported formats: note1..., nevent1..., naddr1..., hex ID, or kind:pubkey:d-tag", ); }