@ashdev/codex-plugin-metadata-mangabaka 1.0.0 → 1.1.0
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 +4 -4
- package/dist/index.js.map +2 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// node_modules/@ashdev/codex-plugin-sdk/dist/types/rpc.js
|
|
4
4
|
var JSON_RPC_ERROR_CODES = {
|
|
5
5
|
/** Invalid JSON was received */
|
|
6
6
|
PARSE_ERROR: -32700,
|
|
@@ -26,7 +26,7 @@ var PLUGIN_ERROR_CODES = {
|
|
|
26
26
|
CONFIG_ERROR: -32005
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
//
|
|
29
|
+
// node_modules/@ashdev/codex-plugin-sdk/dist/errors.js
|
|
30
30
|
var PluginError = class extends Error {
|
|
31
31
|
data;
|
|
32
32
|
constructor(message, data) {
|
|
@@ -77,7 +77,7 @@ var ConfigError = class extends PluginError {
|
|
|
77
77
|
code = PLUGIN_ERROR_CODES.CONFIG_ERROR;
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
//
|
|
80
|
+
// node_modules/@ashdev/codex-plugin-sdk/dist/logger.js
|
|
81
81
|
var LOG_LEVELS = {
|
|
82
82
|
debug: 0,
|
|
83
83
|
info: 1,
|
|
@@ -142,7 +142,7 @@ function createLogger(options) {
|
|
|
142
142
|
return new Logger(options);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
//
|
|
145
|
+
// node_modules/@ashdev/codex-plugin-sdk/dist/server.js
|
|
146
146
|
import { createInterface } from "node:readline";
|
|
147
147
|
function validateStringFields(params, fields) {
|
|
148
148
|
if (params === null || params === void 0) {
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["
|
|
4
|
-
"sourcesContent": ["/**\n * JSON-RPC 2.0 types for plugin communication\n */\n\nexport interface JsonRpcRequest {\n jsonrpc: \"2.0\";\n id: string | number | null;\n method: string;\n params?: unknown;\n}\n\nexport interface JsonRpcSuccessResponse {\n jsonrpc: \"2.0\";\n id: string | number | null;\n result: unknown;\n}\n\nexport interface JsonRpcErrorResponse {\n jsonrpc: \"2.0\";\n id: string | number | null;\n error: JsonRpcError;\n}\n\nexport interface JsonRpcError {\n code: number;\n message: string;\n data?: unknown;\n}\n\nexport type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;\n\n/**\n * Standard JSON-RPC error codes\n */\nexport const JSON_RPC_ERROR_CODES = {\n /** Invalid JSON was received */\n PARSE_ERROR: -32700,\n /** The JSON sent is not a valid Request object */\n INVALID_REQUEST: -32600,\n /** The method does not exist / is not available */\n METHOD_NOT_FOUND: -32601,\n /** Invalid method parameter(s) */\n INVALID_PARAMS: -32602,\n /** Internal JSON-RPC error */\n INTERNAL_ERROR: -32603,\n} as const;\n\n/**\n * Plugin-specific error codes (in the -32000 to -32099 range)\n */\nexport const PLUGIN_ERROR_CODES = {\n /** Rate limited by external API */\n RATE_LIMITED: -32001,\n /** Resource not found (e.g., series ID doesn't exist) */\n NOT_FOUND: -32002,\n /** Authentication failed (invalid credentials) */\n AUTH_FAILED: -32003,\n /** External API error */\n API_ERROR: -32004,\n /** Plugin configuration error */\n CONFIG_ERROR: -32005,\n} as const;\n", "/**\n * Plugin error classes for structured error handling\n */\n\nimport { type JsonRpcError, PLUGIN_ERROR_CODES } from \"./types/rpc.js\";\n\n/**\n * Base class for plugin errors that map to JSON-RPC errors\n */\nexport abstract class PluginError extends Error {\n abstract readonly code: number;\n readonly data?: unknown;\n\n constructor(message: string, data?: unknown) {\n super(message);\n this.name = this.constructor.name;\n this.data = data;\n }\n\n /**\n * Convert to JSON-RPC error format\n */\n toJsonRpcError(): JsonRpcError {\n return {\n code: this.code,\n message: this.message,\n data: this.data,\n };\n }\n}\n\n/**\n * Thrown when rate limited by an external API\n */\nexport class RateLimitError extends PluginError {\n readonly code = PLUGIN_ERROR_CODES.RATE_LIMITED;\n /** Seconds to wait before retrying */\n readonly retryAfterSeconds: number;\n\n constructor(retryAfterSeconds: number, message?: string) {\n super(message ?? `Rate limited, retry after ${retryAfterSeconds}s`, {\n retryAfterSeconds,\n });\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\n/**\n * Thrown when a requested resource is not found\n */\nexport class NotFoundError extends PluginError {\n readonly code = PLUGIN_ERROR_CODES.NOT_FOUND;\n}\n\n/**\n * Thrown when authentication fails (invalid credentials)\n */\nexport class AuthError extends PluginError {\n readonly code = PLUGIN_ERROR_CODES.AUTH_FAILED;\n\n constructor(message?: string) {\n super(message ?? \"Authentication failed\");\n }\n}\n\n/**\n * Thrown when an external API returns an error\n */\nexport class ApiError extends PluginError {\n readonly code = PLUGIN_ERROR_CODES.API_ERROR;\n readonly statusCode: number | undefined;\n\n constructor(message: string, statusCode?: number) {\n super(message, statusCode !== undefined ? { statusCode } : undefined);\n this.statusCode = statusCode;\n }\n}\n\n/**\n * Thrown when the plugin is misconfigured\n */\nexport class ConfigError extends PluginError {\n readonly code = PLUGIN_ERROR_CODES.CONFIG_ERROR;\n}\n", "/**\n * Logging utilities for plugins\n *\n * IMPORTANT: Plugins must ONLY write to stderr for logging.\n * stdout is reserved for JSON-RPC communication.\n */\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nexport interface LoggerOptions {\n /** Plugin name to prefix log messages */\n name: string;\n /** Minimum log level (default: \"info\") */\n level?: LogLevel;\n /** Whether to include timestamps (default: true) */\n timestamps?: boolean;\n}\n\n/**\n * Logger that writes to stderr (safe for plugins)\n */\nexport class Logger {\n private readonly name: string;\n private readonly minLevel: number;\n private readonly timestamps: boolean;\n\n constructor(options: LoggerOptions) {\n this.name = options.name;\n this.minLevel = LOG_LEVELS[options.level ?? \"info\"];\n this.timestamps = options.timestamps ?? true;\n }\n\n private shouldLog(level: LogLevel): boolean {\n return LOG_LEVELS[level] >= this.minLevel;\n }\n\n private format(level: LogLevel, message: string, data?: unknown): string {\n const parts: string[] = [];\n\n if (this.timestamps) {\n parts.push(new Date().toISOString());\n }\n\n parts.push(`[${level.toUpperCase()}]`);\n parts.push(`[${this.name}]`);\n parts.push(message);\n\n if (data !== undefined) {\n if (data instanceof Error) {\n parts.push(`- ${data.message}`);\n if (data.stack) {\n parts.push(`\\n${data.stack}`);\n }\n } else if (typeof data === \"object\") {\n parts.push(`- ${JSON.stringify(data)}`);\n } else {\n parts.push(`- ${String(data)}`);\n }\n }\n\n return parts.join(\" \");\n }\n\n private log(level: LogLevel, message: string, data?: unknown): void {\n if (this.shouldLog(level)) {\n // Write to stderr (not stdout!) - stdout is for JSON-RPC only\n process.stderr.write(`${this.format(level, message, data)}\\n`);\n }\n }\n\n debug(message: string, data?: unknown): void {\n this.log(\"debug\", message, data);\n }\n\n info(message: string, data?: unknown): void {\n this.log(\"info\", message, data);\n }\n\n warn(message: string, data?: unknown): void {\n this.log(\"warn\", message, data);\n }\n\n error(message: string, data?: unknown): void {\n this.log(\"error\", message, data);\n }\n}\n\n/**\n * Create a logger for a plugin\n */\nexport function createLogger(options: LoggerOptions): Logger {\n return new Logger(options);\n}\n", "/**\n * Plugin server - handles JSON-RPC communication over stdio\n */\n\nimport { createInterface } from \"node:readline\";\nimport { PluginError } from \"./errors.js\";\nimport { createLogger, type Logger } from \"./logger.js\";\nimport type { MetadataContentType, MetadataProvider } from \"./types/capabilities.js\";\nimport type { PluginManifest } from \"./types/manifest.js\";\nimport type {\n MetadataGetParams,\n MetadataMatchParams,\n MetadataSearchParams,\n} from \"./types/protocol.js\";\nimport {\n JSON_RPC_ERROR_CODES,\n type JsonRpcError,\n type JsonRpcRequest,\n type JsonRpcResponse,\n} from \"./types/rpc.js\";\n\n// =============================================================================\n// Parameter Validation\n// =============================================================================\n\ninterface ValidationError {\n field: string;\n message: string;\n}\n\n/**\n * Validate that the required string fields are present and non-empty\n */\nfunction validateStringFields(params: unknown, fields: string[]): ValidationError | null {\n if (params === null || params === undefined) {\n return { field: \"params\", message: \"params is required\" };\n }\n if (typeof params !== \"object\") {\n return { field: \"params\", message: \"params must be an object\" };\n }\n\n const obj = params as Record<string, unknown>;\n for (const field of fields) {\n const value = obj[field];\n if (value === undefined || value === null) {\n return { field, message: `${field} is required` };\n }\n if (typeof value !== \"string\") {\n return { field, message: `${field} must be a string` };\n }\n if (value.trim() === \"\") {\n return { field, message: `${field} cannot be empty` };\n }\n }\n\n return null;\n}\n\n/**\n * Validate MetadataSearchParams\n */\nfunction validateSearchParams(params: unknown): ValidationError | null {\n return validateStringFields(params, [\"query\"]);\n}\n\n/**\n * Validate MetadataGetParams\n */\nfunction validateGetParams(params: unknown): ValidationError | null {\n return validateStringFields(params, [\"externalId\"]);\n}\n\n/**\n * Validate MetadataMatchParams\n */\nfunction validateMatchParams(params: unknown): ValidationError | null {\n return validateStringFields(params, [\"title\"]);\n}\n\n/**\n * Create an INVALID_PARAMS error response\n */\nfunction invalidParamsError(id: string | number | null, error: ValidationError): JsonRpcResponse {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,\n message: `Invalid params: ${error.message}`,\n data: { field: error.field },\n } as JsonRpcError,\n };\n}\n\n/**\n * Initialize parameters received from Codex\n */\nexport interface InitializeParams {\n /** Plugin configuration */\n config?: Record<string, unknown>;\n /** Plugin credentials (API keys, tokens, etc.) */\n credentials?: Record<string, string>;\n}\n\n/**\n * Options for creating a metadata plugin\n */\nexport interface MetadataPluginOptions {\n /** Plugin manifest - must have capabilities.metadataProvider with content types */\n manifest: PluginManifest & {\n capabilities: { metadataProvider: MetadataContentType[] };\n };\n /** MetadataProvider implementation */\n provider: MetadataProvider;\n /** Called when plugin receives initialize with credentials/config */\n onInitialize?: (params: InitializeParams) => void | Promise<void>;\n /** Log level (default: \"info\") */\n logLevel?: \"debug\" | \"info\" | \"warn\" | \"error\";\n}\n\n/**\n * Create and run a metadata provider plugin\n *\n * Creates a plugin server that handles JSON-RPC communication over stdio.\n * The TypeScript compiler will ensure you implement all required methods.\n *\n * @example\n * ```typescript\n * import { createMetadataPlugin, type MetadataProvider } from \"@ashdev/codex-plugin-sdk\";\n *\n * const provider: MetadataProvider = {\n * async search(params) {\n * return {\n * results: [{\n * externalId: \"123\",\n * title: \"Example\",\n * alternateTitles: [],\n * relevanceScore: 0.95,\n * }],\n * };\n * },\n * async get(params) {\n * return {\n * externalId: params.externalId,\n * externalUrl: \"https://example.com/123\",\n * alternateTitles: [],\n * genres: [],\n * tags: [],\n * authors: [],\n * artists: [],\n * externalLinks: [],\n * };\n * },\n * };\n *\n * createMetadataPlugin({\n * manifest: {\n * name: \"my-plugin\",\n * displayName: \"My Plugin\",\n * version: \"1.0.0\",\n * description: \"Example plugin\",\n * author: \"Me\",\n * protocolVersion: \"1.0\",\n * capabilities: { metadataProvider: [\"series\"] },\n * },\n * provider,\n * });\n * ```\n */\nexport function createMetadataPlugin(options: MetadataPluginOptions): void {\n const { manifest, provider, onInitialize, logLevel = \"info\" } = options;\n const logger = createLogger({ name: manifest.name, level: logLevel });\n\n logger.info(`Starting plugin: ${manifest.displayName} v${manifest.version}`);\n\n const rl = createInterface({\n input: process.stdin,\n terminal: false,\n });\n\n rl.on(\"line\", (line) => {\n void handleLine(line, manifest, provider, onInitialize, logger);\n });\n\n rl.on(\"close\", () => {\n logger.info(\"stdin closed, shutting down\");\n process.exit(0);\n });\n\n // Handle uncaught errors\n process.on(\"uncaughtException\", (error) => {\n logger.error(\"Uncaught exception\", error);\n process.exit(1);\n });\n\n process.on(\"unhandledRejection\", (reason) => {\n logger.error(\"Unhandled rejection\", reason);\n });\n}\n\n// =============================================================================\n// Backwards Compatibility (deprecated)\n// =============================================================================\n\n/**\n * @deprecated Use createMetadataPlugin instead\n */\nexport function createSeriesMetadataPlugin(options: SeriesMetadataPluginOptions): void {\n // Convert legacy options to new format\n const newOptions: MetadataPluginOptions = {\n ...options,\n manifest: {\n ...options.manifest,\n capabilities: {\n ...options.manifest.capabilities,\n metadataProvider: [\"series\"] as MetadataContentType[],\n },\n },\n };\n createMetadataPlugin(newOptions);\n}\n\n/**\n * @deprecated Use MetadataPluginOptions instead\n */\nexport interface SeriesMetadataPluginOptions {\n /** Plugin manifest - must have capabilities.seriesMetadataProvider: true */\n manifest: PluginManifest & {\n capabilities: { seriesMetadataProvider: true };\n };\n /** SeriesMetadataProvider implementation */\n provider: MetadataProvider;\n /** Called when plugin receives initialize with credentials/config */\n onInitialize?: (params: InitializeParams) => void | Promise<void>;\n /** Log level (default: \"info\") */\n logLevel?: \"debug\" | \"info\" | \"warn\" | \"error\";\n}\n\n// =============================================================================\n// Internal Implementation\n// =============================================================================\n\nasync function handleLine(\n line: string,\n manifest: PluginManifest,\n provider: MetadataProvider,\n onInitialize: ((params: InitializeParams) => void | Promise<void>) | undefined,\n logger: Logger,\n): Promise<void> {\n const trimmed = line.trim();\n if (!trimmed) return;\n\n let id: string | number | null = null;\n\n try {\n const request = JSON.parse(trimmed) as JsonRpcRequest;\n id = request.id;\n\n logger.debug(`Received request: ${request.method}`, { id: request.id });\n\n const response = await handleRequest(request, manifest, provider, onInitialize, logger);\n // Shutdown handler writes response directly and returns null\n if (response !== null) {\n writeResponse(response);\n }\n } catch (error) {\n if (error instanceof SyntaxError) {\n // JSON parse error\n writeResponse({\n jsonrpc: \"2.0\",\n id: null,\n error: {\n code: JSON_RPC_ERROR_CODES.PARSE_ERROR,\n message: \"Parse error: invalid JSON\",\n },\n });\n } else if (error instanceof PluginError) {\n writeResponse({\n jsonrpc: \"2.0\",\n id,\n error: error.toJsonRpcError(),\n });\n } else {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n logger.error(\"Request failed\", error);\n writeResponse({\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,\n message,\n },\n });\n }\n }\n}\n\nasync function handleRequest(\n request: JsonRpcRequest,\n manifest: PluginManifest,\n provider: MetadataProvider,\n onInitialize: ((params: InitializeParams) => void | Promise<void>) | undefined,\n logger: Logger,\n): Promise<JsonRpcResponse> {\n const { method, params, id } = request;\n\n switch (method) {\n case \"initialize\":\n // Call onInitialize callback if provided (to receive credentials/config)\n if (onInitialize) {\n await onInitialize(params as InitializeParams);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: manifest,\n };\n\n case \"ping\":\n return {\n jsonrpc: \"2.0\",\n id,\n result: \"pong\",\n };\n\n case \"shutdown\": {\n logger.info(\"Shutdown requested\");\n // Write response directly with callback to ensure it's flushed before exit\n const response: JsonRpcResponse = {\n jsonrpc: \"2.0\",\n id,\n result: null,\n };\n process.stdout.write(`${JSON.stringify(response)}\\n`, () => {\n // Callback is called after the write is flushed to the OS\n process.exit(0);\n });\n // Return a sentinel that handleLine will recognize and skip normal writeResponse\n return null as unknown as JsonRpcResponse;\n }\n\n // Series metadata methods (scoped by content type)\n case \"metadata/series/search\": {\n const validationError = validateSearchParams(params);\n if (validationError) {\n return invalidParamsError(id, validationError);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: await provider.search(params as MetadataSearchParams),\n };\n }\n\n case \"metadata/series/get\": {\n const validationError = validateGetParams(params);\n if (validationError) {\n return invalidParamsError(id, validationError);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: await provider.get(params as MetadataGetParams),\n };\n }\n\n case \"metadata/series/match\": {\n if (!provider.match) {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: \"This plugin does not support match\",\n },\n };\n }\n const validationError = validateMatchParams(params);\n if (validationError) {\n return invalidParamsError(id, validationError);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: await provider.match(params as MetadataMatchParams),\n };\n }\n\n // Future: book metadata methods\n // case \"metadata/book/search\":\n // case \"metadata/book/get\":\n // case \"metadata/book/match\":\n\n default:\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: `Method not found: ${method}`,\n },\n };\n }\n}\n\nfunction writeResponse(response: JsonRpcResponse): void {\n // Write to stdout - this is the JSON-RPC channel\n process.stdout.write(`${JSON.stringify(response)}\\n`);\n}\n", "/**\n * MangaBaka API client\n * API docs: https://mangabaka.org/api\n */\n\nimport {\n ApiError,\n AuthError,\n createLogger,\n NotFoundError,\n RateLimitError,\n} from \"@ashdev/codex-plugin-sdk\";\nimport type { MbGetSeriesResponse, MbSearchResponse, MbSeries } from \"./types.js\";\n\nconst BASE_URL = \"https://api.mangabaka.dev\";\nconst logger = createLogger({ name: \"mangabaka-api\", level: \"debug\" });\n\nexport class MangaBakaClient {\n private readonly apiKey: string;\n\n constructor(apiKey: string) {\n if (!apiKey) {\n throw new AuthError(\"API key is required\");\n }\n this.apiKey = apiKey;\n }\n\n /**\n * Search for series by query\n */\n async search(\n query: string,\n page = 1,\n perPage = 20,\n ): Promise<{ data: MbSeries[]; total: number; page: number; totalPages: number }> {\n logger.debug(`Searching for: \"${query}\" (page ${page})`);\n\n const params = new URLSearchParams({\n q: query,\n page: String(page),\n limit: String(perPage),\n });\n\n const response = await this.request<MbSearchResponse>(`/v1/series/search?${params.toString()}`);\n\n return {\n data: response.data,\n total: response.pagination?.total ?? response.data.length,\n page: response.pagination?.page ?? page,\n totalPages: response.pagination?.total_pages ?? 1,\n };\n }\n\n /**\n * Get full series details by ID\n */\n async getSeries(id: number): Promise<MbSeries> {\n logger.debug(`Getting series: ${id}`);\n\n const response = await this.request<MbGetSeriesResponse>(`/v1/series/${id}`);\n\n return response.data;\n }\n\n /**\n * Make an authenticated request to the MangaBaka API\n */\n private async request<T>(path: string): Promise<T> {\n const url = `${BASE_URL}${path}`;\n const headers: Record<string, string> = {\n \"x-api-key\": this.apiKey,\n Accept: \"application/json\",\n };\n\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers,\n });\n\n // Handle rate limiting\n if (response.status === 429) {\n const retryAfter = response.headers.get(\"Retry-After\");\n const seconds = retryAfter ? Number.parseInt(retryAfter, 10) : 60;\n throw new RateLimitError(seconds);\n }\n\n // Handle auth errors\n if (response.status === 401 || response.status === 403) {\n throw new AuthError(\"Invalid API key\");\n }\n\n // Handle not found\n if (response.status === 404) {\n throw new NotFoundError(`Resource not found: ${path}`);\n }\n\n // Handle other errors\n if (!response.ok) {\n const text = await response.text();\n logger.error(`API error: ${response.status}`, { body: text });\n throw new ApiError(`API error: ${response.status} ${response.statusText}`, response.status);\n }\n\n return response.json() as Promise<T>;\n } catch (error) {\n // Re-throw plugin errors\n if (\n error instanceof RateLimitError ||\n error instanceof AuthError ||\n error instanceof NotFoundError ||\n error instanceof ApiError\n ) {\n throw error;\n }\n\n // Wrap other errors\n const message = error instanceof Error ? error.message : \"Unknown error\";\n logger.error(\"Request failed\", error);\n throw new ApiError(`Request failed: ${message}`);\n }\n }\n}\n", "/**\n * Mappers to convert MangaBaka API responses to Codex plugin protocol types\n */\n\nimport type {\n AlternateTitle,\n ExternalLink,\n ExternalRating,\n PluginSeriesMetadata,\n ReadingDirection,\n SearchResult,\n SeriesStatus,\n} from \"@ashdev/codex-plugin-sdk\";\nimport type { MbContentRating, MbSeries, MbSeriesType, MbStatus } from \"./types.js\";\n\n/**\n * Map MangaBaka status to protocol SeriesStatus\n * MangaBaka uses: cancelled, completed, hiatus, releasing, unknown, upcoming\n * Codex uses: ongoing, ended, hiatus, abandoned, unknown\n */\nfunction mapStatus(mbStatus: MbStatus): SeriesStatus {\n switch (mbStatus) {\n case \"completed\":\n return \"ended\";\n case \"releasing\":\n case \"upcoming\":\n return \"ongoing\";\n case \"hiatus\":\n return \"hiatus\";\n case \"cancelled\":\n return \"abandoned\";\n default:\n return \"unknown\";\n }\n}\n\n/**\n * Format genre from snake_case to Title Case\n */\nfunction formatGenre(genre: string): string {\n return genre\n .split(\"_\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\n/**\n * Detect language code from country of origin\n */\nfunction detectLanguageFromCountry(country: string | null | undefined): string | undefined {\n if (!country) return undefined;\n\n const countryLower = country.toLowerCase();\n if (countryLower === \"jp\" || countryLower === \"japan\") return \"ja\";\n if (countryLower === \"kr\" || countryLower === \"korea\" || countryLower === \"south korea\")\n return \"ko\";\n if (countryLower === \"cn\" || countryLower === \"china\") return \"zh\";\n if (countryLower === \"tw\" || countryLower === \"taiwan\") return \"zh-TW\";\n\n return undefined;\n}\n\n/**\n * Map MangaBaka content rating to numeric age rating\n */\nfunction mapContentRating(rating: MbContentRating | null | undefined): number | undefined {\n if (!rating) return undefined;\n\n switch (rating) {\n case \"safe\":\n return 0; // All ages\n case \"suggestive\":\n return 13; // Teen\n case \"erotica\":\n return 16; // Mature\n case \"pornographic\":\n return 18; // Adults only\n default:\n return undefined;\n }\n}\n\n/**\n * Extract rating value from either a number or an object with bayesian/average\n */\nfunction extractRating(\n rating: number | { bayesian?: number | null; average?: number | null } | null | undefined,\n): number | undefined {\n if (rating == null) return undefined;\n if (typeof rating === \"number\") return rating;\n return rating.bayesian ?? rating.average ?? undefined;\n}\n\n/**\n * Infer reading direction from series type and country\n */\nfunction inferReadingDirection(\n seriesType: MbSeriesType,\n country: string | null | undefined,\n): ReadingDirection | undefined {\n // Manhwa (Korean) and Manhua (Chinese) are typically left-to-right\n if (seriesType === \"manhwa\" || seriesType === \"manhua\") {\n return \"ltr\";\n }\n\n // Manga (Japanese) is typically right-to-left\n if (seriesType === \"manga\") {\n return \"rtl\";\n }\n\n // OEL (Original English Language) is left-to-right\n if (seriesType === \"oel\") {\n return \"ltr\";\n }\n\n // Fall back to country-based detection\n if (country) {\n const countryLower = country.toLowerCase();\n if (countryLower === \"jp\" || countryLower === \"japan\") return \"rtl\";\n if (countryLower === \"kr\" || countryLower === \"korea\" || countryLower === \"south korea\")\n return \"ltr\";\n if (countryLower === \"cn\" || countryLower === \"china\") return \"ltr\";\n if (countryLower === \"tw\" || countryLower === \"taiwan\") return \"ltr\";\n }\n\n return undefined;\n}\n\n/**\n * Map a MangaBaka series to a protocol SearchResult\n */\nexport function mapSearchResult(series: MbSeries): SearchResult {\n // Get cover URL - prefer x250 for search results\n const coverUrl = series.cover?.x250?.x1 ?? series.cover?.raw?.url ?? undefined;\n\n // Build alternate titles array\n const alternateTitles: string[] = [];\n if (series.native_title && series.native_title !== series.title) {\n alternateTitles.push(series.native_title);\n }\n if (series.romanized_title && series.romanized_title !== series.title) {\n alternateTitles.push(series.romanized_title);\n }\n\n // Note: relevanceScore is omitted - the API already returns results in relevance order\n return {\n externalId: String(series.id),\n title: series.title,\n alternateTitles,\n year: series.year ?? undefined,\n coverUrl: coverUrl ?? undefined,\n preview: {\n status: mapStatus(series.status),\n genres: (series.genres ?? []).slice(0, 3).map(formatGenre),\n rating: extractRating(series.rating),\n description: series.description?.slice(0, 200) ?? undefined,\n },\n };\n}\n\n/**\n * Map full series response to protocol PluginSeriesMetadata\n */\nexport function mapSeriesMetadata(series: MbSeries): PluginSeriesMetadata {\n // Build alternate titles array with language info\n const alternateTitles: AlternateTitle[] = [];\n\n // Add native title\n if (series.native_title && series.native_title !== series.title) {\n alternateTitles.push({\n title: series.native_title,\n language: detectLanguageFromCountry(series.country_of_origin),\n titleType: \"native\",\n });\n }\n\n // Add romanized title\n if (series.romanized_title && series.romanized_title !== series.title) {\n alternateTitles.push({\n title: series.romanized_title,\n language: \"en\",\n titleType: \"romaji\",\n });\n }\n\n // Add secondary titles from all languages\n if (series.secondary_titles) {\n for (const [langCode, titleList] of Object.entries(series.secondary_titles)) {\n if (titleList) {\n for (const titleEntry of titleList) {\n if (titleEntry.title !== series.title) {\n alternateTitles.push({\n title: titleEntry.title,\n language: langCode,\n });\n }\n }\n }\n }\n }\n\n // Extract authors and artists as string arrays\n const authors = series.authors ?? [];\n const artists = series.artists ?? [];\n\n // Format genres\n const genres = (series.genres ?? []).map(formatGenre);\n\n // Get cover URL - prefer raw for full metadata\n const coverUrl = series.cover?.raw?.url ?? series.cover?.x350?.x1 ?? undefined;\n\n // Build external links from sources\n // Always include MangaBaka link first\n const externalLinks: ExternalLink[] = [\n {\n url: `https://mangabaka.org/${series.id}`,\n label: \"MangaBaka\",\n linkType: \"provider\",\n },\n ];\n\n // Source configuration: display name, rating key, and URL pattern\n // URL pattern uses {id} as placeholder for the source ID\n const sourceConfig: Record<string, { label: string; ratingKey: string; urlPattern?: string }> = {\n anilist: {\n label: \"AniList\",\n ratingKey: \"anilist\",\n urlPattern: \"https://anilist.co/manga/{id}\",\n },\n my_anime_list: {\n label: \"MyAnimeList\",\n ratingKey: \"myanimelist\",\n urlPattern: \"https://myanimelist.net/manga/{id}\",\n },\n mangadex: {\n label: \"MangaDex\",\n ratingKey: \"mangadex\",\n urlPattern: \"https://mangadex.org/title/{id}\",\n },\n manga_updates: {\n label: \"MangaUpdates\",\n ratingKey: \"mangaupdates\",\n urlPattern: \"https://www.mangaupdates.com/series/{id}\",\n },\n kitsu: { label: \"Kitsu\", ratingKey: \"kitsu\", urlPattern: \"https://kitsu.app/manga/{id}\" },\n anime_planet: {\n label: \"Anime-Planet\",\n ratingKey: \"animeplanet\",\n urlPattern: \"https://www.anime-planet.com/manga/{id}\",\n },\n anime_news_network: { label: \"Anime News Network\", ratingKey: \"animenewsnetwork\" },\n shikimori: {\n label: \"Shikimori\",\n ratingKey: \"shikimori\",\n urlPattern: \"https://shikimori.one/mangas/{id}\",\n },\n };\n\n // Build external links and ratings from sources in a single pass\n const externalRatings: ExternalRating[] = [];\n\n if (series.source) {\n for (const [key, info] of Object.entries(series.source)) {\n if (!info) continue;\n\n const config = sourceConfig[key];\n // Use config if available, otherwise generate defaults from key\n const ratingKey = config?.ratingKey ?? key.replace(/_/g, \"\");\n\n // Add external link if source has an ID and URL pattern\n if (info.id != null && config?.urlPattern) {\n externalLinks.push({\n url: config.urlPattern.replace(\"{id}\", String(info.id)),\n label: config.label,\n linkType: \"provider\",\n });\n }\n\n // Add external rating if source has a normalized rating\n if (info.rating_normalized != null) {\n externalRatings.push({ score: info.rating_normalized, source: ratingKey });\n }\n }\n }\n\n // Get publisher name (pick first one if available)\n const publisher = series.publishers?.[0]?.name ?? undefined;\n\n return {\n externalId: String(series.id),\n externalUrl: `https://mangabaka.org/${series.id}`,\n title: series.title,\n alternateTitles,\n summary: series.description ?? undefined,\n status: mapStatus(series.status),\n year: series.year ?? undefined,\n // Extended metadata\n publisher,\n totalBookCount: series.final_volume ? Number.parseInt(series.final_volume, 10) : undefined,\n ageRating: mapContentRating(series.content_rating),\n readingDirection: inferReadingDirection(series.type, series.country_of_origin),\n // Taxonomy\n genres,\n tags: series.tags ?? [],\n authors,\n artists,\n coverUrl: coverUrl ?? undefined,\n rating: (() => {\n const r = extractRating(series.rating);\n return r != null ? { score: r, source: \"mangabaka\" } : undefined;\n })(),\n externalRatings: externalRatings.length > 0 ? externalRatings : undefined,\n externalLinks,\n };\n}\n", "import {\n type MetadataGetParams,\n NotFoundError,\n type PluginSeriesMetadata,\n} from \"@ashdev/codex-plugin-sdk\";\nimport type { MangaBakaClient } from \"../api.js\";\nimport { mapSeriesMetadata } from \"../mappers.js\";\n\nexport async function handleGet(\n params: MetadataGetParams,\n client: MangaBakaClient,\n): Promise<PluginSeriesMetadata> {\n const seriesId = Number.parseInt(params.externalId, 10);\n\n if (Number.isNaN(seriesId)) {\n throw new NotFoundError(`Invalid external ID: ${params.externalId}`);\n }\n\n const response = await client.getSeries(seriesId);\n\n return mapSeriesMetadata(response);\n}\n", "import {\n createLogger,\n type MetadataMatchParams,\n type MetadataMatchResponse,\n type SearchResult,\n} from \"@ashdev/codex-plugin-sdk\";\nimport type { MangaBakaClient } from \"../api.js\";\nimport { mapSearchResult } from \"../mappers.js\";\n\nconst logger = createLogger({ name: \"mangabaka-match\", level: \"info\" });\n\n/**\n * Calculate string similarity using word overlap\n * Returns a value between 0 and 1\n */\nfunction similarity(a: string, b: string): number {\n const aLower = a.toLowerCase().trim();\n const bLower = b.toLowerCase().trim();\n\n if (aLower === bLower) return 1.0;\n if (aLower.length === 0 || bLower.length === 0) return 0;\n\n // Check if one contains the other\n if (aLower.includes(bLower) || bLower.includes(aLower)) {\n return 0.8;\n }\n\n // Simple word overlap scoring\n const aWords = new Set(aLower.split(/\\s+/));\n const bWords = new Set(bLower.split(/\\s+/));\n const intersection = [...aWords].filter((w) => bWords.has(w));\n const union = new Set([...aWords, ...bWords]);\n\n return intersection.length / union.size;\n}\n\n/**\n * Score a search result against the match parameters\n * Returns a value between 0 and 1\n */\nfunction scoreResult(result: SearchResult, params: MetadataMatchParams): number {\n let score = 0;\n\n // Title similarity (up to 0.6)\n const titleScore = similarity(result.title, params.title);\n score += titleScore * 0.6;\n\n // Year match (up to 0.2)\n if (params.year && result.year) {\n if (result.year === params.year) {\n score += 0.2;\n } else if (Math.abs(result.year - params.year) <= 1) {\n score += 0.1;\n }\n }\n\n // Boost for exact title match (up to 0.2)\n if (result.title.toLowerCase() === params.title.toLowerCase()) {\n score += 0.2;\n }\n\n return Math.min(1.0, score);\n}\n\nexport async function handleMatch(\n params: MetadataMatchParams,\n client: MangaBakaClient,\n): Promise<MetadataMatchResponse> {\n logger.debug(`Matching: \"${params.title}\"`);\n\n // Search for the title\n const response = await client.search(params.title, 1, 10);\n\n if (response.data.length === 0) {\n return {\n match: null,\n confidence: 0,\n };\n }\n\n // Map and score results\n const scoredResults = response.data\n .map((series) => {\n const result = mapSearchResult(series);\n const score = scoreResult(result, params);\n return { result, score };\n })\n .sort((a, b) => b.score - a.score);\n\n const best = scoredResults[0];\n\n if (!best) {\n return {\n match: null,\n confidence: 0,\n };\n }\n\n // If confidence is low, include alternatives\n const alternatives =\n best.score < 0.8\n ? scoredResults.slice(1, 4).map((s) => ({\n ...s.result,\n relevanceScore: s.score,\n }))\n : undefined;\n\n return {\n match: {\n ...best.result,\n relevanceScore: best.score,\n },\n confidence: best.score,\n alternatives,\n };\n}\n", "import {\n createLogger,\n type MetadataSearchParams,\n type MetadataSearchResponse,\n} from \"@ashdev/codex-plugin-sdk\";\nimport type { MangaBakaClient } from \"../api.js\";\nimport { mapSearchResult } from \"../mappers.js\";\n\nconst logger = createLogger({ name: \"mangabaka-search\", level: \"debug\" });\n\nexport async function handleSearch(\n params: MetadataSearchParams,\n client: MangaBakaClient,\n): Promise<MetadataSearchResponse> {\n logger.debug(\"Search params received:\", params);\n\n const limit = params.limit ?? 20;\n\n // Parse cursor as page number (default to 1)\n const page = params.cursor ? Number.parseInt(params.cursor, 10) : 1;\n\n logger.debug(`Searching for: \"${params.query}\" (page ${page}, limit ${limit})`);\n\n const response = await client.search(params.query, page, limit);\n\n // Map results - API already returns them sorted by relevance\n const results = response.data.map(mapSearchResult);\n\n // Calculate next cursor (next page number) if there are more results\n const hasNextPage = response.page < response.totalPages;\n const nextCursor = hasNextPage ? String(response.page + 1) : undefined;\n\n return {\n results,\n nextCursor,\n };\n}\n", "import type { MetadataContentType, PluginManifest } from \"@ashdev/codex-plugin-sdk\";\n\nexport const manifest = {\n name: \"metadata-mangabaka\",\n displayName: \"MangaBaka Metadata\",\n version: \"1.0.0\",\n description: \"Fetch manga metadata from MangaBaka - aggregated data from multiple sources\",\n author: \"Codex\",\n homepage: \"https://mangabaka.org\",\n protocolVersion: \"1.0\",\n capabilities: {\n metadataProvider: [\"series\"] as MetadataContentType[],\n },\n requiredCredentials: [\n {\n key: \"api_key\",\n label: \"API Key\",\n description: \"Get your API key at https://mangabaka.org/settings/api (requires account)\",\n required: true,\n sensitive: true,\n type: \"password\",\n placeholder: \"mb-...\",\n },\n ],\n} as const satisfies PluginManifest & {\n capabilities: { metadataProvider: MetadataContentType[] };\n};\n", "/**\n * MangaBaka Plugin - Fetch manga metadata from MangaBaka\n *\n * MangaBaka aggregates metadata from multiple sources (AniList, MAL, MangaDex, etc.)\n * and provides a unified API for manga/novel metadata.\n *\n * API docs: https://mangabaka.org/api\n *\n * Credentials are provided by Codex via the initialize message.\n * Required credential: api_key (get one at https://mangabaka.org/settings/api)\n */\n\nimport {\n ConfigError,\n createLogger,\n createMetadataPlugin,\n type InitializeParams,\n type MetadataProvider,\n} from \"@ashdev/codex-plugin-sdk\";\nimport { MangaBakaClient } from \"./api.js\";\nimport { handleGet } from \"./handlers/get.js\";\nimport { handleMatch } from \"./handlers/match.js\";\nimport { handleSearch } from \"./handlers/search.js\";\nimport { manifest } from \"./manifest.js\";\n\nconst logger = createLogger({ name: \"mangabaka\", level: \"info\" });\n\n// Client is initialized when we receive credentials from Codex\nlet client: MangaBakaClient | null = null;\n\nfunction getClient(): MangaBakaClient {\n if (!client) {\n throw new ConfigError(\"Plugin not initialized - missing API key\");\n }\n return client;\n}\n\n// Create the MetadataProvider implementation\nconst provider: MetadataProvider = {\n async search(params) {\n return handleSearch(params, getClient());\n },\n\n async get(params) {\n return handleGet(params, getClient());\n },\n\n async match(params) {\n return handleMatch(params, getClient());\n },\n};\n\n// Start the plugin server\ncreateMetadataPlugin({\n manifest,\n provider,\n logLevel: \"info\",\n onInitialize(params: InitializeParams) {\n const apiKey = params.credentials?.api_key;\n if (!apiKey) {\n throw new ConfigError(\"api_key credential is required\");\n }\n client = new MangaBakaClient(apiKey);\n logger.info(\"MangaBaka client initialized\");\n },\n});\n\nlogger.info(\"MangaBaka plugin started\");\n"],
|
|
3
|
+
"sources": ["../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/api.ts", "../src/mappers.ts", "../src/handlers/get.ts", "../src/handlers/match.ts", "../src/handlers/search.ts", "../src/manifest.ts", "../src/index.ts"],
|
|
4
|
+
"sourcesContent": [null, null, null, null, "/**\n * MangaBaka API client\n * API docs: https://mangabaka.org/api\n */\n\nimport {\n ApiError,\n AuthError,\n createLogger,\n NotFoundError,\n RateLimitError,\n} from \"@ashdev/codex-plugin-sdk\";\nimport type { MbGetSeriesResponse, MbSearchResponse, MbSeries } from \"./types.js\";\n\nconst BASE_URL = \"https://api.mangabaka.dev\";\nconst logger = createLogger({ name: \"mangabaka-api\", level: \"debug\" });\n\nexport class MangaBakaClient {\n private readonly apiKey: string;\n\n constructor(apiKey: string) {\n if (!apiKey) {\n throw new AuthError(\"API key is required\");\n }\n this.apiKey = apiKey;\n }\n\n /**\n * Search for series by query\n */\n async search(\n query: string,\n page = 1,\n perPage = 20,\n ): Promise<{ data: MbSeries[]; total: number; page: number; totalPages: number }> {\n logger.debug(`Searching for: \"${query}\" (page ${page})`);\n\n const params = new URLSearchParams({\n q: query,\n page: String(page),\n limit: String(perPage),\n });\n\n const response = await this.request<MbSearchResponse>(`/v1/series/search?${params.toString()}`);\n\n return {\n data: response.data,\n total: response.pagination?.total ?? response.data.length,\n page: response.pagination?.page ?? page,\n totalPages: response.pagination?.total_pages ?? 1,\n };\n }\n\n /**\n * Get full series details by ID\n */\n async getSeries(id: number): Promise<MbSeries> {\n logger.debug(`Getting series: ${id}`);\n\n const response = await this.request<MbGetSeriesResponse>(`/v1/series/${id}`);\n\n return response.data;\n }\n\n /**\n * Make an authenticated request to the MangaBaka API\n */\n private async request<T>(path: string): Promise<T> {\n const url = `${BASE_URL}${path}`;\n const headers: Record<string, string> = {\n \"x-api-key\": this.apiKey,\n Accept: \"application/json\",\n };\n\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers,\n });\n\n // Handle rate limiting\n if (response.status === 429) {\n const retryAfter = response.headers.get(\"Retry-After\");\n const seconds = retryAfter ? Number.parseInt(retryAfter, 10) : 60;\n throw new RateLimitError(seconds);\n }\n\n // Handle auth errors\n if (response.status === 401 || response.status === 403) {\n throw new AuthError(\"Invalid API key\");\n }\n\n // Handle not found\n if (response.status === 404) {\n throw new NotFoundError(`Resource not found: ${path}`);\n }\n\n // Handle other errors\n if (!response.ok) {\n const text = await response.text();\n logger.error(`API error: ${response.status}`, { body: text });\n throw new ApiError(`API error: ${response.status} ${response.statusText}`, response.status);\n }\n\n return response.json() as Promise<T>;\n } catch (error) {\n // Re-throw plugin errors\n if (\n error instanceof RateLimitError ||\n error instanceof AuthError ||\n error instanceof NotFoundError ||\n error instanceof ApiError\n ) {\n throw error;\n }\n\n // Wrap other errors\n const message = error instanceof Error ? error.message : \"Unknown error\";\n logger.error(\"Request failed\", error);\n throw new ApiError(`Request failed: ${message}`);\n }\n }\n}\n", "/**\n * Mappers to convert MangaBaka API responses to Codex plugin protocol types\n */\n\nimport type {\n AlternateTitle,\n ExternalLink,\n ExternalRating,\n PluginSeriesMetadata,\n ReadingDirection,\n SearchResult,\n SeriesStatus,\n} from \"@ashdev/codex-plugin-sdk\";\nimport type { MbContentRating, MbSeries, MbSeriesType, MbStatus } from \"./types.js\";\n\n/**\n * Map MangaBaka status to protocol SeriesStatus\n * MangaBaka uses: cancelled, completed, hiatus, releasing, unknown, upcoming\n * Codex uses: ongoing, ended, hiatus, abandoned, unknown\n */\nfunction mapStatus(mbStatus: MbStatus): SeriesStatus {\n switch (mbStatus) {\n case \"completed\":\n return \"ended\";\n case \"releasing\":\n case \"upcoming\":\n return \"ongoing\";\n case \"hiatus\":\n return \"hiatus\";\n case \"cancelled\":\n return \"abandoned\";\n default:\n return \"unknown\";\n }\n}\n\n/**\n * Format genre from snake_case to Title Case\n */\nfunction formatGenre(genre: string): string {\n return genre\n .split(\"_\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\n/**\n * Detect language code from country of origin\n */\nfunction detectLanguageFromCountry(country: string | null | undefined): string | undefined {\n if (!country) return undefined;\n\n const countryLower = country.toLowerCase();\n if (countryLower === \"jp\" || countryLower === \"japan\") return \"ja\";\n if (countryLower === \"kr\" || countryLower === \"korea\" || countryLower === \"south korea\")\n return \"ko\";\n if (countryLower === \"cn\" || countryLower === \"china\") return \"zh\";\n if (countryLower === \"tw\" || countryLower === \"taiwan\") return \"zh-TW\";\n\n return undefined;\n}\n\n/**\n * Map MangaBaka content rating to numeric age rating\n */\nfunction mapContentRating(rating: MbContentRating | null | undefined): number | undefined {\n if (!rating) return undefined;\n\n switch (rating) {\n case \"safe\":\n return 0; // All ages\n case \"suggestive\":\n return 13; // Teen\n case \"erotica\":\n return 16; // Mature\n case \"pornographic\":\n return 18; // Adults only\n default:\n return undefined;\n }\n}\n\n/**\n * Extract rating value from either a number or an object with bayesian/average\n */\nfunction extractRating(\n rating: number | { bayesian?: number | null; average?: number | null } | null | undefined,\n): number | undefined {\n if (rating == null) return undefined;\n if (typeof rating === \"number\") return rating;\n return rating.bayesian ?? rating.average ?? undefined;\n}\n\n/**\n * Infer reading direction from series type and country\n */\nfunction inferReadingDirection(\n seriesType: MbSeriesType,\n country: string | null | undefined,\n): ReadingDirection | undefined {\n // Manhwa (Korean) and Manhua (Chinese) are typically left-to-right\n if (seriesType === \"manhwa\" || seriesType === \"manhua\") {\n return \"ltr\";\n }\n\n // Manga (Japanese) is typically right-to-left\n if (seriesType === \"manga\") {\n return \"rtl\";\n }\n\n // OEL (Original English Language) is left-to-right\n if (seriesType === \"oel\") {\n return \"ltr\";\n }\n\n // Fall back to country-based detection\n if (country) {\n const countryLower = country.toLowerCase();\n if (countryLower === \"jp\" || countryLower === \"japan\") return \"rtl\";\n if (countryLower === \"kr\" || countryLower === \"korea\" || countryLower === \"south korea\")\n return \"ltr\";\n if (countryLower === \"cn\" || countryLower === \"china\") return \"ltr\";\n if (countryLower === \"tw\" || countryLower === \"taiwan\") return \"ltr\";\n }\n\n return undefined;\n}\n\n/**\n * Map a MangaBaka series to a protocol SearchResult\n */\nexport function mapSearchResult(series: MbSeries): SearchResult {\n // Get cover URL - prefer x250 for search results\n const coverUrl = series.cover?.x250?.x1 ?? series.cover?.raw?.url ?? undefined;\n\n // Build alternate titles array\n const alternateTitles: string[] = [];\n if (series.native_title && series.native_title !== series.title) {\n alternateTitles.push(series.native_title);\n }\n if (series.romanized_title && series.romanized_title !== series.title) {\n alternateTitles.push(series.romanized_title);\n }\n\n // Note: relevanceScore is omitted - the API already returns results in relevance order\n return {\n externalId: String(series.id),\n title: series.title,\n alternateTitles,\n year: series.year ?? undefined,\n coverUrl: coverUrl ?? undefined,\n preview: {\n status: mapStatus(series.status),\n genres: (series.genres ?? []).slice(0, 3).map(formatGenre),\n rating: extractRating(series.rating),\n description: series.description?.slice(0, 200) ?? undefined,\n },\n };\n}\n\n/**\n * Map full series response to protocol PluginSeriesMetadata\n */\nexport function mapSeriesMetadata(series: MbSeries): PluginSeriesMetadata {\n // Build alternate titles array with language info\n const alternateTitles: AlternateTitle[] = [];\n\n // Add native title\n if (series.native_title && series.native_title !== series.title) {\n alternateTitles.push({\n title: series.native_title,\n language: detectLanguageFromCountry(series.country_of_origin),\n titleType: \"native\",\n });\n }\n\n // Add romanized title\n if (series.romanized_title && series.romanized_title !== series.title) {\n alternateTitles.push({\n title: series.romanized_title,\n language: \"en\",\n titleType: \"romaji\",\n });\n }\n\n // Add secondary titles from all languages\n if (series.secondary_titles) {\n for (const [langCode, titleList] of Object.entries(series.secondary_titles)) {\n if (titleList) {\n for (const titleEntry of titleList) {\n if (titleEntry.title !== series.title) {\n alternateTitles.push({\n title: titleEntry.title,\n language: langCode,\n });\n }\n }\n }\n }\n }\n\n // Extract authors and artists as string arrays\n const authors = series.authors ?? [];\n const artists = series.artists ?? [];\n\n // Format genres\n const genres = (series.genres ?? []).map(formatGenre);\n\n // Get cover URL - prefer raw for full metadata\n const coverUrl = series.cover?.raw?.url ?? series.cover?.x350?.x1 ?? undefined;\n\n // Build external links from sources\n // Always include MangaBaka link first\n const externalLinks: ExternalLink[] = [\n {\n url: `https://mangabaka.org/${series.id}`,\n label: \"MangaBaka\",\n linkType: \"provider\",\n },\n ];\n\n // Source configuration: display name, rating key, and URL pattern\n // URL pattern uses {id} as placeholder for the source ID\n const sourceConfig: Record<string, { label: string; ratingKey: string; urlPattern?: string }> = {\n anilist: {\n label: \"AniList\",\n ratingKey: \"anilist\",\n urlPattern: \"https://anilist.co/manga/{id}\",\n },\n my_anime_list: {\n label: \"MyAnimeList\",\n ratingKey: \"myanimelist\",\n urlPattern: \"https://myanimelist.net/manga/{id}\",\n },\n mangadex: {\n label: \"MangaDex\",\n ratingKey: \"mangadex\",\n urlPattern: \"https://mangadex.org/title/{id}\",\n },\n manga_updates: {\n label: \"MangaUpdates\",\n ratingKey: \"mangaupdates\",\n urlPattern: \"https://www.mangaupdates.com/series/{id}\",\n },\n kitsu: { label: \"Kitsu\", ratingKey: \"kitsu\", urlPattern: \"https://kitsu.app/manga/{id}\" },\n anime_planet: {\n label: \"Anime-Planet\",\n ratingKey: \"animeplanet\",\n urlPattern: \"https://www.anime-planet.com/manga/{id}\",\n },\n anime_news_network: { label: \"Anime News Network\", ratingKey: \"animenewsnetwork\" },\n shikimori: {\n label: \"Shikimori\",\n ratingKey: \"shikimori\",\n urlPattern: \"https://shikimori.one/mangas/{id}\",\n },\n };\n\n // Build external links and ratings from sources in a single pass\n const externalRatings: ExternalRating[] = [];\n\n if (series.source) {\n for (const [key, info] of Object.entries(series.source)) {\n if (!info) continue;\n\n const config = sourceConfig[key];\n // Use config if available, otherwise generate defaults from key\n const ratingKey = config?.ratingKey ?? key.replace(/_/g, \"\");\n\n // Add external link if source has an ID and URL pattern\n if (info.id != null && config?.urlPattern) {\n externalLinks.push({\n url: config.urlPattern.replace(\"{id}\", String(info.id)),\n label: config.label,\n linkType: \"provider\",\n });\n }\n\n // Add external rating if source has a normalized rating\n if (info.rating_normalized != null) {\n externalRatings.push({ score: info.rating_normalized, source: ratingKey });\n }\n }\n }\n\n // Get publisher name (pick first one if available)\n const publisher = series.publishers?.[0]?.name ?? undefined;\n\n return {\n externalId: String(series.id),\n externalUrl: `https://mangabaka.org/${series.id}`,\n title: series.title,\n alternateTitles,\n summary: series.description ?? undefined,\n status: mapStatus(series.status),\n year: series.year ?? undefined,\n // Extended metadata\n publisher,\n totalBookCount: series.final_volume ? Number.parseInt(series.final_volume, 10) : undefined,\n ageRating: mapContentRating(series.content_rating),\n readingDirection: inferReadingDirection(series.type, series.country_of_origin),\n // Taxonomy\n genres,\n tags: series.tags ?? [],\n authors,\n artists,\n coverUrl: coverUrl ?? undefined,\n rating: (() => {\n const r = extractRating(series.rating);\n return r != null ? { score: r, source: \"mangabaka\" } : undefined;\n })(),\n externalRatings: externalRatings.length > 0 ? externalRatings : undefined,\n externalLinks,\n };\n}\n", "import {\n type MetadataGetParams,\n NotFoundError,\n type PluginSeriesMetadata,\n} from \"@ashdev/codex-plugin-sdk\";\nimport type { MangaBakaClient } from \"../api.js\";\nimport { mapSeriesMetadata } from \"../mappers.js\";\n\nexport async function handleGet(\n params: MetadataGetParams,\n client: MangaBakaClient,\n): Promise<PluginSeriesMetadata> {\n const seriesId = Number.parseInt(params.externalId, 10);\n\n if (Number.isNaN(seriesId)) {\n throw new NotFoundError(`Invalid external ID: ${params.externalId}`);\n }\n\n const response = await client.getSeries(seriesId);\n\n return mapSeriesMetadata(response);\n}\n", "import {\n createLogger,\n type MetadataMatchParams,\n type MetadataMatchResponse,\n type SearchResult,\n} from \"@ashdev/codex-plugin-sdk\";\nimport type { MangaBakaClient } from \"../api.js\";\nimport { mapSearchResult } from \"../mappers.js\";\n\nconst logger = createLogger({ name: \"mangabaka-match\", level: \"info\" });\n\n/**\n * Calculate string similarity using word overlap\n * Returns a value between 0 and 1\n */\nfunction similarity(a: string, b: string): number {\n const aLower = a.toLowerCase().trim();\n const bLower = b.toLowerCase().trim();\n\n if (aLower === bLower) return 1.0;\n if (aLower.length === 0 || bLower.length === 0) return 0;\n\n // Check if one contains the other\n if (aLower.includes(bLower) || bLower.includes(aLower)) {\n return 0.8;\n }\n\n // Simple word overlap scoring\n const aWords = new Set(aLower.split(/\\s+/));\n const bWords = new Set(bLower.split(/\\s+/));\n const intersection = [...aWords].filter((w) => bWords.has(w));\n const union = new Set([...aWords, ...bWords]);\n\n return intersection.length / union.size;\n}\n\n/**\n * Score a search result against the match parameters\n * Returns a value between 0 and 1\n */\nfunction scoreResult(result: SearchResult, params: MetadataMatchParams): number {\n let score = 0;\n\n // Title similarity (up to 0.6)\n const titleScore = similarity(result.title, params.title);\n score += titleScore * 0.6;\n\n // Year match (up to 0.2)\n if (params.year && result.year) {\n if (result.year === params.year) {\n score += 0.2;\n } else if (Math.abs(result.year - params.year) <= 1) {\n score += 0.1;\n }\n }\n\n // Boost for exact title match (up to 0.2)\n if (result.title.toLowerCase() === params.title.toLowerCase()) {\n score += 0.2;\n }\n\n return Math.min(1.0, score);\n}\n\nexport async function handleMatch(\n params: MetadataMatchParams,\n client: MangaBakaClient,\n): Promise<MetadataMatchResponse> {\n logger.debug(`Matching: \"${params.title}\"`);\n\n // Search for the title\n const response = await client.search(params.title, 1, 10);\n\n if (response.data.length === 0) {\n return {\n match: null,\n confidence: 0,\n };\n }\n\n // Map and score results\n const scoredResults = response.data\n .map((series) => {\n const result = mapSearchResult(series);\n const score = scoreResult(result, params);\n return { result, score };\n })\n .sort((a, b) => b.score - a.score);\n\n const best = scoredResults[0];\n\n if (!best) {\n return {\n match: null,\n confidence: 0,\n };\n }\n\n // If confidence is low, include alternatives\n const alternatives =\n best.score < 0.8\n ? scoredResults.slice(1, 4).map((s) => ({\n ...s.result,\n relevanceScore: s.score,\n }))\n : undefined;\n\n return {\n match: {\n ...best.result,\n relevanceScore: best.score,\n },\n confidence: best.score,\n alternatives,\n };\n}\n", "import {\n createLogger,\n type MetadataSearchParams,\n type MetadataSearchResponse,\n} from \"@ashdev/codex-plugin-sdk\";\nimport type { MangaBakaClient } from \"../api.js\";\nimport { mapSearchResult } from \"../mappers.js\";\n\nconst logger = createLogger({ name: \"mangabaka-search\", level: \"debug\" });\n\nexport async function handleSearch(\n params: MetadataSearchParams,\n client: MangaBakaClient,\n): Promise<MetadataSearchResponse> {\n logger.debug(\"Search params received:\", params);\n\n const limit = params.limit ?? 20;\n\n // Parse cursor as page number (default to 1)\n const page = params.cursor ? Number.parseInt(params.cursor, 10) : 1;\n\n logger.debug(`Searching for: \"${params.query}\" (page ${page}, limit ${limit})`);\n\n const response = await client.search(params.query, page, limit);\n\n // Map results - API already returns them sorted by relevance\n const results = response.data.map(mapSearchResult);\n\n // Calculate next cursor (next page number) if there are more results\n const hasNextPage = response.page < response.totalPages;\n const nextCursor = hasNextPage ? String(response.page + 1) : undefined;\n\n return {\n results,\n nextCursor,\n };\n}\n", "import type { MetadataContentType, PluginManifest } from \"@ashdev/codex-plugin-sdk\";\n\nexport const manifest = {\n name: \"metadata-mangabaka\",\n displayName: \"MangaBaka Metadata\",\n version: \"1.0.0\",\n description: \"Fetch manga metadata from MangaBaka - aggregated data from multiple sources\",\n author: \"Codex\",\n homepage: \"https://mangabaka.org\",\n protocolVersion: \"1.0\",\n capabilities: {\n metadataProvider: [\"series\"] as MetadataContentType[],\n },\n requiredCredentials: [\n {\n key: \"api_key\",\n label: \"API Key\",\n description: \"Get your API key at https://mangabaka.org/settings/api (requires account)\",\n required: true,\n sensitive: true,\n type: \"password\",\n placeholder: \"mb-...\",\n },\n ],\n} as const satisfies PluginManifest & {\n capabilities: { metadataProvider: MetadataContentType[] };\n};\n", "/**\n * MangaBaka Plugin - Fetch manga metadata from MangaBaka\n *\n * MangaBaka aggregates metadata from multiple sources (AniList, MAL, MangaDex, etc.)\n * and provides a unified API for manga/novel metadata.\n *\n * API docs: https://mangabaka.org/api\n *\n * Credentials are provided by Codex via the initialize message.\n * Required credential: api_key (get one at https://mangabaka.org/settings/api)\n */\n\nimport {\n ConfigError,\n createLogger,\n createMetadataPlugin,\n type InitializeParams,\n type MetadataProvider,\n} from \"@ashdev/codex-plugin-sdk\";\nimport { MangaBakaClient } from \"./api.js\";\nimport { handleGet } from \"./handlers/get.js\";\nimport { handleMatch } from \"./handlers/match.js\";\nimport { handleSearch } from \"./handlers/search.js\";\nimport { manifest } from \"./manifest.js\";\n\nconst logger = createLogger({ name: \"mangabaka\", level: \"info\" });\n\n// Client is initialized when we receive credentials from Codex\nlet client: MangaBakaClient | null = null;\n\nfunction getClient(): MangaBakaClient {\n if (!client) {\n throw new ConfigError(\"Plugin not initialized - missing API key\");\n }\n return client;\n}\n\n// Create the MetadataProvider implementation\nconst provider: MetadataProvider = {\n async search(params) {\n return handleSearch(params, getClient());\n },\n\n async get(params) {\n return handleGet(params, getClient());\n },\n\n async match(params) {\n return handleMatch(params, getClient());\n },\n};\n\n// Start the plugin server\ncreateMetadataPlugin({\n manifest,\n provider,\n logLevel: \"info\",\n onInitialize(params: InitializeParams) {\n const apiKey = params.credentials?.api_key;\n if (!apiKey) {\n throw new ConfigError(\"api_key credential is required\");\n }\n client = new MangaBakaClient(apiKey);\n logger.info(\"MangaBaka client initialized\");\n },\n});\n\nlogger.info(\"MangaBaka plugin started\");\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["manifest", "provider", "logger", "manifest", "provider", "logger", "client", "logger", "client", "logger", "client", "logger"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashdev/codex-plugin-metadata-mangabaka",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MangaBaka metadata provider plugin for Codex",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": "dist/index.js",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=22.0.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@ashdev/codex-plugin-sdk": "^1.
|
|
42
|
+
"@ashdev/codex-plugin-sdk": "^1.1.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@biomejs/biome": "^2.3.11",
|