Files
eranos/src/lib/bookstr.ts
T
Alex Gleason 29fd0c9a0f Remove unused exports and dead code
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.
2026-05-23 20:56:43 -05:00

97 lines
2.9 KiB
TypeScript

import type { NostrEvent } from '@nostrify/nostrify';
/** Bookstr NIP-XX event kind constants. */
export const BOOKSTR_KINDS = {
READ_BOOKS: 10073,
CURRENTLY_READING: 10074,
TO_BE_READ: 10075,
BOOK_REVIEW: 31985,
READING_GOAL: 30078,
} as const;
/** Parsed book review data. */
export interface BookReview {
isbn: string;
content: string;
/** 0-1 fraction, where 1.0 = 5/5 stars. */
rating?: number;
contentWarning?: string;
}
/** Extract an ISBN from a book event, or null if none found. */
export function extractISBNFromEvent(event: NostrEvent): string | null {
// For reviews (kind 31985), check the d tag
if (event.kind === BOOKSTR_KINDS.BOOK_REVIEW) {
const dTag = event.tags.find(([name]) => name === 'd')?.[1];
if (dTag?.startsWith('isbn:')) {
return dTag.replace('isbn:', '');
}
}
// For kind 1111 comments, check uppercase I tag (NIP-22 root reference)
if (event.kind === 1111) {
const iTag = event.tags.find(
([name, value]) => name === 'I' && value?.startsWith('isbn:'),
)?.[1];
if (iTag) return iTag.replace('isbn:', '');
}
// For other events, check lowercase i tags
const iTag = event.tags.find(
([name, value]) => name === 'i' && value?.startsWith('isbn:'),
)?.[1];
return iTag ? iTag.replace('isbn:', '') : null;
}
/** Parse a kind 31985 event into a BookReview, or null if invalid. */
export function parseBookReview(event: NostrEvent): BookReview | null {
if (event.kind !== BOOKSTR_KINDS.BOOK_REVIEW) return null;
const dTag = event.tags.find(([name]) => name === 'd')?.[1];
if (!dTag?.startsWith('isbn:')) return null;
const isbn = dTag.replace('isbn:', '');
const ratingTag = event.tags.find(([name]) => name === 'rating')?.[1];
const contentWarningTag = event.tags.find(([name]) => name === 'content-warning')?.[1];
let rating: number | undefined;
if (ratingTag) {
const parsed = parseFloat(ratingTag);
if (!isNaN(parsed) && parsed >= 0 && parsed <= 1) {
rating = parsed;
}
}
return {
isbn,
content: event.content,
rating,
contentWarning: contentWarningTag,
};
}
/** Validate that a kind 31985 event has the required tags. */
export function validateBookReview(event: NostrEvent): boolean {
if (event.kind !== BOOKSTR_KINDS.BOOK_REVIEW) return false;
const dTag = event.tags.find(([name]) => name === 'd')?.[1];
if (!dTag?.startsWith('isbn:')) return false;
const kTag = event.tags.find(([name]) => name === 'k')?.[1];
if (kTag !== 'isbn') return false;
// If rating tag exists, validate range
const ratingTag = event.tags.find(([name]) => name === 'rating')?.[1];
if (ratingTag) {
const parsed = parseFloat(ratingTag);
if (isNaN(parsed) || parsed < 0 || parsed > 1) return false;
}
return true;
}
/** Convert a 0-1 rating fraction to a 0-5 star count (rounded to nearest integer). */
export function ratingToStars(fraction: number): number {
return Math.round(fraction * 5);
}