@ashdev/codex-plugin-metadata-openlibrary 1.9.0 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -686,7 +686,7 @@ init_api();
|
|
|
686
686
|
// package.json
|
|
687
687
|
var package_default = {
|
|
688
688
|
name: "@ashdev/codex-plugin-metadata-openlibrary",
|
|
689
|
-
version: "1.9.
|
|
689
|
+
version: "1.9.2",
|
|
690
690
|
description: "Open Library metadata plugin for Codex - fetches book metadata by ISBN or title search",
|
|
691
691
|
main: "dist/index.js",
|
|
692
692
|
bin: "dist/index.js",
|
|
@@ -726,7 +726,7 @@ var package_default = {
|
|
|
726
726
|
node: ">=22.0.0"
|
|
727
727
|
},
|
|
728
728
|
dependencies: {
|
|
729
|
-
"@ashdev/codex-plugin-sdk": "^1.9.
|
|
729
|
+
"@ashdev/codex-plugin-sdk": "^1.9.2"
|
|
730
730
|
},
|
|
731
731
|
devDependencies: {
|
|
732
732
|
"@biomejs/biome": "^2.3.13",
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/api.ts", "../node_modules/@ashdev/codex-plugin-sdk/src/types/rpc.ts", "../node_modules/@ashdev/codex-plugin-sdk/src/errors.ts", "../node_modules/@ashdev/codex-plugin-sdk/src/logger.ts", "../node_modules/@ashdev/codex-plugin-sdk/src/server.ts", "../src/index.ts", "../package.json", "../src/manifest.ts", "../src/mapper.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Open Library API Client\n *\n * Handles communication with the Open Library API with:\n * - Rate limiting (100 requests per 5 minutes recommended)\n * - Caching to reduce API calls\n * - Error handling with retries\n *\n * @see https://openlibrary.org/developers/api\n */\n\nimport type {\n OLAuthor,\n OLEdition,\n OLSearchResponse,\n OLWork,\n OLWorkEditionsResponse,\n} from \"./types.js\";\n\nconst BASE_URL = \"https://openlibrary.org\";\nconst COVERS_BASE_URL = \"https://covers.openlibrary.org\";\n\n// Simple in-memory cache with TTL\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\nconst CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes\nconst cache = new Map<string, CacheEntry<unknown>>();\n\n/**\n * Get cached value if not expired\n */\nfunction getCached<T>(key: string): T | null {\n const entry = cache.get(key);\n if (entry && Date.now() - entry.timestamp < CACHE_TTL_MS) {\n return entry.data as T;\n }\n if (entry) {\n cache.delete(key); // Cleanup expired\n }\n return null;\n}\n\n/**\n * Store value in cache\n */\nfunction setCache<T>(key: string, data: T): void {\n cache.set(key, { data, timestamp: Date.now() });\n}\n\n/**\n * Make an HTTP request with error handling\n */\nasync function fetchJson<T>(url: string, description: string): Promise<T | null> {\n // Check cache first\n const cached = getCached<T>(url);\n if (cached !== null) {\n return cached;\n }\n\n try {\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": \"Codex/1.0 (https://github.com/AshDevFr/codex; codex-plugin)\",\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as T;\n setCache(url, data);\n return data;\n } catch (error) {\n console.error(`[openlibrary] Failed to fetch ${description}:`, error);\n return null;\n }\n}\n\n/**\n * Normalize ISBN by removing hyphens and spaces\n */\nexport function normalizeIsbn(isbn: string): string {\n return isbn.replace(/[-\\s]/g, \"\").toUpperCase();\n}\n\n/**\n * Check if a string is a valid ISBN-10 or ISBN-13\n */\nexport function isValidIsbn(isbn: string): boolean {\n const normalized = normalizeIsbn(isbn);\n return normalized.length === 10 || normalized.length === 13;\n}\n\n/**\n * Fetch book edition by ISBN\n *\n * @param isbn ISBN-10 or ISBN-13\n * @returns Edition data or null if not found\n */\nexport async function getEditionByIsbn(isbn: string): Promise<OLEdition | null> {\n const normalized = normalizeIsbn(isbn);\n const url = `${BASE_URL}/isbn/${normalized}.json`;\n return fetchJson<OLEdition>(url, `edition by ISBN ${normalized}`);\n}\n\n/**\n * Fetch work details by key\n *\n * @param workKey Work key (e.g., \"/works/OL45883W\" or just \"OL45883W\")\n * @returns Work data or null if not found\n */\nexport async function getWork(workKey: string): Promise<OLWork | null> {\n // Normalize key to just the ID part\n const key = workKey.startsWith(\"/works/\") ? workKey : `/works/${workKey}`;\n const url = `${BASE_URL}${key}.json`;\n return fetchJson<OLWork>(url, `work ${key}`);\n}\n\n/**\n * Fetch editions for a work\n *\n * Returns editions directly associated with a work, ordered by most recent.\n * This is more reliable than searching by title, which can return unrelated books.\n *\n * @param workKey Work key (e.g., \"/works/OL45883W\")\n * @param limit Maximum number of editions to fetch\n * @returns Array of editions or empty array if none found\n */\nexport async function getWorkEditions(workKey: string, limit = 5): Promise<OLEdition[]> {\n const key = workKey.startsWith(\"/works/\") ? workKey : `/works/${workKey}`;\n const url = `${BASE_URL}${key}/editions.json?limit=${limit}`;\n const response = await fetchJson<OLWorkEditionsResponse>(url, `editions for ${key}`);\n return response?.entries || [];\n}\n\n/**\n * Fetch author details by key\n *\n * @param authorKey Author key (e.g., \"/authors/OL34184A\" or just \"OL34184A\")\n * @returns Author data or null if not found\n */\nexport async function getAuthor(authorKey: string): Promise<OLAuthor | null> {\n // Normalize key to just the ID part\n const key = authorKey.startsWith(\"/authors/\") ? authorKey : `/authors/${authorKey}`;\n const url = `${BASE_URL}${key}.json`;\n return fetchJson<OLAuthor>(url, `author ${key}`);\n}\n\n/** Fields to request from the Open Library search API */\nconst SEARCH_FIELDS = [\n \"key\",\n \"title\",\n \"subtitle\",\n \"author_name\",\n \"author_key\",\n \"first_publish_year\",\n \"publish_year\",\n \"publisher\",\n \"isbn\",\n \"number_of_pages_median\",\n \"cover_i\",\n \"cover_edition_key\",\n \"edition_count\",\n \"language\",\n \"subject\",\n \"ratings_average\",\n \"ratings_count\",\n].join(\",\");\n\n/**\n * Search for books\n *\n * When an author is provided, uses the `title` + `author` parameters for\n * more precise results. If that yields no results, falls back to a general\n * `q` search to ensure we still return something useful.\n *\n * @param query Search query (title, author, or combined)\n * @param options Additional search options\n * @returns Search results\n */\nexport async function searchBooks(\n query: string,\n options: {\n author?: string;\n limit?: number;\n } = {},\n): Promise<OLSearchResponse | null> {\n const { author, limit = 10 } = options;\n\n // When author is provided, try a refined title + author search first\n if (author) {\n const params = new URLSearchParams({\n title: query,\n author,\n fields: SEARCH_FIELDS,\n limit: String(limit),\n });\n\n const url = `${BASE_URL}/search.json?${params}`;\n const response = await fetchJson<OLSearchResponse>(\n url,\n `search title=\"${query}\" author=\"${author}\"`,\n );\n\n if (response?.docs?.length) {\n return response;\n }\n\n // Fall back to general q search if title+author yielded no results\n }\n\n // General search using q parameter\n const params = new URLSearchParams({\n q: query,\n fields: SEARCH_FIELDS,\n limit: String(limit),\n });\n\n if (author) {\n params.set(\"author\", author);\n }\n\n const url = `${BASE_URL}/search.json?${params}`;\n return fetchJson<OLSearchResponse>(url, `search \"${query}\"`);\n}\n\n/**\n * Get cover image URL by ISBN\n *\n * @param isbn ISBN-10 or ISBN-13\n * @param size Cover size: S (small ~50w), M (medium ~180w), L (large ~300w+)\n * @returns Cover URL\n */\nexport function getCoverUrlByIsbn(isbn: string, size: \"S\" | \"M\" | \"L\"): string {\n const normalized = normalizeIsbn(isbn);\n return `${COVERS_BASE_URL}/b/isbn/${normalized}-${size}.jpg`;\n}\n\n/**\n * Get cover image URL by cover ID\n *\n * @param coverId Open Library cover ID\n * @param size Cover size: S (small), M (medium), L (large)\n * @returns Cover URL\n */\nexport function getCoverUrlById(coverId: number, size: \"S\" | \"M\" | \"L\"): string {\n return `${COVERS_BASE_URL}/b/id/${coverId}-${size}.jpg`;\n}\n\n/**\n * Get cover image URL by Open Library ID (OLID)\n *\n * @param olid Open Library ID (e.g., \"OL7353617M\" for edition, \"OL45883W\" for work)\n * @param size Cover size: S (small), M (medium), L (large)\n * @returns Cover URL\n */\nexport function getCoverUrlByOlid(olid: string, size: \"S\" | \"M\" | \"L\"): string {\n // Strip any prefix if present\n const id = olid.replace(/^\\/(?:books|works)\\//, \"\");\n return `${COVERS_BASE_URL}/b/olid/${id}-${size}.jpg`;\n}\n\n/**\n * Parse year from Open Library date string\n *\n * Open Library dates can be in various formats:\n * - \"2020\"\n * - \"January 1, 2020\"\n * - \"2020-01-15\"\n * - \"c1985\"\n * - \"1985?\"\n *\n * @param dateStr Date string from Open Library\n * @returns Parsed year or undefined if unable to parse\n */\nexport function parseYear(dateStr: string | undefined): number | undefined {\n if (!dateStr) return undefined;\n\n // Try to extract a 4-digit year\n // Using (?:^|[^0-9]) to handle \"c1985\" format where there's no word boundary\n const match = dateStr.match(/(?:^|[^0-9])(1[89]\\d{2}|20\\d{2})(?:[^0-9]|$)/);\n if (match) {\n return Number.parseInt(match[1], 10);\n }\n\n return undefined;\n}\n\n/**\n * Parse description from Open Library\n *\n * Description can be either a string or an object with { type, value }.\n * Strips HTML tags and normalizes whitespace, since Open Library descriptions\n * can contain raw HTML (e.g., from Standard Ebooks imports).\n */\nexport function parseDescription(\n desc: string | { type?: string; value: string } | undefined,\n): string | undefined {\n if (!desc) return undefined;\n const raw = typeof desc === \"string\" ? desc : desc.value;\n return stripHtml(raw);\n}\n\n/**\n * Strip HTML tags from a string and normalize whitespace.\n *\n * Converts block-level tags (p, br, div, li) to newlines,\n * strips all remaining tags, decodes common HTML entities,\n * and collapses excessive whitespace.\n */\nfunction stripHtml(html: string): string | undefined {\n let text = html;\n\n // Convert block-level elements to newlines\n text = text.replace(/<\\/(p|div|li|tr|h[1-6])>/gi, \"\\n\");\n text = text.replace(/<br\\s*\\/?>/gi, \"\\n\");\n\n // Remove all remaining HTML tags\n text = text.replace(/<[^>]+>/g, \"\");\n\n // Decode common HTML entities\n text = text\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/'/g, \"'\")\n .replace(/ /g, \" \");\n\n // Collapse multiple spaces/tabs on the same line into one space\n text = text.replace(/[^\\S\\n]+/g, \" \");\n\n // Collapse 3+ consecutive newlines into 2\n text = text.replace(/\\n{3,}/g, \"\\n\\n\");\n\n // Trim each line and remove leading/trailing whitespace\n text = text\n .split(\"\\n\")\n .map((line) => line.trim())\n .join(\"\\n\")\n .trim();\n\n return text || undefined;\n}\n\n/**\n * Convert Open Library language code to BCP47\n *\n * Open Library uses format like \"/languages/eng\"\n *\n * @param langRef Language reference (e.g., \"/languages/eng\")\n * @returns BCP47 language code (e.g., \"en\")\n */\nexport function parseLanguage(langRef: string | undefined): string | undefined {\n if (!langRef) return undefined;\n\n // Extract language code from \"/languages/xxx\" format\n const match = langRef.match(/\\/languages\\/(\\w+)$/);\n if (!match) return undefined;\n\n const code = match[1].toLowerCase();\n\n // Map Open Library 3-letter codes to BCP47 2-letter codes\n const languageMap: Record<string, string> = {\n eng: \"en\",\n spa: \"es\",\n fre: \"fr\",\n fra: \"fr\",\n ger: \"de\",\n deu: \"de\",\n ita: \"it\",\n por: \"pt\",\n rus: \"ru\",\n jpn: \"ja\",\n chi: \"zh\",\n zho: \"zh\",\n kor: \"ko\",\n ara: \"ar\",\n hin: \"hi\",\n pol: \"pl\",\n tur: \"tr\",\n dut: \"nl\",\n nld: \"nl\",\n swe: \"sv\",\n nor: \"no\",\n dan: \"da\",\n fin: \"fi\",\n cze: \"cs\",\n ces: \"cs\",\n gre: \"el\",\n ell: \"el\",\n heb: \"he\",\n hun: \"hu\",\n rom: \"ro\",\n ron: \"ro\",\n tha: \"th\",\n vie: \"vi\",\n ind: \"id\",\n mal: \"ms\",\n msa: \"ms\",\n ukr: \"uk\",\n cat: \"ca\",\n lat: \"la\",\n };\n\n return languageMap[code] || code;\n}\n\n/**\n * Extract Open Library ID from a key\n *\n * @param key Full key (e.g., \"/works/OL45883W\" or \"/books/OL7353617M\")\n * @returns Just the ID (e.g., \"OL45883W\" or \"OL7353617M\")\n */\nexport function extractOlid(key: string): string {\n return key.replace(/^\\/(?:works|books|authors)\\//, \"\");\n}\n\n/**\n * Build Open Library URL from a key\n *\n * @param key Key (e.g., \"/works/OL45883W\")\n * @returns Full URL (e.g., \"https://openlibrary.org/works/OL45883W\")\n */\nexport function buildOpenLibraryUrl(key: string): string {\n return `${BASE_URL}${key.startsWith(\"/\") ? key : `/${key}`}`;\n}\n\n/**\n * Clear the cache\n */\nexport function clearCache(): void {\n cache.clear();\n}\n", null, null, null, null, "/**\n * Open Library Metadata Plugin for Codex\n *\n * Fetches book metadata from Open Library (openlibrary.org), a free and open\n * book database with extensive ISBN coverage.\n *\n * Features:\n * - ISBN lookup for direct, accurate matching\n * - Title/author search for fuzzy matching\n * - Cover image fetching in multiple sizes\n * - Author resolution with proper names\n * - Subject/genre extraction\n *\n * @see https://openlibrary.org/developers/api\n */\n\nimport {\n type BookMatchParams,\n type BookMetadataProvider,\n type BookSearchParams,\n createLogger,\n createMetadataPlugin,\n type InitializeParams,\n type MetadataGetParams,\n type MetadataMatchResponse,\n type MetadataSearchResponse,\n type PluginBookMetadata,\n} from \"@ashdev/codex-plugin-sdk\";\n\nimport { getEditionByIsbn, getWork, isValidIsbn, searchBooks } from \"./api.js\";\nimport { DEFAULT_MAX_RESULTS, manifest } from \"./manifest.js\";\nimport {\n getFullBookMetadata,\n mapEditionToBookMetadata,\n mapSearchDocToSearchResult,\n} from \"./mapper.js\";\n\nconst logger = createLogger({ name: \"openlibrary\", level: \"info\" });\n\n// Plugin configuration (set during initialization)\nconst config = {\n maxResults: DEFAULT_MAX_RESULTS,\n};\n\n/**\n * Book metadata provider implementation\n */\nconst bookProvider: BookMetadataProvider = {\n /**\n * Search for books by ISBN or title/author query\n *\n * If ISBN is provided, it takes priority for direct lookup.\n * Otherwise, falls back to title/author search.\n */\n async search(params: BookSearchParams): Promise<MetadataSearchResponse> {\n const { isbn, query, author, limit } = params;\n const maxResults = Math.min(limit || config.maxResults, 50);\n\n // If ISBN is provided, try direct lookup first\n if (isbn && isValidIsbn(isbn)) {\n const edition = await getEditionByIsbn(isbn);\n\n if (edition) {\n // Found by ISBN - return as single result with high relevance\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n const metadata = await mapEditionToBookMetadata(edition, workData);\n\n return {\n results: [\n {\n externalId: metadata.externalId,\n title: metadata.title || \"Unknown\",\n alternateTitles: metadata.subtitle ? [metadata.subtitle] : [],\n year: metadata.year,\n coverUrl: metadata.coverUrl,\n relevanceScore: 1.0, // Perfect match by ISBN\n preview: {\n genres: metadata.subjects.slice(0, 5),\n authors: metadata.authors.map((a) => a.name),\n },\n },\n ],\n };\n }\n\n // ISBN not found, fall through to search if query is also provided\n if (!query) {\n return { results: [] };\n }\n }\n\n // Title/author search\n if (!query) {\n return { results: [] };\n }\n\n const searchResponse = await searchBooks(query, {\n author,\n limit: maxResults,\n });\n\n if (!searchResponse?.docs?.length) {\n return { results: [] };\n }\n\n return {\n results: searchResponse.docs.map(mapSearchDocToSearchResult),\n };\n },\n\n /**\n * Get full book metadata by external ID\n *\n * The external ID can be:\n * - A work key: \"/works/OL45883W\"\n * - An edition key: \"/books/OL7353617M\"\n */\n async get(params: MetadataGetParams): Promise<PluginBookMetadata> {\n const { externalId } = params;\n\n // Try to get full metadata\n const metadata = await getFullBookMetadata(externalId);\n\n if (metadata) {\n return metadata;\n }\n\n // Fallback: return minimal metadata\n return {\n externalId,\n externalUrl: `https://openlibrary.org${externalId.startsWith(\"/\") ? externalId : `/${externalId}`}`,\n alternateTitles: [],\n isbns: [],\n genres: [],\n tags: [],\n subjects: [],\n authors: [],\n artists: [],\n covers: [],\n externalRatings: [],\n awards: [],\n externalLinks: [\n {\n url: `https://openlibrary.org${externalId.startsWith(\"/\") ? externalId : `/${externalId}`}`,\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n },\n\n /**\n * Auto-match a book using available identifiers\n *\n * Match priority:\n * 1. ISBN (if provided) - highest confidence\n * 2. Title + author search - lower confidence\n */\n async match(params: BookMatchParams): Promise<MetadataMatchResponse> {\n const { title, authors, isbn, year } = params;\n\n // Try ISBN first if available\n if (isbn && isValidIsbn(isbn)) {\n const edition = await getEditionByIsbn(isbn);\n\n if (edition) {\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n const metadata = await mapEditionToBookMetadata(edition, workData);\n\n return {\n match: {\n externalId: metadata.externalId,\n title: metadata.title || \"Unknown\",\n alternateTitles: metadata.subtitle ? [metadata.subtitle] : [],\n year: metadata.year,\n coverUrl: metadata.coverUrl,\n relevanceScore: 1.0,\n preview: {\n genres: metadata.subjects.slice(0, 5),\n authors: metadata.authors.map((a) => a.name),\n },\n },\n confidence: 0.99, // Very high confidence for ISBN match\n alternatives: [],\n };\n }\n }\n\n // Fall back to title search\n const searchQuery = authors?.length ? `${title} ${authors[0]}` : title;\n\n const searchResponse = await searchBooks(searchQuery, {\n limit: 5,\n });\n\n if (!searchResponse?.docs?.length) {\n return {\n match: null,\n confidence: 0,\n alternatives: [],\n };\n }\n\n const results = searchResponse.docs.map(mapSearchDocToSearchResult);\n\n // Calculate confidence based on title similarity and other factors\n const bestMatch = results[0];\n let confidence = bestMatch.relevanceScore || 0.5;\n\n // Boost confidence if title matches closely\n const normalizedTitle = title.toLowerCase().trim();\n const normalizedMatchTitle = bestMatch.title.toLowerCase().trim();\n\n if (normalizedTitle === normalizedMatchTitle) {\n confidence = Math.min(1.0, confidence + 0.3);\n } else if (\n normalizedMatchTitle.includes(normalizedTitle) ||\n normalizedTitle.includes(normalizedMatchTitle)\n ) {\n confidence = Math.min(1.0, confidence + 0.15);\n }\n\n // Boost if year matches\n if (year && bestMatch.year === year) {\n confidence = Math.min(1.0, confidence + 0.1);\n }\n\n // Reduce confidence without ISBN\n confidence = Math.min(confidence, 0.85);\n\n return {\n match: bestMatch,\n confidence,\n alternatives: results.slice(1),\n };\n },\n};\n\n// =============================================================================\n// Plugin Initialization\n// =============================================================================\n\ncreateMetadataPlugin({\n manifest,\n bookProvider,\n logLevel: \"info\",\n onInitialize(params: InitializeParams) {\n // Read config from initialization params\n const maxResults = params.config?.maxResults as number | undefined;\n if (maxResults !== undefined) {\n config.maxResults = Math.min(Math.max(1, maxResults), 50); // Clamp 1-50\n }\n logger.info(`Plugin initialized (maxResults: ${config.maxResults})`);\n },\n});\n\nlogger.info(\"Open Library plugin started\");\n", "{\n \"name\": \"@ashdev/codex-plugin-metadata-openlibrary\",\n \"version\": \"1.9.0\",\n \"description\": \"Open Library metadata plugin for Codex - fetches book metadata by ISBN or title search\",\n \"main\": \"dist/index.js\",\n \"bin\": \"dist/index.js\",\n \"type\": \"module\",\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/AshDevFr/codex.git\",\n \"directory\": \"plugins/metadata-openlibrary\"\n },\n \"scripts\": {\n \"build\": \"esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/index.js --sourcemap --banner:js='#!/usr/bin/env node'\",\n \"dev\": \"npm run build -- --watch\",\n \"clean\": \"rm -rf dist\",\n \"start\": \"node dist/index.js\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"npm run lint && npm run build\"\n },\n \"keywords\": [\n \"codex\",\n \"plugin\",\n \"openlibrary\",\n \"metadata\",\n \"books\",\n \"isbn\"\n ],\n \"author\": \"Codex\",\n \"license\": \"MIT\",\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"dependencies\": {\n \"@ashdev/codex-plugin-sdk\": \"^1.9.0\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.3.13\",\n \"@types/node\": \"^22.0.0\",\n \"esbuild\": \"^0.24.0\",\n \"typescript\": \"^5.7.0\",\n \"vitest\": \"^3.0.0\"\n }\n}\n", "import type { MetadataContentType, PluginManifest } from \"@ashdev/codex-plugin-sdk\";\nimport packageJson from \"../package.json\" with { type: \"json\" };\n\n// Default config values\nexport const DEFAULT_MAX_RESULTS = 10;\n\nexport const manifest = {\n name: \"metadata-openlibrary\",\n displayName: \"Open Library\",\n version: packageJson.version,\n description:\n \"Fetches book metadata from Open Library (openlibrary.org). Supports ISBN lookup and title search for EPUBs, PDFs, and other book formats.\",\n author: \"Codex\",\n homepage: \"https://openlibrary.org\",\n protocolVersion: \"1.0\",\n capabilities: {\n // Book metadata provider only (not series)\n metadataProvider: [\"book\"] as MetadataContentType[],\n },\n configSchema: {\n description: \"Configuration options for the Open Library plugin\",\n fields: [\n {\n key: \"maxResults\",\n label: \"Maximum Results\",\n description: \"Maximum number of results to return for search queries (1-50)\",\n type: \"number\" as const,\n required: false,\n default: DEFAULT_MAX_RESULTS,\n example: 20,\n },\n ],\n },\n} as const satisfies PluginManifest & {\n capabilities: { metadataProvider: MetadataContentType[] };\n};\n", "/**\n * Mapper functions to convert Open Library data to Codex plugin format\n */\n\nimport type {\n BookAuthor,\n BookCover,\n ExternalLink,\n ExternalRating,\n PluginBookMetadata,\n SearchResult,\n} from \"@ashdev/codex-plugin-sdk\";\n\nimport {\n buildOpenLibraryUrl,\n getAuthor,\n getCoverUrlById,\n getCoverUrlByIsbn,\n getWork,\n getWorkEditions,\n parseDescription,\n parseLanguage,\n parseYear,\n} from \"./api.js\";\nimport type { OLAuthorReference, OLEdition, OLSearchDoc, OLWork, ParsedAuthor } from \"./types.js\";\n\n/**\n * Map Open Library search result to Codex SearchResult\n */\nexport function mapSearchDocToSearchResult(doc: OLSearchDoc): SearchResult {\n const year = doc.first_publish_year;\n const coverUrl = doc.cover_i ? getCoverUrlById(doc.cover_i, \"M\") : undefined;\n\n // Calculate a relevance score based on available data\n // More complete entries get higher scores\n let relevanceScore = 0.5;\n if (doc.author_name?.length) relevanceScore += 0.1;\n if (doc.isbn?.length) relevanceScore += 0.15;\n if (doc.cover_i) relevanceScore += 0.1;\n if (doc.first_publish_year) relevanceScore += 0.05;\n if (doc.subject?.length) relevanceScore += 0.05;\n if (doc.ratings_count && doc.ratings_count > 0) relevanceScore += 0.05;\n\n return {\n externalId: doc.key, // Work key, e.g., \"/works/OL45883W\"\n title: doc.title,\n alternateTitles: doc.subtitle ? [doc.subtitle] : [],\n year,\n coverUrl,\n relevanceScore: Math.min(1.0, relevanceScore),\n preview: {\n genres: doc.subject?.slice(0, 5) || [],\n rating: doc.ratings_average\n ? Math.round(doc.ratings_average * 2) / 2 // Normalize to 0-10 scale (OL uses 1-5)\n : undefined,\n authors: doc.author_name?.slice(0, 3) || [],\n description: doc.publisher?.length ? `Published by ${doc.publisher[0]}` : undefined,\n },\n };\n}\n\n/**\n * Resolve author references to full author data\n */\nasync function resolveAuthors(\n authorRefs: OLAuthorReference[] | undefined,\n): Promise<ParsedAuthor[]> {\n if (!authorRefs?.length) return [];\n\n const authors: ParsedAuthor[] = [];\n\n for (const ref of authorRefs) {\n const key = ref.author?.key || ref.key;\n if (!key) continue;\n\n const authorData = await getAuthor(key);\n if (authorData) {\n authors.push({\n name: authorData.name,\n key,\n sortName: authorData.personal_name || undefined,\n });\n }\n }\n\n return authors;\n}\n\n/**\n * Map parsed authors to BookAuthor format\n */\nfunction mapToBookAuthors(authors: ParsedAuthor[]): BookAuthor[] {\n return authors.map((author) => ({\n name: author.name,\n role: \"author\" as const,\n sortName: author.sortName,\n }));\n}\n\n/**\n * Build cover URLs for a book\n */\nfunction buildCoverUrls(isbn: string | undefined, coverId: number | undefined): BookCover[] {\n const covers: BookCover[] = [];\n\n // Prefer ISBN-based URLs as they're more reliable\n if (isbn) {\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"S\"),\n size: \"small\",\n });\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"M\"),\n size: \"medium\",\n });\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"L\"),\n size: \"large\",\n });\n } else if (coverId) {\n // Fallback to cover ID\n covers.push({\n url: getCoverUrlById(coverId, \"S\"),\n size: \"small\",\n });\n covers.push({\n url: getCoverUrlById(coverId, \"M\"),\n size: \"medium\",\n });\n covers.push({\n url: getCoverUrlById(coverId, \"L\"),\n size: \"large\",\n });\n }\n\n return covers;\n}\n\n/**\n * Build external links for Open Library book\n */\nfunction buildExternalLinks(editionKey: string, workKey: string | undefined): ExternalLink[] {\n const links: ExternalLink[] = [\n {\n url: buildOpenLibraryUrl(editionKey),\n label: \"Open Library (Edition)\",\n linkType: \"provider\",\n },\n ];\n\n if (workKey) {\n links.push({\n url: buildOpenLibraryUrl(workKey),\n label: \"Open Library (Work)\",\n linkType: \"provider\",\n });\n }\n\n return links;\n}\n\n/**\n * Get all ISBNs from edition data\n */\nfunction collectIsbns(edition: OLEdition): string[] {\n const isbns: string[] = [];\n\n // Prefer ISBN-13\n if (edition.isbn_13?.length) {\n isbns.push(...edition.isbn_13);\n }\n\n // Add ISBN-10 as well\n if (edition.isbn_10?.length) {\n isbns.push(...edition.isbn_10);\n }\n\n return [...new Set(isbns)]; // Deduplicate\n}\n\n/**\n * Map Open Library edition and optional work to full book metadata\n */\nexport async function mapEditionToBookMetadata(\n edition: OLEdition,\n workData?: OLWork | null,\n): Promise<PluginBookMetadata> {\n // Resolve authors from edition or work\n const authorRefs = edition.authors || workData?.authors;\n const authors = await resolveAuthors(authorRefs);\n\n // Get ISBNs\n const isbns = collectIsbns(edition);\n const primaryIsbn = isbns[0];\n\n // Get cover ID from edition or work\n const coverId = edition.covers?.[0] || workData?.covers?.[0];\n\n // Get description from edition or work\n const description =\n parseDescription(edition.description) || parseDescription(workData?.description);\n\n // Get subjects from both edition and work\n const subjects = [...(edition.subjects || []), ...(workData?.subjects || [])];\n const uniqueSubjects = [...new Set(subjects)];\n\n // Parse year\n const year = parseYear(edition.publish_date);\n const originalYear = parseYear(workData?.first_publish_date);\n\n // Parse language\n const language = parseLanguage(edition.languages?.[0]?.key);\n\n // Build external rating if ratings exist from search\n const externalRatings: ExternalRating[] = [];\n\n // Build metadata\n const workKey = edition.works?.[0]?.key || workData?.key;\n const externalId = workKey || edition.key;\n\n return {\n externalId,\n externalUrl: buildOpenLibraryUrl(externalId),\n\n // Core fields\n title: edition.title,\n subtitle: edition.subtitle || workData?.subtitle,\n alternateTitles: [],\n summary: description,\n bookType: detectBookType(edition),\n\n // Book-specific fields\n pageCount: edition.number_of_pages,\n year,\n\n // ISBN\n isbn: primaryIsbn,\n isbns,\n\n // Edition info\n edition: edition.edition_name,\n originalTitle: workData?.title !== edition.title ? workData?.title : undefined,\n originalYear,\n language,\n\n // Taxonomy\n genres: [], // Open Library doesn't have genres, just subjects\n tags: [],\n subjects: uniqueSubjects.slice(0, 20), // Limit to 20 subjects\n\n // Credits\n authors: mapToBookAuthors(authors),\n artists: [], // Open Library doesn't track artists separately\n publisher: edition.publishers?.[0],\n\n // Media\n coverUrl: primaryIsbn\n ? getCoverUrlByIsbn(primaryIsbn, \"L\")\n : coverId\n ? getCoverUrlById(coverId, \"L\")\n : undefined,\n covers: buildCoverUrls(primaryIsbn, coverId),\n\n // Rating\n externalRatings,\n awards: [],\n\n // Links\n externalLinks: buildExternalLinks(edition.key, workKey),\n };\n}\n\n/**\n * Detect book type from edition data\n *\n * Open Library doesn't have explicit book type, but we can infer from:\n * - physical_format field\n * - subjects\n * - other metadata\n */\nfunction detectBookType(edition: OLEdition): string | undefined {\n const format = edition.physical_format?.toLowerCase();\n\n if (format) {\n if (format.includes(\"comic\") || format.includes(\"graphic novel\")) {\n return \"graphic_novel\";\n }\n if (format.includes(\"manga\")) {\n return \"manga\";\n }\n if (format.includes(\"magazine\") || format.includes(\"periodical\")) {\n return \"magazine\";\n }\n }\n\n // Check subjects for hints\n const subjects = (edition.subjects || []).join(\" \").toLowerCase();\n\n if (subjects.includes(\"graphic novel\") || subjects.includes(\"comics\")) {\n return \"graphic_novel\";\n }\n if (subjects.includes(\"manga\")) {\n return \"manga\";\n }\n\n // Default to novel for most books\n return \"novel\";\n}\n\n/**\n * Map Open Library search doc to book metadata for quick preview\n *\n * This is a lighter version that doesn't fetch additional data\n */\nexport function mapSearchDocToBookPreview(doc: OLSearchDoc): PluginBookMetadata {\n const isbns = doc.isbn?.slice(0, 5) || [];\n const primaryIsbn = isbns[0];\n const coverId = doc.cover_i;\n\n return {\n externalId: doc.key,\n externalUrl: buildOpenLibraryUrl(doc.key),\n\n // Core fields\n title: doc.title,\n subtitle: doc.subtitle,\n alternateTitles: [],\n summary: undefined, // Not available in search results\n\n // Book-specific fields\n pageCount: doc.number_of_pages_median,\n year: doc.first_publish_year,\n\n // ISBN\n isbn: primaryIsbn,\n isbns,\n\n // Taxonomy\n genres: [],\n tags: [],\n subjects: doc.subject?.slice(0, 10) || [],\n\n // Credits\n authors:\n doc.author_name?.map((name) => ({\n name,\n role: \"author\" as const,\n })) || [],\n artists: [],\n publisher: doc.publisher?.[0],\n\n // Media\n coverUrl: primaryIsbn\n ? getCoverUrlByIsbn(primaryIsbn, \"L\")\n : coverId\n ? getCoverUrlById(coverId, \"L\")\n : undefined,\n covers: buildCoverUrls(primaryIsbn, coverId),\n\n // Rating\n rating: doc.ratings_average\n ? {\n score: Math.round(doc.ratings_average * 20), // Convert 1-5 to 0-100\n voteCount: doc.ratings_count,\n source: \"openlibrary\",\n }\n : undefined,\n externalRatings:\n doc.ratings_average && doc.ratings_count\n ? [\n {\n score: Math.round(doc.ratings_average * 20),\n voteCount: doc.ratings_count,\n source: \"openlibrary\",\n },\n ]\n : [],\n awards: [],\n\n // Links\n externalLinks: [\n {\n url: buildOpenLibraryUrl(doc.key),\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n}\n\n/**\n * Get full book metadata by fetching edition, work, and author data\n *\n * @param editionOrWorkKey Either an edition key or work key\n * @param isbn Optional ISBN for direct lookup\n */\nexport async function getFullBookMetadata(\n editionOrWorkKey: string,\n isbn?: string,\n): Promise<PluginBookMetadata | null> {\n // If we have an ISBN, try to get edition directly\n if (isbn) {\n const { getEditionByIsbn } = await import(\"./api.js\");\n const edition = await getEditionByIsbn(isbn);\n if (edition) {\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n return mapEditionToBookMetadata(edition, workData);\n }\n }\n\n // Check if it's a work key\n if (editionOrWorkKey.includes(\"/works/\")) {\n const workData = await getWork(editionOrWorkKey);\n if (!workData) return null;\n\n // Fetch editions directly from the work using the editions API.\n // This is much more reliable than searching by title, which can\n // return completely unrelated books with similar titles.\n const editions = await getWorkEditions(editionOrWorkKey, 5);\n\n if (editions.length > 0) {\n // Prefer an edition that has ISBNs for richer metadata\n const editionWithIsbn = editions.find((e) => e.isbn_13?.length || e.isbn_10?.length);\n const edition = editionWithIsbn || editions[0];\n return mapEditionToBookMetadata(edition, workData);\n }\n\n // Fallback: create metadata from work data only\n const authors = await resolveAuthors(workData.authors);\n const coverId = workData.covers?.[0];\n\n return {\n externalId: workData.key,\n externalUrl: buildOpenLibraryUrl(workData.key),\n title: workData.title,\n subtitle: workData.subtitle,\n alternateTitles: [],\n summary: parseDescription(workData.description),\n isbns: [],\n genres: [],\n tags: [],\n subjects: workData.subjects?.slice(0, 20) || [],\n authors: mapToBookAuthors(authors),\n artists: [],\n coverUrl: coverId ? getCoverUrlById(coverId, \"L\") : undefined,\n covers: coverId\n ? [\n { url: getCoverUrlById(coverId, \"S\"), size: \"small\" },\n { url: getCoverUrlById(coverId, \"M\"), size: \"medium\" },\n { url: getCoverUrlById(coverId, \"L\"), size: \"large\" },\n ]\n : [],\n externalRatings: [],\n awards: [],\n externalLinks: [\n {\n url: buildOpenLibraryUrl(workData.key),\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n }\n\n // It's an edition key - fetch directly\n // For edition keys, we need to use a different approach\n // since there's no direct edition endpoint by key\n // Try to use the key directly\n const url = `https://openlibrary.org${editionOrWorkKey}.json`;\n try {\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": \"Codex/1.0 (https://github.com/AshDevFr/codex; codex-plugin)\",\n Accept: \"application/json\",\n },\n });\n\n if (response.ok) {\n const edition = (await response.json()) as OLEdition;\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n return mapEditionToBookMetadata(edition, workData);\n }\n } catch {\n // Ignore fetch errors\n }\n\n return null;\n}\n"],
|
|
4
|
+
"sourcesContent": ["/**\n * Open Library API Client\n *\n * Handles communication with the Open Library API with:\n * - Rate limiting (100 requests per 5 minutes recommended)\n * - Caching to reduce API calls\n * - Error handling with retries\n *\n * @see https://openlibrary.org/developers/api\n */\n\nimport type {\n OLAuthor,\n OLEdition,\n OLSearchResponse,\n OLWork,\n OLWorkEditionsResponse,\n} from \"./types.js\";\n\nconst BASE_URL = \"https://openlibrary.org\";\nconst COVERS_BASE_URL = \"https://covers.openlibrary.org\";\n\n// Simple in-memory cache with TTL\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\nconst CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes\nconst cache = new Map<string, CacheEntry<unknown>>();\n\n/**\n * Get cached value if not expired\n */\nfunction getCached<T>(key: string): T | null {\n const entry = cache.get(key);\n if (entry && Date.now() - entry.timestamp < CACHE_TTL_MS) {\n return entry.data as T;\n }\n if (entry) {\n cache.delete(key); // Cleanup expired\n }\n return null;\n}\n\n/**\n * Store value in cache\n */\nfunction setCache<T>(key: string, data: T): void {\n cache.set(key, { data, timestamp: Date.now() });\n}\n\n/**\n * Make an HTTP request with error handling\n */\nasync function fetchJson<T>(url: string, description: string): Promise<T | null> {\n // Check cache first\n const cached = getCached<T>(url);\n if (cached !== null) {\n return cached;\n }\n\n try {\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": \"Codex/1.0 (https://github.com/AshDevFr/codex; codex-plugin)\",\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as T;\n setCache(url, data);\n return data;\n } catch (error) {\n console.error(`[openlibrary] Failed to fetch ${description}:`, error);\n return null;\n }\n}\n\n/**\n * Normalize ISBN by removing hyphens and spaces\n */\nexport function normalizeIsbn(isbn: string): string {\n return isbn.replace(/[-\\s]/g, \"\").toUpperCase();\n}\n\n/**\n * Check if a string is a valid ISBN-10 or ISBN-13\n */\nexport function isValidIsbn(isbn: string): boolean {\n const normalized = normalizeIsbn(isbn);\n return normalized.length === 10 || normalized.length === 13;\n}\n\n/**\n * Fetch book edition by ISBN\n *\n * @param isbn ISBN-10 or ISBN-13\n * @returns Edition data or null if not found\n */\nexport async function getEditionByIsbn(isbn: string): Promise<OLEdition | null> {\n const normalized = normalizeIsbn(isbn);\n const url = `${BASE_URL}/isbn/${normalized}.json`;\n return fetchJson<OLEdition>(url, `edition by ISBN ${normalized}`);\n}\n\n/**\n * Fetch work details by key\n *\n * @param workKey Work key (e.g., \"/works/OL45883W\" or just \"OL45883W\")\n * @returns Work data or null if not found\n */\nexport async function getWork(workKey: string): Promise<OLWork | null> {\n // Normalize key to just the ID part\n const key = workKey.startsWith(\"/works/\") ? workKey : `/works/${workKey}`;\n const url = `${BASE_URL}${key}.json`;\n return fetchJson<OLWork>(url, `work ${key}`);\n}\n\n/**\n * Fetch editions for a work\n *\n * Returns editions directly associated with a work, ordered by most recent.\n * This is more reliable than searching by title, which can return unrelated books.\n *\n * @param workKey Work key (e.g., \"/works/OL45883W\")\n * @param limit Maximum number of editions to fetch\n * @returns Array of editions or empty array if none found\n */\nexport async function getWorkEditions(workKey: string, limit = 5): Promise<OLEdition[]> {\n const key = workKey.startsWith(\"/works/\") ? workKey : `/works/${workKey}`;\n const url = `${BASE_URL}${key}/editions.json?limit=${limit}`;\n const response = await fetchJson<OLWorkEditionsResponse>(url, `editions for ${key}`);\n return response?.entries || [];\n}\n\n/**\n * Fetch author details by key\n *\n * @param authorKey Author key (e.g., \"/authors/OL34184A\" or just \"OL34184A\")\n * @returns Author data or null if not found\n */\nexport async function getAuthor(authorKey: string): Promise<OLAuthor | null> {\n // Normalize key to just the ID part\n const key = authorKey.startsWith(\"/authors/\") ? authorKey : `/authors/${authorKey}`;\n const url = `${BASE_URL}${key}.json`;\n return fetchJson<OLAuthor>(url, `author ${key}`);\n}\n\n/** Fields to request from the Open Library search API */\nconst SEARCH_FIELDS = [\n \"key\",\n \"title\",\n \"subtitle\",\n \"author_name\",\n \"author_key\",\n \"first_publish_year\",\n \"publish_year\",\n \"publisher\",\n \"isbn\",\n \"number_of_pages_median\",\n \"cover_i\",\n \"cover_edition_key\",\n \"edition_count\",\n \"language\",\n \"subject\",\n \"ratings_average\",\n \"ratings_count\",\n].join(\",\");\n\n/**\n * Search for books\n *\n * When an author is provided, uses the `title` + `author` parameters for\n * more precise results. If that yields no results, falls back to a general\n * `q` search to ensure we still return something useful.\n *\n * @param query Search query (title, author, or combined)\n * @param options Additional search options\n * @returns Search results\n */\nexport async function searchBooks(\n query: string,\n options: {\n author?: string;\n limit?: number;\n } = {},\n): Promise<OLSearchResponse | null> {\n const { author, limit = 10 } = options;\n\n // When author is provided, try a refined title + author search first\n if (author) {\n const params = new URLSearchParams({\n title: query,\n author,\n fields: SEARCH_FIELDS,\n limit: String(limit),\n });\n\n const url = `${BASE_URL}/search.json?${params}`;\n const response = await fetchJson<OLSearchResponse>(\n url,\n `search title=\"${query}\" author=\"${author}\"`,\n );\n\n if (response?.docs?.length) {\n return response;\n }\n\n // Fall back to general q search if title+author yielded no results\n }\n\n // General search using q parameter\n const params = new URLSearchParams({\n q: query,\n fields: SEARCH_FIELDS,\n limit: String(limit),\n });\n\n if (author) {\n params.set(\"author\", author);\n }\n\n const url = `${BASE_URL}/search.json?${params}`;\n return fetchJson<OLSearchResponse>(url, `search \"${query}\"`);\n}\n\n/**\n * Get cover image URL by ISBN\n *\n * @param isbn ISBN-10 or ISBN-13\n * @param size Cover size: S (small ~50w), M (medium ~180w), L (large ~300w+)\n * @returns Cover URL\n */\nexport function getCoverUrlByIsbn(isbn: string, size: \"S\" | \"M\" | \"L\"): string {\n const normalized = normalizeIsbn(isbn);\n return `${COVERS_BASE_URL}/b/isbn/${normalized}-${size}.jpg`;\n}\n\n/**\n * Get cover image URL by cover ID\n *\n * @param coverId Open Library cover ID\n * @param size Cover size: S (small), M (medium), L (large)\n * @returns Cover URL\n */\nexport function getCoverUrlById(coverId: number, size: \"S\" | \"M\" | \"L\"): string {\n return `${COVERS_BASE_URL}/b/id/${coverId}-${size}.jpg`;\n}\n\n/**\n * Get cover image URL by Open Library ID (OLID)\n *\n * @param olid Open Library ID (e.g., \"OL7353617M\" for edition, \"OL45883W\" for work)\n * @param size Cover size: S (small), M (medium), L (large)\n * @returns Cover URL\n */\nexport function getCoverUrlByOlid(olid: string, size: \"S\" | \"M\" | \"L\"): string {\n // Strip any prefix if present\n const id = olid.replace(/^\\/(?:books|works)\\//, \"\");\n return `${COVERS_BASE_URL}/b/olid/${id}-${size}.jpg`;\n}\n\n/**\n * Parse year from Open Library date string\n *\n * Open Library dates can be in various formats:\n * - \"2020\"\n * - \"January 1, 2020\"\n * - \"2020-01-15\"\n * - \"c1985\"\n * - \"1985?\"\n *\n * @param dateStr Date string from Open Library\n * @returns Parsed year or undefined if unable to parse\n */\nexport function parseYear(dateStr: string | undefined): number | undefined {\n if (!dateStr) return undefined;\n\n // Try to extract a 4-digit year\n // Using (?:^|[^0-9]) to handle \"c1985\" format where there's no word boundary\n const match = dateStr.match(/(?:^|[^0-9])(1[89]\\d{2}|20\\d{2})(?:[^0-9]|$)/);\n if (match) {\n return Number.parseInt(match[1], 10);\n }\n\n return undefined;\n}\n\n/**\n * Parse description from Open Library\n *\n * Description can be either a string or an object with { type, value }.\n * Strips HTML tags and normalizes whitespace, since Open Library descriptions\n * can contain raw HTML (e.g., from Standard Ebooks imports).\n */\nexport function parseDescription(\n desc: string | { type?: string; value: string } | undefined,\n): string | undefined {\n if (!desc) return undefined;\n const raw = typeof desc === \"string\" ? desc : desc.value;\n return stripHtml(raw);\n}\n\n/**\n * Strip HTML tags from a string and normalize whitespace.\n *\n * Converts block-level tags (p, br, div, li) to newlines,\n * strips all remaining tags, decodes common HTML entities,\n * and collapses excessive whitespace.\n */\nfunction stripHtml(html: string): string | undefined {\n let text = html;\n\n // Convert block-level elements to newlines\n text = text.replace(/<\\/(p|div|li|tr|h[1-6])>/gi, \"\\n\");\n text = text.replace(/<br\\s*\\/?>/gi, \"\\n\");\n\n // Remove all remaining HTML tags\n text = text.replace(/<[^>]+>/g, \"\");\n\n // Decode common HTML entities\n text = text\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/'/g, \"'\")\n .replace(/ /g, \" \");\n\n // Collapse multiple spaces/tabs on the same line into one space\n text = text.replace(/[^\\S\\n]+/g, \" \");\n\n // Collapse 3+ consecutive newlines into 2\n text = text.replace(/\\n{3,}/g, \"\\n\\n\");\n\n // Trim each line and remove leading/trailing whitespace\n text = text\n .split(\"\\n\")\n .map((line) => line.trim())\n .join(\"\\n\")\n .trim();\n\n return text || undefined;\n}\n\n/**\n * Convert Open Library language code to BCP47\n *\n * Open Library uses format like \"/languages/eng\"\n *\n * @param langRef Language reference (e.g., \"/languages/eng\")\n * @returns BCP47 language code (e.g., \"en\")\n */\nexport function parseLanguage(langRef: string | undefined): string | undefined {\n if (!langRef) return undefined;\n\n // Extract language code from \"/languages/xxx\" format\n const match = langRef.match(/\\/languages\\/(\\w+)$/);\n if (!match) return undefined;\n\n const code = match[1].toLowerCase();\n\n // Map Open Library 3-letter codes to BCP47 2-letter codes\n const languageMap: Record<string, string> = {\n eng: \"en\",\n spa: \"es\",\n fre: \"fr\",\n fra: \"fr\",\n ger: \"de\",\n deu: \"de\",\n ita: \"it\",\n por: \"pt\",\n rus: \"ru\",\n jpn: \"ja\",\n chi: \"zh\",\n zho: \"zh\",\n kor: \"ko\",\n ara: \"ar\",\n hin: \"hi\",\n pol: \"pl\",\n tur: \"tr\",\n dut: \"nl\",\n nld: \"nl\",\n swe: \"sv\",\n nor: \"no\",\n dan: \"da\",\n fin: \"fi\",\n cze: \"cs\",\n ces: \"cs\",\n gre: \"el\",\n ell: \"el\",\n heb: \"he\",\n hun: \"hu\",\n rom: \"ro\",\n ron: \"ro\",\n tha: \"th\",\n vie: \"vi\",\n ind: \"id\",\n mal: \"ms\",\n msa: \"ms\",\n ukr: \"uk\",\n cat: \"ca\",\n lat: \"la\",\n };\n\n return languageMap[code] || code;\n}\n\n/**\n * Extract Open Library ID from a key\n *\n * @param key Full key (e.g., \"/works/OL45883W\" or \"/books/OL7353617M\")\n * @returns Just the ID (e.g., \"OL45883W\" or \"OL7353617M\")\n */\nexport function extractOlid(key: string): string {\n return key.replace(/^\\/(?:works|books|authors)\\//, \"\");\n}\n\n/**\n * Build Open Library URL from a key\n *\n * @param key Key (e.g., \"/works/OL45883W\")\n * @returns Full URL (e.g., \"https://openlibrary.org/works/OL45883W\")\n */\nexport function buildOpenLibraryUrl(key: string): string {\n return `${BASE_URL}${key.startsWith(\"/\") ? key : `/${key}`}`;\n}\n\n/**\n * Clear the cache\n */\nexport function clearCache(): void {\n cache.clear();\n}\n", null, null, null, null, "/**\n * Open Library Metadata Plugin for Codex\n *\n * Fetches book metadata from Open Library (openlibrary.org), a free and open\n * book database with extensive ISBN coverage.\n *\n * Features:\n * - ISBN lookup for direct, accurate matching\n * - Title/author search for fuzzy matching\n * - Cover image fetching in multiple sizes\n * - Author resolution with proper names\n * - Subject/genre extraction\n *\n * @see https://openlibrary.org/developers/api\n */\n\nimport {\n type BookMatchParams,\n type BookMetadataProvider,\n type BookSearchParams,\n createLogger,\n createMetadataPlugin,\n type InitializeParams,\n type MetadataGetParams,\n type MetadataMatchResponse,\n type MetadataSearchResponse,\n type PluginBookMetadata,\n} from \"@ashdev/codex-plugin-sdk\";\n\nimport { getEditionByIsbn, getWork, isValidIsbn, searchBooks } from \"./api.js\";\nimport { DEFAULT_MAX_RESULTS, manifest } from \"./manifest.js\";\nimport {\n getFullBookMetadata,\n mapEditionToBookMetadata,\n mapSearchDocToSearchResult,\n} from \"./mapper.js\";\n\nconst logger = createLogger({ name: \"openlibrary\", level: \"info\" });\n\n// Plugin configuration (set during initialization)\nconst config = {\n maxResults: DEFAULT_MAX_RESULTS,\n};\n\n/**\n * Book metadata provider implementation\n */\nconst bookProvider: BookMetadataProvider = {\n /**\n * Search for books by ISBN or title/author query\n *\n * If ISBN is provided, it takes priority for direct lookup.\n * Otherwise, falls back to title/author search.\n */\n async search(params: BookSearchParams): Promise<MetadataSearchResponse> {\n const { isbn, query, author, limit } = params;\n const maxResults = Math.min(limit || config.maxResults, 50);\n\n // If ISBN is provided, try direct lookup first\n if (isbn && isValidIsbn(isbn)) {\n const edition = await getEditionByIsbn(isbn);\n\n if (edition) {\n // Found by ISBN - return as single result with high relevance\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n const metadata = await mapEditionToBookMetadata(edition, workData);\n\n return {\n results: [\n {\n externalId: metadata.externalId,\n title: metadata.title || \"Unknown\",\n alternateTitles: metadata.subtitle ? [metadata.subtitle] : [],\n year: metadata.year,\n coverUrl: metadata.coverUrl,\n relevanceScore: 1.0, // Perfect match by ISBN\n preview: {\n genres: metadata.subjects.slice(0, 5),\n authors: metadata.authors.map((a) => a.name),\n },\n },\n ],\n };\n }\n\n // ISBN not found, fall through to search if query is also provided\n if (!query) {\n return { results: [] };\n }\n }\n\n // Title/author search\n if (!query) {\n return { results: [] };\n }\n\n const searchResponse = await searchBooks(query, {\n author,\n limit: maxResults,\n });\n\n if (!searchResponse?.docs?.length) {\n return { results: [] };\n }\n\n return {\n results: searchResponse.docs.map(mapSearchDocToSearchResult),\n };\n },\n\n /**\n * Get full book metadata by external ID\n *\n * The external ID can be:\n * - A work key: \"/works/OL45883W\"\n * - An edition key: \"/books/OL7353617M\"\n */\n async get(params: MetadataGetParams): Promise<PluginBookMetadata> {\n const { externalId } = params;\n\n // Try to get full metadata\n const metadata = await getFullBookMetadata(externalId);\n\n if (metadata) {\n return metadata;\n }\n\n // Fallback: return minimal metadata\n return {\n externalId,\n externalUrl: `https://openlibrary.org${externalId.startsWith(\"/\") ? externalId : `/${externalId}`}`,\n alternateTitles: [],\n isbns: [],\n genres: [],\n tags: [],\n subjects: [],\n authors: [],\n artists: [],\n covers: [],\n externalRatings: [],\n awards: [],\n externalLinks: [\n {\n url: `https://openlibrary.org${externalId.startsWith(\"/\") ? externalId : `/${externalId}`}`,\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n },\n\n /**\n * Auto-match a book using available identifiers\n *\n * Match priority:\n * 1. ISBN (if provided) - highest confidence\n * 2. Title + author search - lower confidence\n */\n async match(params: BookMatchParams): Promise<MetadataMatchResponse> {\n const { title, authors, isbn, year } = params;\n\n // Try ISBN first if available\n if (isbn && isValidIsbn(isbn)) {\n const edition = await getEditionByIsbn(isbn);\n\n if (edition) {\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n const metadata = await mapEditionToBookMetadata(edition, workData);\n\n return {\n match: {\n externalId: metadata.externalId,\n title: metadata.title || \"Unknown\",\n alternateTitles: metadata.subtitle ? [metadata.subtitle] : [],\n year: metadata.year,\n coverUrl: metadata.coverUrl,\n relevanceScore: 1.0,\n preview: {\n genres: metadata.subjects.slice(0, 5),\n authors: metadata.authors.map((a) => a.name),\n },\n },\n confidence: 0.99, // Very high confidence for ISBN match\n alternatives: [],\n };\n }\n }\n\n // Fall back to title search\n const searchQuery = authors?.length ? `${title} ${authors[0]}` : title;\n\n const searchResponse = await searchBooks(searchQuery, {\n limit: 5,\n });\n\n if (!searchResponse?.docs?.length) {\n return {\n match: null,\n confidence: 0,\n alternatives: [],\n };\n }\n\n const results = searchResponse.docs.map(mapSearchDocToSearchResult);\n\n // Calculate confidence based on title similarity and other factors\n const bestMatch = results[0];\n let confidence = bestMatch.relevanceScore || 0.5;\n\n // Boost confidence if title matches closely\n const normalizedTitle = title.toLowerCase().trim();\n const normalizedMatchTitle = bestMatch.title.toLowerCase().trim();\n\n if (normalizedTitle === normalizedMatchTitle) {\n confidence = Math.min(1.0, confidence + 0.3);\n } else if (\n normalizedMatchTitle.includes(normalizedTitle) ||\n normalizedTitle.includes(normalizedMatchTitle)\n ) {\n confidence = Math.min(1.0, confidence + 0.15);\n }\n\n // Boost if year matches\n if (year && bestMatch.year === year) {\n confidence = Math.min(1.0, confidence + 0.1);\n }\n\n // Reduce confidence without ISBN\n confidence = Math.min(confidence, 0.85);\n\n return {\n match: bestMatch,\n confidence,\n alternatives: results.slice(1),\n };\n },\n};\n\n// =============================================================================\n// Plugin Initialization\n// =============================================================================\n\ncreateMetadataPlugin({\n manifest,\n bookProvider,\n logLevel: \"info\",\n onInitialize(params: InitializeParams) {\n // Read config from initialization params\n const maxResults = params.config?.maxResults as number | undefined;\n if (maxResults !== undefined) {\n config.maxResults = Math.min(Math.max(1, maxResults), 50); // Clamp 1-50\n }\n logger.info(`Plugin initialized (maxResults: ${config.maxResults})`);\n },\n});\n\nlogger.info(\"Open Library plugin started\");\n", "{\n \"name\": \"@ashdev/codex-plugin-metadata-openlibrary\",\n \"version\": \"1.9.2\",\n \"description\": \"Open Library metadata plugin for Codex - fetches book metadata by ISBN or title search\",\n \"main\": \"dist/index.js\",\n \"bin\": \"dist/index.js\",\n \"type\": \"module\",\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/AshDevFr/codex.git\",\n \"directory\": \"plugins/metadata-openlibrary\"\n },\n \"scripts\": {\n \"build\": \"esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/index.js --sourcemap --banner:js='#!/usr/bin/env node'\",\n \"dev\": \"npm run build -- --watch\",\n \"clean\": \"rm -rf dist\",\n \"start\": \"node dist/index.js\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"npm run lint && npm run build\"\n },\n \"keywords\": [\n \"codex\",\n \"plugin\",\n \"openlibrary\",\n \"metadata\",\n \"books\",\n \"isbn\"\n ],\n \"author\": \"Codex\",\n \"license\": \"MIT\",\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"dependencies\": {\n \"@ashdev/codex-plugin-sdk\": \"^1.9.2\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.3.13\",\n \"@types/node\": \"^22.0.0\",\n \"esbuild\": \"^0.24.0\",\n \"typescript\": \"^5.7.0\",\n \"vitest\": \"^3.0.0\"\n }\n}\n", "import type { MetadataContentType, PluginManifest } from \"@ashdev/codex-plugin-sdk\";\nimport packageJson from \"../package.json\" with { type: \"json\" };\n\n// Default config values\nexport const DEFAULT_MAX_RESULTS = 10;\n\nexport const manifest = {\n name: \"metadata-openlibrary\",\n displayName: \"Open Library\",\n version: packageJson.version,\n description:\n \"Fetches book metadata from Open Library (openlibrary.org). Supports ISBN lookup and title search for EPUBs, PDFs, and other book formats.\",\n author: \"Codex\",\n homepage: \"https://openlibrary.org\",\n protocolVersion: \"1.0\",\n capabilities: {\n // Book metadata provider only (not series)\n metadataProvider: [\"book\"] as MetadataContentType[],\n },\n configSchema: {\n description: \"Configuration options for the Open Library plugin\",\n fields: [\n {\n key: \"maxResults\",\n label: \"Maximum Results\",\n description: \"Maximum number of results to return for search queries (1-50)\",\n type: \"number\" as const,\n required: false,\n default: DEFAULT_MAX_RESULTS,\n example: 20,\n },\n ],\n },\n} as const satisfies PluginManifest & {\n capabilities: { metadataProvider: MetadataContentType[] };\n};\n", "/**\n * Mapper functions to convert Open Library data to Codex plugin format\n */\n\nimport type {\n BookAuthor,\n BookCover,\n ExternalLink,\n ExternalRating,\n PluginBookMetadata,\n SearchResult,\n} from \"@ashdev/codex-plugin-sdk\";\n\nimport {\n buildOpenLibraryUrl,\n getAuthor,\n getCoverUrlById,\n getCoverUrlByIsbn,\n getWork,\n getWorkEditions,\n parseDescription,\n parseLanguage,\n parseYear,\n} from \"./api.js\";\nimport type { OLAuthorReference, OLEdition, OLSearchDoc, OLWork, ParsedAuthor } from \"./types.js\";\n\n/**\n * Map Open Library search result to Codex SearchResult\n */\nexport function mapSearchDocToSearchResult(doc: OLSearchDoc): SearchResult {\n const year = doc.first_publish_year;\n const coverUrl = doc.cover_i ? getCoverUrlById(doc.cover_i, \"M\") : undefined;\n\n // Calculate a relevance score based on available data\n // More complete entries get higher scores\n let relevanceScore = 0.5;\n if (doc.author_name?.length) relevanceScore += 0.1;\n if (doc.isbn?.length) relevanceScore += 0.15;\n if (doc.cover_i) relevanceScore += 0.1;\n if (doc.first_publish_year) relevanceScore += 0.05;\n if (doc.subject?.length) relevanceScore += 0.05;\n if (doc.ratings_count && doc.ratings_count > 0) relevanceScore += 0.05;\n\n return {\n externalId: doc.key, // Work key, e.g., \"/works/OL45883W\"\n title: doc.title,\n alternateTitles: doc.subtitle ? [doc.subtitle] : [],\n year,\n coverUrl,\n relevanceScore: Math.min(1.0, relevanceScore),\n preview: {\n genres: doc.subject?.slice(0, 5) || [],\n rating: doc.ratings_average\n ? Math.round(doc.ratings_average * 2) / 2 // Normalize to 0-10 scale (OL uses 1-5)\n : undefined,\n authors: doc.author_name?.slice(0, 3) || [],\n description: doc.publisher?.length ? `Published by ${doc.publisher[0]}` : undefined,\n },\n };\n}\n\n/**\n * Resolve author references to full author data\n */\nasync function resolveAuthors(\n authorRefs: OLAuthorReference[] | undefined,\n): Promise<ParsedAuthor[]> {\n if (!authorRefs?.length) return [];\n\n const authors: ParsedAuthor[] = [];\n\n for (const ref of authorRefs) {\n const key = ref.author?.key || ref.key;\n if (!key) continue;\n\n const authorData = await getAuthor(key);\n if (authorData) {\n authors.push({\n name: authorData.name,\n key,\n sortName: authorData.personal_name || undefined,\n });\n }\n }\n\n return authors;\n}\n\n/**\n * Map parsed authors to BookAuthor format\n */\nfunction mapToBookAuthors(authors: ParsedAuthor[]): BookAuthor[] {\n return authors.map((author) => ({\n name: author.name,\n role: \"author\" as const,\n sortName: author.sortName,\n }));\n}\n\n/**\n * Build cover URLs for a book\n */\nfunction buildCoverUrls(isbn: string | undefined, coverId: number | undefined): BookCover[] {\n const covers: BookCover[] = [];\n\n // Prefer ISBN-based URLs as they're more reliable\n if (isbn) {\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"S\"),\n size: \"small\",\n });\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"M\"),\n size: \"medium\",\n });\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"L\"),\n size: \"large\",\n });\n } else if (coverId) {\n // Fallback to cover ID\n covers.push({\n url: getCoverUrlById(coverId, \"S\"),\n size: \"small\",\n });\n covers.push({\n url: getCoverUrlById(coverId, \"M\"),\n size: \"medium\",\n });\n covers.push({\n url: getCoverUrlById(coverId, \"L\"),\n size: \"large\",\n });\n }\n\n return covers;\n}\n\n/**\n * Build external links for Open Library book\n */\nfunction buildExternalLinks(editionKey: string, workKey: string | undefined): ExternalLink[] {\n const links: ExternalLink[] = [\n {\n url: buildOpenLibraryUrl(editionKey),\n label: \"Open Library (Edition)\",\n linkType: \"provider\",\n },\n ];\n\n if (workKey) {\n links.push({\n url: buildOpenLibraryUrl(workKey),\n label: \"Open Library (Work)\",\n linkType: \"provider\",\n });\n }\n\n return links;\n}\n\n/**\n * Get all ISBNs from edition data\n */\nfunction collectIsbns(edition: OLEdition): string[] {\n const isbns: string[] = [];\n\n // Prefer ISBN-13\n if (edition.isbn_13?.length) {\n isbns.push(...edition.isbn_13);\n }\n\n // Add ISBN-10 as well\n if (edition.isbn_10?.length) {\n isbns.push(...edition.isbn_10);\n }\n\n return [...new Set(isbns)]; // Deduplicate\n}\n\n/**\n * Map Open Library edition and optional work to full book metadata\n */\nexport async function mapEditionToBookMetadata(\n edition: OLEdition,\n workData?: OLWork | null,\n): Promise<PluginBookMetadata> {\n // Resolve authors from edition or work\n const authorRefs = edition.authors || workData?.authors;\n const authors = await resolveAuthors(authorRefs);\n\n // Get ISBNs\n const isbns = collectIsbns(edition);\n const primaryIsbn = isbns[0];\n\n // Get cover ID from edition or work\n const coverId = edition.covers?.[0] || workData?.covers?.[0];\n\n // Get description from edition or work\n const description =\n parseDescription(edition.description) || parseDescription(workData?.description);\n\n // Get subjects from both edition and work\n const subjects = [...(edition.subjects || []), ...(workData?.subjects || [])];\n const uniqueSubjects = [...new Set(subjects)];\n\n // Parse year\n const year = parseYear(edition.publish_date);\n const originalYear = parseYear(workData?.first_publish_date);\n\n // Parse language\n const language = parseLanguage(edition.languages?.[0]?.key);\n\n // Build external rating if ratings exist from search\n const externalRatings: ExternalRating[] = [];\n\n // Build metadata\n const workKey = edition.works?.[0]?.key || workData?.key;\n const externalId = workKey || edition.key;\n\n return {\n externalId,\n externalUrl: buildOpenLibraryUrl(externalId),\n\n // Core fields\n title: edition.title,\n subtitle: edition.subtitle || workData?.subtitle,\n alternateTitles: [],\n summary: description,\n bookType: detectBookType(edition),\n\n // Book-specific fields\n pageCount: edition.number_of_pages,\n year,\n\n // ISBN\n isbn: primaryIsbn,\n isbns,\n\n // Edition info\n edition: edition.edition_name,\n originalTitle: workData?.title !== edition.title ? workData?.title : undefined,\n originalYear,\n language,\n\n // Taxonomy\n genres: [], // Open Library doesn't have genres, just subjects\n tags: [],\n subjects: uniqueSubjects.slice(0, 20), // Limit to 20 subjects\n\n // Credits\n authors: mapToBookAuthors(authors),\n artists: [], // Open Library doesn't track artists separately\n publisher: edition.publishers?.[0],\n\n // Media\n coverUrl: primaryIsbn\n ? getCoverUrlByIsbn(primaryIsbn, \"L\")\n : coverId\n ? getCoverUrlById(coverId, \"L\")\n : undefined,\n covers: buildCoverUrls(primaryIsbn, coverId),\n\n // Rating\n externalRatings,\n awards: [],\n\n // Links\n externalLinks: buildExternalLinks(edition.key, workKey),\n };\n}\n\n/**\n * Detect book type from edition data\n *\n * Open Library doesn't have explicit book type, but we can infer from:\n * - physical_format field\n * - subjects\n * - other metadata\n */\nfunction detectBookType(edition: OLEdition): string | undefined {\n const format = edition.physical_format?.toLowerCase();\n\n if (format) {\n if (format.includes(\"comic\") || format.includes(\"graphic novel\")) {\n return \"graphic_novel\";\n }\n if (format.includes(\"manga\")) {\n return \"manga\";\n }\n if (format.includes(\"magazine\") || format.includes(\"periodical\")) {\n return \"magazine\";\n }\n }\n\n // Check subjects for hints\n const subjects = (edition.subjects || []).join(\" \").toLowerCase();\n\n if (subjects.includes(\"graphic novel\") || subjects.includes(\"comics\")) {\n return \"graphic_novel\";\n }\n if (subjects.includes(\"manga\")) {\n return \"manga\";\n }\n\n // Default to novel for most books\n return \"novel\";\n}\n\n/**\n * Map Open Library search doc to book metadata for quick preview\n *\n * This is a lighter version that doesn't fetch additional data\n */\nexport function mapSearchDocToBookPreview(doc: OLSearchDoc): PluginBookMetadata {\n const isbns = doc.isbn?.slice(0, 5) || [];\n const primaryIsbn = isbns[0];\n const coverId = doc.cover_i;\n\n return {\n externalId: doc.key,\n externalUrl: buildOpenLibraryUrl(doc.key),\n\n // Core fields\n title: doc.title,\n subtitle: doc.subtitle,\n alternateTitles: [],\n summary: undefined, // Not available in search results\n\n // Book-specific fields\n pageCount: doc.number_of_pages_median,\n year: doc.first_publish_year,\n\n // ISBN\n isbn: primaryIsbn,\n isbns,\n\n // Taxonomy\n genres: [],\n tags: [],\n subjects: doc.subject?.slice(0, 10) || [],\n\n // Credits\n authors:\n doc.author_name?.map((name) => ({\n name,\n role: \"author\" as const,\n })) || [],\n artists: [],\n publisher: doc.publisher?.[0],\n\n // Media\n coverUrl: primaryIsbn\n ? getCoverUrlByIsbn(primaryIsbn, \"L\")\n : coverId\n ? getCoverUrlById(coverId, \"L\")\n : undefined,\n covers: buildCoverUrls(primaryIsbn, coverId),\n\n // Rating\n rating: doc.ratings_average\n ? {\n score: Math.round(doc.ratings_average * 20), // Convert 1-5 to 0-100\n voteCount: doc.ratings_count,\n source: \"openlibrary\",\n }\n : undefined,\n externalRatings:\n doc.ratings_average && doc.ratings_count\n ? [\n {\n score: Math.round(doc.ratings_average * 20),\n voteCount: doc.ratings_count,\n source: \"openlibrary\",\n },\n ]\n : [],\n awards: [],\n\n // Links\n externalLinks: [\n {\n url: buildOpenLibraryUrl(doc.key),\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n}\n\n/**\n * Get full book metadata by fetching edition, work, and author data\n *\n * @param editionOrWorkKey Either an edition key or work key\n * @param isbn Optional ISBN for direct lookup\n */\nexport async function getFullBookMetadata(\n editionOrWorkKey: string,\n isbn?: string,\n): Promise<PluginBookMetadata | null> {\n // If we have an ISBN, try to get edition directly\n if (isbn) {\n const { getEditionByIsbn } = await import(\"./api.js\");\n const edition = await getEditionByIsbn(isbn);\n if (edition) {\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n return mapEditionToBookMetadata(edition, workData);\n }\n }\n\n // Check if it's a work key\n if (editionOrWorkKey.includes(\"/works/\")) {\n const workData = await getWork(editionOrWorkKey);\n if (!workData) return null;\n\n // Fetch editions directly from the work using the editions API.\n // This is much more reliable than searching by title, which can\n // return completely unrelated books with similar titles.\n const editions = await getWorkEditions(editionOrWorkKey, 5);\n\n if (editions.length > 0) {\n // Prefer an edition that has ISBNs for richer metadata\n const editionWithIsbn = editions.find((e) => e.isbn_13?.length || e.isbn_10?.length);\n const edition = editionWithIsbn || editions[0];\n return mapEditionToBookMetadata(edition, workData);\n }\n\n // Fallback: create metadata from work data only\n const authors = await resolveAuthors(workData.authors);\n const coverId = workData.covers?.[0];\n\n return {\n externalId: workData.key,\n externalUrl: buildOpenLibraryUrl(workData.key),\n title: workData.title,\n subtitle: workData.subtitle,\n alternateTitles: [],\n summary: parseDescription(workData.description),\n isbns: [],\n genres: [],\n tags: [],\n subjects: workData.subjects?.slice(0, 20) || [],\n authors: mapToBookAuthors(authors),\n artists: [],\n coverUrl: coverId ? getCoverUrlById(coverId, \"L\") : undefined,\n covers: coverId\n ? [\n { url: getCoverUrlById(coverId, \"S\"), size: \"small\" },\n { url: getCoverUrlById(coverId, \"M\"), size: \"medium\" },\n { url: getCoverUrlById(coverId, \"L\"), size: \"large\" },\n ]\n : [],\n externalRatings: [],\n awards: [],\n externalLinks: [\n {\n url: buildOpenLibraryUrl(workData.key),\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n }\n\n // It's an edition key - fetch directly\n // For edition keys, we need to use a different approach\n // since there's no direct edition endpoint by key\n // Try to use the key directly\n const url = `https://openlibrary.org${editionOrWorkKey}.json`;\n try {\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": \"Codex/1.0 (https://github.com/AshDevFr/codex; codex-plugin)\",\n Accept: \"application/json\",\n },\n });\n\n if (response.ok) {\n const edition = (await response.json()) as OLEdition;\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n return mapEditionToBookMetadata(edition, workData);\n }\n } catch {\n // Ignore fetch errors\n }\n\n return null;\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["params", "url", "manifest", "bookProvider", "logger", "manifest", "bookProvider", "logger", "getEditionByIsbn"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashdev/codex-plugin-metadata-openlibrary",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"description": "Open Library metadata plugin for Codex - fetches book metadata by ISBN or title search",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": "dist/index.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"node": ">=22.0.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@ashdev/codex-plugin-sdk": "^1.9.
|
|
43
|
+
"@ashdev/codex-plugin-sdk": "^1.9.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@biomejs/biome": "^2.3.13",
|