29fd0c9a0f
Aggressive cleanup of 359 exports across 153 files identified as having zero importers outside their declaring module: - 105 symbols deleted entirely (no internal uses either) - 254 symbols un-exported (still referenced file-locally; dropped the `export` keyword to shrink the public surface) - ~70 cascade cleanups of locals that became dead once their sole consumer was removed Notable shrinkage: - src/hooks/useShakespeare.ts: 626 \u2192 22 lines (unwired AI chat surface; only the ChatMessage type is consumed) - src/hooks/useTrending.ts: only useEventStats survives; trending feed hooks were never wired up - src/hooks/useTrustedCountryStats.ts: dead type re-exports removed - src/lib/bitcoin.ts: PSBT helpers \u2014 unused wallet feature scaffolding - src/lib/communityUtils.ts: unused NIP-72 moderation helpers - src/lib/extraKinds.ts, src/lib/colorUtils.ts: unused helpers - src/lib/logger.ts: bare debug/info/warn/error exports dropped; consumers use the `logger` object - src/lib/aiChatSystemPrompt.ts: trimmed to the DEFAULT_SYSTEM_PROMPT_TEMPLATE constant - src/components/music/MusicTrackRow.tsx: dead row component removed; only the skeleton is consumed src/hooks/useNostr.ts (intentional decoy) and src/i18n.ts (side-effect import) were preserved per their respective contracts.
99 lines
3.8 KiB
TypeScript
99 lines
3.8 KiB
TypeScript
import type { NostrEvent } from '@nostrify/nostrify';
|
|
|
|
/**
|
|
* Returns true if the event is a reply (has a root or reply e-tag, or an unmarked e-tag).
|
|
* e-tags with marker "mention" are intentional inline quotes and do NOT make an event a reply.
|
|
* Follows NIP-10 conventions.
|
|
*/
|
|
export function isReplyEvent(event: NostrEvent): boolean {
|
|
const eTags = event.tags.filter(([name]) => name === 'e');
|
|
if (eTags.length === 0) return false;
|
|
|
|
// If every e-tag is explicitly marked "mention", this is not a reply
|
|
const nonMentionTags = eTags.filter(([, , , marker]) => marker !== 'mention');
|
|
return nonMentionTags.length > 0;
|
|
}
|
|
|
|
/** Hints extracted from an `e` tag for relay resolution. */
|
|
interface ParentEventHints {
|
|
id: string;
|
|
relayHint?: string;
|
|
authorHint?: string;
|
|
}
|
|
|
|
/**
|
|
* Extracts the parent (replied-to) event ID from an event's tags following NIP-10 conventions.
|
|
* Supports both the preferred marked-tag scheme and the deprecated positional scheme.
|
|
* For kind 7 reactions, uses NIP-25 semantics: the last `e` tag is the reacted-to event.
|
|
*/
|
|
export function getParentEventId(event: NostrEvent): string | undefined {
|
|
return getParentEventTag(event)?.[1];
|
|
}
|
|
|
|
/**
|
|
* Extracts the parent event ID along with relay and author hints from the `e` tag.
|
|
* Returns the full NIP-10 hints (relay URL at position [2], author pubkey at position [4]).
|
|
*
|
|
* When the `e` tag doesn't include a pubkey at position [4] (many clients omit it),
|
|
* falls back to the first `p` tag in the event, which per NIP-10 convention contains
|
|
* the pubkey of the author being replied to.
|
|
*/
|
|
export function getParentEventHints(event: NostrEvent): ParentEventHints | undefined {
|
|
const tag = getParentEventTag(event);
|
|
if (!tag) return undefined;
|
|
|
|
// Prefer the pubkey embedded in the e tag (NIP-10 position [4]).
|
|
// Fall back to the first p tag, which conventionally holds the parent author's pubkey.
|
|
const authorHint = tag[4] || event.tags.find(([name]) => name === 'p')?.[1] || undefined;
|
|
|
|
return {
|
|
id: tag[1],
|
|
relayHint: tag[2] || undefined,
|
|
authorHint,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns the raw parent `e` tag from an event following NIP-10 conventions.
|
|
* For kind 7 reactions, uses NIP-25 semantics: the last `e` tag is the reacted-to event.
|
|
*/
|
|
function getParentEventTag(event: NostrEvent): string[] | undefined {
|
|
// NIP-25: for kind 7 reactions, the target event is always the last e-tag
|
|
if (event.kind === 7) {
|
|
return event.tags.findLast(([name]) => name === 'e');
|
|
}
|
|
|
|
// Exclude "mention" e-tags — they are inline quotes, not reply/root references
|
|
const eTags = event.tags.filter(([name, , , marker]) => name === 'e' && marker !== 'mention');
|
|
if (eTags.length === 0) return undefined;
|
|
|
|
// Preferred: look for marked "reply" tag first
|
|
const replyTag = eTags.find(([, , , marker]) => marker === 'reply');
|
|
if (replyTag) return replyTag;
|
|
|
|
// If there's a "root" marker but no "reply" marker, the event replies directly to root
|
|
const rootTag = eTags.find(([, , , marker]) => marker === 'root');
|
|
if (rootTag) return rootTag;
|
|
|
|
// Deprecated positional scheme: last non-mention e-tag is the reply target
|
|
return eTags[eTags.length - 1];
|
|
}
|
|
|
|
/**
|
|
* Parse a Nostr `a`-tag coordinate string (`kind:pubkey:identifier`) into its
|
|
* constituent parts. Returns `undefined` if the format is invalid.
|
|
*
|
|
* Handles d-tags that contain colons by joining everything after the second
|
|
* colon back together.
|
|
*/
|
|
export function parseATagCoordinate(aTag: string): { kind: number; pubkey: string; identifier: string } | undefined {
|
|
const parts = aTag.split(':');
|
|
if (parts.length < 3) return undefined;
|
|
const kind = parseInt(parts[0], 10);
|
|
if (isNaN(kind) || kind < 0) return undefined;
|
|
const pubkey = parts[1];
|
|
if (!pubkey) return undefined;
|
|
const identifier = parts.slice(2).join(':');
|
|
return { kind, pubkey, identifier };
|
|
}
|