@ashdev/codex-plugin-recommendations-anilist 1.10.6 → 1.10.8
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 +7 -46
- package/dist/index.js.map +3 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -686,7 +686,7 @@ function stripHtml(html) {
|
|
|
686
686
|
// package.json
|
|
687
687
|
var package_default = {
|
|
688
688
|
name: "@ashdev/codex-plugin-recommendations-anilist",
|
|
689
|
-
version: "1.10.
|
|
689
|
+
version: "1.10.8",
|
|
690
690
|
description: "AniList recommendation provider plugin for Codex - generates personalized manga recommendations based on your reading history",
|
|
691
691
|
main: "dist/index.js",
|
|
692
692
|
bin: "dist/index.js",
|
|
@@ -725,7 +725,7 @@ var package_default = {
|
|
|
725
725
|
node: ">=22.0.0"
|
|
726
726
|
},
|
|
727
727
|
dependencies: {
|
|
728
|
-
"@ashdev/codex-plugin-sdk": "^1.10.
|
|
728
|
+
"@ashdev/codex-plugin-sdk": "^1.10.8"
|
|
729
729
|
},
|
|
730
730
|
devDependencies: {
|
|
731
731
|
"@biomejs/biome": "^2.3.13",
|
|
@@ -762,24 +762,7 @@ var manifest = {
|
|
|
762
762
|
],
|
|
763
763
|
configSchema: {
|
|
764
764
|
description: "Recommendation configuration",
|
|
765
|
-
fields: [
|
|
766
|
-
{
|
|
767
|
-
key: "maxRecommendations",
|
|
768
|
-
label: "Maximum Recommendations",
|
|
769
|
-
description: "Maximum number of recommendations to generate (1-50)",
|
|
770
|
-
type: "number",
|
|
771
|
-
required: false,
|
|
772
|
-
default: 20
|
|
773
|
-
},
|
|
774
|
-
{
|
|
775
|
-
key: "maxSeeds",
|
|
776
|
-
label: "Maximum Seed Titles",
|
|
777
|
-
description: "Number of top-rated library titles used to generate recommendations (1-25)",
|
|
778
|
-
type: "number",
|
|
779
|
-
required: false,
|
|
780
|
-
default: 10
|
|
781
|
-
}
|
|
782
|
-
]
|
|
765
|
+
fields: []
|
|
783
766
|
},
|
|
784
767
|
userConfigSchema: {
|
|
785
768
|
description: "Per-user recommendation settings",
|
|
@@ -809,8 +792,6 @@ var manifest = {
|
|
|
809
792
|
var logger = createLogger({ name: "recommendations-anilist", level: "debug" });
|
|
810
793
|
var client = null;
|
|
811
794
|
var viewerId = null;
|
|
812
|
-
var maxRecommendations = 20;
|
|
813
|
-
var maxSeeds = 10;
|
|
814
795
|
var searchFallback = true;
|
|
815
796
|
var storage = null;
|
|
816
797
|
function setClient(c) {
|
|
@@ -879,14 +860,6 @@ async function resolveAniListIds(entries) {
|
|
|
879
860
|
}
|
|
880
861
|
return resolved;
|
|
881
862
|
}
|
|
882
|
-
function pickSeedEntries(entries, maxSeeds2) {
|
|
883
|
-
const sorted = [...entries].sort((a, b) => {
|
|
884
|
-
const ratingDiff = (b.userRating ?? 0) - (a.userRating ?? 0);
|
|
885
|
-
if (ratingDiff !== 0) return ratingDiff;
|
|
886
|
-
return b.booksRead - a.booksRead;
|
|
887
|
-
});
|
|
888
|
-
return sorted.slice(0, maxSeeds2);
|
|
889
|
-
}
|
|
890
863
|
function mapAniListStatus(status) {
|
|
891
864
|
if (!status) return void 0;
|
|
892
865
|
switch (status) {
|
|
@@ -946,7 +919,7 @@ var provider = {
|
|
|
946
919
|
logger.info(`Authenticated as viewer ${viewerId}`);
|
|
947
920
|
}
|
|
948
921
|
const { library, limit, excludeIds: rawExcludeIds = [] } = params;
|
|
949
|
-
const effectiveLimit = Math.min(limit ??
|
|
922
|
+
const effectiveLimit = Math.min(limit ?? 20, 50);
|
|
950
923
|
const excludeIds = new Set(rawExcludeIds);
|
|
951
924
|
if (!library || library.length === 0) {
|
|
952
925
|
logger.info("Empty library \u2014 returning no recommendations");
|
|
@@ -954,10 +927,9 @@ var provider = {
|
|
|
954
927
|
}
|
|
955
928
|
const userMangaIds = await client.getUserMangaIds(viewerId);
|
|
956
929
|
logger.debug(`User has ${userMangaIds.size} manga in AniList list`);
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
logger.debug(`Resolved ${resolved.size} AniList IDs from ${seeds.length} seeds`);
|
|
930
|
+
logger.debug(`Using ${library.length} seed entries`);
|
|
931
|
+
const resolved = await resolveAniListIds(library);
|
|
932
|
+
logger.debug(`Resolved ${resolved.size} AniList IDs from ${library.length} seeds`);
|
|
961
933
|
const allRecs = /* @__PURE__ */ new Map();
|
|
962
934
|
for (const [, { anilistId, title }] of resolved) {
|
|
963
935
|
try {
|
|
@@ -1019,16 +991,6 @@ createRecommendationPlugin({
|
|
|
1019
991
|
} else {
|
|
1020
992
|
logger.warn("No access token provided - recommendation operations will fail");
|
|
1021
993
|
}
|
|
1022
|
-
const rawMax = params.adminConfig?.maxRecommendations;
|
|
1023
|
-
if (typeof rawMax === "number") {
|
|
1024
|
-
maxRecommendations = Math.max(1, Math.min(Math.round(rawMax), 50));
|
|
1025
|
-
logger.info(`Max recommendations set to: ${maxRecommendations}`);
|
|
1026
|
-
}
|
|
1027
|
-
const rawSeeds = params.adminConfig?.maxSeeds;
|
|
1028
|
-
if (typeof rawSeeds === "number") {
|
|
1029
|
-
maxSeeds = Math.max(1, Math.min(Math.round(rawSeeds), 25));
|
|
1030
|
-
logger.info(`Max seeds set to: ${maxSeeds}`);
|
|
1031
|
-
}
|
|
1032
994
|
const uc = params.userConfig;
|
|
1033
995
|
if (uc && typeof uc.searchFallback === "boolean") {
|
|
1034
996
|
searchFallback = uc.searchFallback;
|
|
@@ -1043,7 +1005,6 @@ export {
|
|
|
1043
1005
|
convertRecommendations,
|
|
1044
1006
|
dismissedIds,
|
|
1045
1007
|
mapAniListStatus,
|
|
1046
|
-
pickSeedEntries,
|
|
1047
1008
|
resolveAniListIds,
|
|
1048
1009
|
setClient,
|
|
1049
1010
|
setSearchFallback
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
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", "../node_modules/@ashdev/codex-plugin-sdk/src/storage.ts", "../src/anilist.ts", "../package.json", "../src/manifest.ts", "../src/index.ts"],
|
|
4
|
-
"sourcesContent": [null, null, null, null, null, "/**\n * AniList GraphQL API client for recommendations\n *\n * Uses AniList's recommendations and user list data to generate\n * personalized manga suggestions.\n */\n\nimport { ApiError, AuthError, RateLimitError } from \"@ashdev/codex-plugin-sdk\";\n\nconst ANILIST_API_URL = \"https://graphql.anilist.co\";\n\n// =============================================================================\n// GraphQL Queries\n// =============================================================================\n\nconst VIEWER_QUERY = `\n query {\n Viewer {\n id\n name\n }\n }\n`;\n\n/** Get recommendations for a specific manga */\nconst MEDIA_RECOMMENDATIONS_QUERY = `\n query ($mediaId: Int!, $page: Int, $perPage: Int) {\n Media(id: $mediaId, type: MANGA) {\n id\n title {\n romaji\n english\n }\n recommendations(page: $page, perPage: $perPage, sort: RATING_DESC) {\n pageInfo {\n hasNextPage\n }\n nodes {\n rating\n mediaRecommendation {\n id\n title {\n romaji\n english\n }\n coverImage {\n large\n }\n description(asHtml: false)\n genres\n averageScore\n popularity\n siteUrl\n status\n volumes\n }\n }\n }\n }\n }\n`;\n\n/** Search for a manga by title to find its AniList ID */\nconst SEARCH_MANGA_QUERY = `\n query ($search: String!) {\n Media(search: $search, type: MANGA) {\n id\n title {\n romaji\n english\n }\n }\n }\n`;\n\n/** Get the user's manga list to know what they've already seen */\nconst USER_MANGA_IDS_QUERY = `\n query ($userId: Int!, $page: Int, $perPage: Int) {\n Page(page: $page, perPage: $perPage) {\n pageInfo {\n hasNextPage\n currentPage\n }\n mediaList(userId: $userId, type: MANGA) {\n mediaId\n }\n }\n }\n`;\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** AniList media status values */\nexport type AniListMediaStatus =\n | \"FINISHED\"\n | \"RELEASING\"\n | \"NOT_YET_RELEASED\"\n | \"CANCELLED\"\n | \"HIATUS\";\n\nexport interface AniListRecommendationNode {\n rating: number;\n mediaRecommendation: {\n id: number;\n title: { romaji?: string; english?: string };\n coverImage: { large?: string };\n description: string | null;\n genres: string[];\n averageScore: number | null;\n popularity: number | null;\n siteUrl: string;\n status: AniListMediaStatus | null;\n volumes: number | null;\n } | null;\n}\n\ninterface SearchResult {\n id: number;\n title: { romaji?: string; english?: string };\n}\n\n// =============================================================================\n// Client\n// =============================================================================\n\nexport class AniListRecommendationClient {\n private accessToken: string;\n\n constructor(accessToken: string) {\n this.accessToken = accessToken;\n }\n\n private async query<T>(queryStr: string, variables?: Record<string, unknown>): Promise<T> {\n return this.executeQuery<T>(queryStr, variables, true);\n }\n\n private async executeQuery<T>(\n queryStr: string,\n variables: Record<string, unknown> | undefined,\n allowRetry: boolean,\n ): Promise<T> {\n let response: Response;\n try {\n response = await fetch(ANILIST_API_URL, {\n method: \"POST\",\n signal: AbortSignal.timeout(30_000),\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n Authorization: `Bearer ${this.accessToken}`,\n },\n body: JSON.stringify({ query: queryStr, variables }),\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === \"TimeoutError\") {\n throw new ApiError(\"AniList API request timed out after 30 seconds\");\n }\n throw error;\n }\n\n if (response.status === 401) {\n throw new AuthError(\"AniList access token is invalid or expired\");\n }\n\n if (response.status === 429) {\n const retryAfter = response.headers.get(\"Retry-After\");\n const retrySeconds = retryAfter ? Number.parseInt(retryAfter, 10) : 60;\n const waitSeconds = Number.isNaN(retrySeconds) ? 60 : retrySeconds;\n\n if (allowRetry) {\n await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));\n return this.executeQuery<T>(queryStr, variables, false);\n }\n\n throw new RateLimitError(waitSeconds, \"AniList rate limit exceeded\");\n }\n\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `AniList API error: ${response.status} ${response.statusText}${body ? ` - ${body}` : \"\"}`,\n );\n }\n\n const json = (await response.json()) as {\n data?: T;\n errors?: Array<{ message: string }>;\n };\n\n if (json.errors?.length) {\n const message = json.errors.map((e) => e.message).join(\"; \");\n throw new ApiError(`AniList GraphQL error: ${message}`);\n }\n\n if (!json.data) {\n throw new ApiError(\"AniList returned empty data\");\n }\n\n return json.data;\n }\n\n /** Get the authenticated viewer's ID */\n async getViewerId(): Promise<number> {\n const data = await this.query<{ Viewer: { id: number; name: string } }>(VIEWER_QUERY);\n return data.Viewer.id;\n }\n\n /** Search for a manga by title and return its AniList ID */\n async searchManga(title: string): Promise<SearchResult | null> {\n try {\n const data = await this.query<{ Media: SearchResult | null }>(SEARCH_MANGA_QUERY, {\n search: title,\n });\n return data.Media;\n } catch {\n return null;\n }\n }\n\n /** Get community recommendations for a specific manga (up to maxPages pages) */\n async getRecommendationsForMedia(\n mediaId: number,\n perPage = 10,\n maxPages = 3,\n ): Promise<AniListRecommendationNode[]> {\n const allNodes: AniListRecommendationNode[] = [];\n let page = 1;\n let hasMore = true;\n\n while (hasMore && page <= maxPages) {\n const data = await this.query<{\n Media: {\n id: number;\n title: { romaji?: string; english?: string };\n recommendations: {\n pageInfo: { hasNextPage: boolean };\n nodes: AniListRecommendationNode[];\n };\n };\n }>(MEDIA_RECOMMENDATIONS_QUERY, { mediaId, page, perPage });\n\n allNodes.push(...data.Media.recommendations.nodes);\n hasMore = data.Media.recommendations.pageInfo.hasNextPage;\n page++;\n }\n\n return allNodes;\n }\n\n /** Get all manga IDs in the user's list (for deduplication) */\n async getUserMangaIds(userId: number): Promise<Set<number>> {\n const ids = new Set<number>();\n let page = 1;\n let hasMore = true;\n\n while (hasMore) {\n const data = await this.query<{\n Page: {\n pageInfo: { hasNextPage: boolean; currentPage: number };\n mediaList: Array<{ mediaId: number }>;\n };\n }>(USER_MANGA_IDS_QUERY, { userId, page, perPage: 50 });\n\n for (const entry of data.Page.mediaList) {\n ids.add(entry.mediaId);\n }\n\n hasMore = data.Page.pageInfo.hasNextPage;\n page++;\n }\n\n return ids;\n }\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Get the best title from an AniList title object */\nexport function getBestTitle(title: { romaji?: string; english?: string }): string {\n return title.english || title.romaji || \"Unknown\";\n}\n\n/** Common HTML entities to decode */\nconst HTML_ENTITIES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n \""\": '\"',\n \"'\": \"'\",\n \"'\": \"'\",\n \" \": \" \",\n \"—\": \"\\u2014\",\n \"–\": \"\\u2013\",\n \"…\": \"\\u2026\",\n};\n\nconst ENTITY_PATTERN = /&(?:#(\\d+)|#x([0-9a-fA-F]+)|[a-zA-Z]+);/g;\n\n/** Strip HTML tags and decode HTML entities */\nexport function stripHtml(html: string | null): string | undefined {\n if (!html) return undefined;\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<[^>]*>/g, \"\")\n .replace(ENTITY_PATTERN, (match, decimal, hex) => {\n if (decimal) return String.fromCharCode(Number.parseInt(decimal, 10));\n if (hex) return String.fromCharCode(Number.parseInt(hex, 16));\n return HTML_ENTITIES[match] ?? match;\n })\n .trim();\n}\n", "{\n \"name\": \"@ashdev/codex-plugin-recommendations-anilist\",\n \"version\": \"1.10.6\",\n \"description\": \"AniList recommendation provider plugin for Codex - generates personalized manga recommendations based on your reading history\",\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/recommendations-anilist\"\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 --passWithNoTests\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"npm run lint && npm run build\"\n },\n \"keywords\": [\n \"codex\",\n \"plugin\",\n \"anilist\",\n \"recommendations\",\n \"manga\"\n ],\n \"author\": \"Codex\",\n \"license\": \"MIT\",\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"dependencies\": {\n \"@ashdev/codex-plugin-sdk\": \"^1.10.6\"\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 { PluginManifest } from \"@ashdev/codex-plugin-sdk\";\nimport packageJson from \"../package.json\" with { type: \"json\" };\n\n/** Canonical external ID source for AniList (`api:<service>` convention) */\nexport const EXTERNAL_ID_SOURCE_ANILIST = \"api:anilist\" as const;\n\nexport const manifest = {\n name: \"recommendations-anilist\",\n displayName: \"AniList Recommendations\",\n version: packageJson.version,\n description:\n \"Personalized manga recommendations from AniList based on your reading history and ratings.\",\n author: \"Codex\",\n homepage: \"https://github.com/AshDevFr/codex\",\n protocolVersion: \"1.0\",\n capabilities: {\n userRecommendationProvider: true,\n externalIdSource: EXTERNAL_ID_SOURCE_ANILIST,\n },\n requiredCredentials: [\n {\n key: \"access_token\",\n label: \"AniList Access Token\",\n description: \"OAuth access token for AniList API\",\n type: \"password\" as const,\n required: true,\n sensitive: true,\n },\n ],\n configSchema: {\n description: \"Recommendation configuration\",\n fields: [\n {\n key: \"maxRecommendations\",\n label: \"Maximum Recommendations\",\n description: \"Maximum number of recommendations to generate (1-50)\",\n type: \"number\" as const,\n required: false,\n default: 20,\n },\n {\n key: \"maxSeeds\",\n label: \"Maximum Seed Titles\",\n description: \"Number of top-rated library titles used to generate recommendations (1-25)\",\n type: \"number\" as const,\n required: false,\n default: 10,\n },\n ],\n },\n userConfigSchema: {\n description: \"Per-user recommendation settings\",\n fields: [\n {\n key: \"searchFallback\",\n label: \"Search Fallback\",\n description:\n \"When a series has no AniList ID, search by title to find a match. Disable for strict matching only.\",\n type: \"boolean\" as const,\n required: false,\n default: true,\n },\n ],\n },\n oauth: {\n authorizationUrl: \"https://anilist.co/api/v2/oauth/authorize\",\n tokenUrl: \"https://anilist.co/api/v2/oauth/token\",\n scopes: [],\n pkce: false,\n },\n userDescription: \"Personalized manga recommendations powered by AniList community data\",\n adminSetupInstructions:\n \"To enable OAuth login, create an AniList API client at https://anilist.co/settings/developer. Set the redirect URL to {your-codex-url}/api/v1/user/plugins/oauth/callback. Enter the Client ID below. Without OAuth configured, users can still connect by pasting a personal access token.\",\n userSetupInstructions:\n \"Connect your AniList account via OAuth, or paste a personal access token. To generate a token, visit https://anilist.co/settings/developer, create a client with redirect URL https://anilist.co/api/v2/oauth/pin, then authorize it to receive your token.\",\n} as const satisfies PluginManifest & {\n capabilities: { userRecommendationProvider: true };\n};\n", "/**\n * AniList Recommendations Plugin for Codex\n *\n * Generates personalized manga recommendations by:\n * 1. Matching user's library entries to AniList manga IDs\n * 2. Fetching community recommendations for highly-rated titles\n * 3. Scoring and deduplicating results\n * 4. Returning the top recommendations\n *\n * Communicates via JSON-RPC over stdio using the Codex plugin SDK.\n */\n\nimport {\n createLogger,\n createRecommendationPlugin,\n type InitializeParams,\n type PluginStorage,\n type Recommendation,\n type RecommendationClearResponse,\n type RecommendationDismissRequest,\n type RecommendationDismissResponse,\n type RecommendationProvider,\n type RecommendationRequest,\n type RecommendationResponse,\n type SeriesStatus,\n type UserLibraryEntry,\n} from \"@ashdev/codex-plugin-sdk\";\nimport {\n type AniListMediaStatus,\n AniListRecommendationClient,\n type AniListRecommendationNode,\n getBestTitle,\n stripHtml,\n} from \"./anilist.js\";\nimport { EXTERNAL_ID_SOURCE_ANILIST, manifest } from \"./manifest.js\";\n\nconst logger = createLogger({ name: \"recommendations-anilist\", level: \"debug\" });\n\n// Plugin state (set during initialization)\nlet client: AniListRecommendationClient | null = null;\nlet viewerId: number | null = null;\nlet maxRecommendations = 20;\nlet maxSeeds = 10;\nlet searchFallback = true;\nlet storage: PluginStorage | null = null;\n\n/** Set the AniList client (exported for testing) */\nexport function setClient(c: AniListRecommendationClient | null): void {\n client = c;\n}\n\n/** Set the searchFallback flag (exported for testing) */\nexport function setSearchFallback(enabled: boolean): void {\n searchFallback = enabled;\n}\n\n/** Storage key for persisted dismissed recommendation IDs */\nconst DISMISSED_STORAGE_KEY = \"dismissed_ids\";\n\n// In-memory cache of dismissed IDs (synced with storage).\n// Loaded from storage on initialize, updated on dismiss/clear.\nexport const dismissedIds = new Set<string>();\n\n/**\n * Load dismissed IDs from persistent storage into the in-memory cache.\n */\nasync function loadDismissedIds(): Promise<void> {\n if (!storage) return;\n try {\n const result = await storage.get(DISMISSED_STORAGE_KEY);\n if (Array.isArray(result.data)) {\n dismissedIds.clear();\n for (const id of result.data) {\n if (typeof id === \"string\") {\n dismissedIds.add(id);\n }\n }\n logger.debug(`Loaded ${dismissedIds.size} dismissed IDs from storage`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n logger.warn(`Failed to load dismissed IDs from storage: ${msg}`);\n }\n}\n\n/**\n * Persist the current dismissed IDs set to storage.\n */\nasync function saveDismissedIds(): Promise<void> {\n if (!storage) return;\n try {\n await storage.set(DISMISSED_STORAGE_KEY, [...dismissedIds]);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n logger.warn(`Failed to save dismissed IDs to storage: ${msg}`);\n }\n}\n\n// =============================================================================\n// Recommendation Generation\n// =============================================================================\n\n/**\n * Find AniList IDs for library entries.\n * Tries external_ids first, falls back to title search.\n */\nexport async function resolveAniListIds(\n entries: UserLibraryEntry[],\n): Promise<Map<string, { anilistId: number; title: string; rating: number }>> {\n if (!client) throw new Error(\"Plugin not initialized\");\n\n const resolved = new Map<string, { anilistId: number; title: string; rating: number }>();\n\n for (const entry of entries) {\n // Check if we already have an AniList external ID\n // Prefer api:anilist (new convention), fall back to legacy source names\n const anilistExt = entry.externalIds?.find(\n (e) =>\n e.source === EXTERNAL_ID_SOURCE_ANILIST || e.source === \"anilist\" || e.source === \"AniList\",\n );\n\n if (anilistExt) {\n const id = Number.parseInt(anilistExt.externalId, 10);\n if (!Number.isNaN(id)) {\n resolved.set(entry.seriesId, {\n anilistId: id,\n title: entry.title,\n rating: entry.userRating ?? 0,\n });\n continue;\n }\n }\n\n // Fall back to title search (when enabled)\n if (searchFallback) {\n const result = await client.searchManga(entry.title);\n if (result) {\n resolved.set(entry.seriesId, {\n anilistId: result.id,\n title: entry.title,\n rating: entry.userRating ?? 0,\n });\n }\n }\n }\n\n return resolved;\n}\n\n/**\n * Pick the best entries from the user's library to seed recommendations.\n * Prioritizes highly-rated, recently-read titles.\n */\nexport function pickSeedEntries(entries: UserLibraryEntry[], maxSeeds: number): UserLibraryEntry[] {\n // Sort by rating (desc), then by recency\n const sorted = [...entries].sort((a, b) => {\n const ratingDiff = (b.userRating ?? 0) - (a.userRating ?? 0);\n if (ratingDiff !== 0) return ratingDiff;\n // Fall back to books read as a proxy for engagement\n return b.booksRead - a.booksRead;\n });\n\n return sorted.slice(0, maxSeeds);\n}\n\n/**\n * Map AniList media status to Codex SeriesStatus.\n * AniList values: FINISHED, RELEASING, NOT_YET_RELEASED, CANCELLED, HIATUS\n */\nexport function mapAniListStatus(status: AniListMediaStatus | null): SeriesStatus | undefined {\n if (!status) return undefined;\n switch (status) {\n case \"RELEASING\":\n return \"ongoing\";\n case \"FINISHED\":\n return \"ended\";\n case \"HIATUS\":\n return \"hiatus\";\n case \"CANCELLED\":\n return \"abandoned\";\n case \"NOT_YET_RELEASED\":\n return \"unknown\";\n default:\n return undefined;\n }\n}\n\n/**\n * Convert AniList recommendation nodes into Recommendation objects.\n */\nexport function convertRecommendations(\n nodes: AniListRecommendationNode[],\n basedOnTitle: string,\n userMangaIds: Set<number>,\n excludeIds: Set<string>,\n): Recommendation[] {\n const results: Recommendation[] = [];\n\n for (const node of nodes) {\n if (!node.mediaRecommendation) continue;\n\n const media = node.mediaRecommendation;\n const externalId = String(media.id);\n\n // Skip if excluded or dismissed\n if (excludeIds.has(externalId) || dismissedIds.has(externalId)) continue;\n\n const inLibrary = userMangaIds.has(media.id);\n\n // Compute a relevance score based on community rating and AniList average score\n const communityScore = Math.max(0, Math.min(node.rating, 100)) / 100;\n const avgScore = media.averageScore ? media.averageScore / 100 : 0.5;\n const score = Math.round((communityScore * 0.6 + avgScore * 0.4) * 100) / 100;\n\n const status = mapAniListStatus(media.status);\n const totalBookCount = media.volumes ?? undefined;\n\n results.push({\n externalId,\n externalUrl: media.siteUrl,\n title: getBestTitle(media.title),\n coverUrl: media.coverImage.large ?? undefined,\n summary: stripHtml(media.description),\n genres: media.genres ?? [],\n score: Math.max(0, Math.min(score, 1)),\n reason: `Recommended because you liked ${basedOnTitle}`,\n basedOn: [basedOnTitle],\n inLibrary,\n status,\n totalBookCount,\n rating: media.averageScore ?? undefined,\n popularity: media.popularity ?? undefined,\n });\n }\n\n return results;\n}\n\n// =============================================================================\n// Provider Implementation\n// =============================================================================\n\nconst provider: RecommendationProvider = {\n async get(params: RecommendationRequest): Promise<RecommendationResponse> {\n if (!client) {\n throw new Error(\"Plugin not initialized - no AniList client\");\n }\n\n if (viewerId === null) {\n viewerId = await client.getViewerId();\n logger.info(`Authenticated as viewer ${viewerId}`);\n }\n\n const { library, limit, excludeIds: rawExcludeIds = [] } = params;\n const effectiveLimit = Math.min(limit ?? maxRecommendations, 50);\n const excludeIds = new Set(rawExcludeIds);\n\n // Return early if library is empty \u2014 no seeds to work with\n if (!library || library.length === 0) {\n logger.info(\"Empty library \u2014 returning no recommendations\");\n return { recommendations: [], generatedAt: new Date().toISOString(), cached: false };\n }\n\n // Get user's existing manga IDs for dedup\n const userMangaIds = await client.getUserMangaIds(viewerId);\n logger.debug(`User has ${userMangaIds.size} manga in AniList list`);\n\n // Pick seed entries (top-rated from user's library)\n const seeds = pickSeedEntries(library, maxSeeds);\n logger.debug(`Using ${seeds.length} seed entries from library of ${library.length}`);\n\n // Resolve AniList IDs for seed entries\n const resolved = await resolveAniListIds(seeds);\n logger.debug(`Resolved ${resolved.size} AniList IDs from ${seeds.length} seeds`);\n\n // Fetch recommendations for each seed\n const allRecs = new Map<string, Recommendation>();\n\n for (const [, { anilistId, title }] of resolved) {\n try {\n const nodes = await client.getRecommendationsForMedia(anilistId, 10);\n const recs = convertRecommendations(nodes, title, userMangaIds, excludeIds);\n\n for (const rec of recs) {\n // If we've seen this recommendation before, merge basedOn and keep higher score\n const existing = allRecs.get(rec.externalId);\n if (existing) {\n // Merge basedOn titles\n const mergedBasedOn = [...new Set([...existing.basedOn, ...rec.basedOn])];\n // Boost score slightly for multiply-recommended titles\n const boostedScore = Math.min(existing.score + 0.05, 1.0);\n allRecs.set(rec.externalId, {\n ...existing,\n score: Math.round(boostedScore * 100) / 100,\n basedOn: mergedBasedOn,\n reason:\n mergedBasedOn.length > 1\n ? `Recommended based on ${mergedBasedOn.join(\", \")}`\n : existing.reason,\n });\n } else {\n allRecs.set(rec.externalId, rec);\n }\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n logger.warn(`Failed to get recommendations for AniList ID ${anilistId}: ${msg}`);\n }\n }\n\n // Sort by score descending and take top N\n const sorted = [...allRecs.values()].sort((a, b) => b.score - a.score).slice(0, effectiveLimit);\n\n logger.info(`Generated ${sorted.length} recommendations from ${resolved.size} seed titles`);\n\n return {\n recommendations: sorted,\n generatedAt: new Date().toISOString(),\n cached: false,\n };\n },\n\n async dismiss(params: RecommendationDismissRequest): Promise<RecommendationDismissResponse> {\n dismissedIds.add(params.externalId);\n logger.debug(\n `Dismissed recommendation: ${params.externalId} (reason: ${params.reason ?? \"none\"})`,\n );\n await saveDismissedIds();\n return { dismissed: true };\n },\n\n async clear(): Promise<RecommendationClearResponse> {\n const count = dismissedIds.size;\n dismissedIds.clear();\n logger.info(`Cleared ${count} dismissed recommendations`);\n await saveDismissedIds();\n return { cleared: true };\n },\n};\n\n// =============================================================================\n// Plugin Initialization\n// =============================================================================\n\ncreateRecommendationPlugin({\n manifest,\n provider,\n logLevel: \"debug\",\n async onInitialize(params: InitializeParams) {\n const accessToken = params.credentials?.access_token;\n if (accessToken) {\n client = new AniListRecommendationClient(accessToken);\n logger.info(\"AniList client initialized with access token\");\n } else {\n logger.warn(\"No access token provided - recommendation operations will fail\");\n }\n\n // Read maxRecommendations from adminConfig (defined in configSchema)\n const rawMax = params.adminConfig?.maxRecommendations;\n if (typeof rawMax === \"number\") {\n maxRecommendations = Math.max(1, Math.min(Math.round(rawMax), 50));\n logger.info(`Max recommendations set to: ${maxRecommendations}`);\n }\n\n // Read maxSeeds from adminConfig (defined in configSchema)\n const rawSeeds = params.adminConfig?.maxSeeds;\n if (typeof rawSeeds === \"number\") {\n maxSeeds = Math.max(1, Math.min(Math.round(rawSeeds), 25));\n logger.info(`Max seeds set to: ${maxSeeds}`);\n }\n\n // Read searchFallback from userConfig (default: true \u2014 preserve existing behavior)\n const uc = params.userConfig;\n if (uc && typeof uc.searchFallback === \"boolean\") {\n searchFallback = uc.searchFallback;\n logger.info(`Search fallback set to: ${searchFallback}`);\n }\n\n // Capture the storage client and restore persisted dismissed IDs\n storage = params.storage;\n await loadDismissedIds();\n },\n});\n\nlogger.info(\"AniList recommendations plugin started\");\n"],
|
|
5
|
-
"mappings": ";;;AAkCO,IAAM,uBAAuB;;EAElC,aAAa;;EAEb,iBAAiB;;EAEjB,kBAAkB;;EAElB,gBAAgB;;EAEhB,gBAAgB;;AAMX,IAAM,qBAAqB;;EAEhC,cAAc;;EAEd,WAAW;;EAEX,aAAa;;EAEb,WAAW;;EAEX,cAAc;;;;ACnDV,IAAgB,cAAhB,cAAoC,MAAK;EAEpC;EAET,YAAY,SAAiB,MAAc;AACzC,UAAM,OAAO;AACb,SAAK,OAAO,KAAK,YAAY;AAC7B,SAAK,OAAO;EACd;;;;EAKA,iBAAc;AACZ,WAAO;MACL,MAAM,KAAK;MACX,SAAS,KAAK;MACd,MAAM,KAAK;;EAEf;;AAMI,IAAO,iBAAP,cAA8B,YAAW;EACpC,OAAO,mBAAmB;;EAE1B;EAET,YAAY,mBAA2B,SAAgB;AACrD,UAAM,WAAW,6BAA6B,iBAAiB,KAAK;MAClE;KACD;AACD,SAAK,oBAAoB;EAC3B;;AAaI,IAAO,YAAP,cAAyB,YAAW;EAC/B,OAAO,mBAAmB;EAEnC,YAAY,SAAgB;AAC1B,UAAM,WAAW,uBAAuB;EAC1C;;AAMI,IAAO,WAAP,cAAwB,YAAW;EAC9B,OAAO,mBAAmB;EAC1B;EAET,YAAY,SAAiB,YAAmB;AAC9C,UAAM,SAAS,eAAe,SAAY,EAAE,WAAU,IAAK,MAAS;AACpE,SAAK,aAAa;EACpB;;;;AClEF,IAAM,aAAuC;EAC3C,OAAO;EACP,MAAM;EACN,MAAM;EACN,OAAO;;AAeH,IAAO,SAAP,MAAa;EACA;EACA;EACA;EAEjB,YAAY,SAAsB;AAChC,SAAK,OAAO,QAAQ;AACpB,SAAK,WAAW,WAAW,QAAQ,SAAS,MAAM;AAClD,SAAK,aAAa,QAAQ,cAAc;EAC1C;EAEQ,UAAU,OAAe;AAC/B,WAAO,WAAW,KAAK,KAAK,KAAK;EACnC;EAEQ,OAAO,OAAiB,SAAiB,MAAc;AAC7D,UAAM,QAAkB,CAAA;AAExB,QAAI,KAAK,YAAY;AACnB,YAAM,MAAK,oBAAI,KAAI,GAAG,YAAW,CAAE;IACrC;AAEA,UAAM,KAAK,IAAI,MAAM,YAAW,CAAE,GAAG;AACrC,UAAM,KAAK,IAAI,KAAK,IAAI,GAAG;AAC3B,UAAM,KAAK,OAAO;AAElB,QAAI,SAAS,QAAW;AACtB,UAAI,gBAAgB,OAAO;AACzB,cAAM,KAAK,KAAK,KAAK,OAAO,EAAE;AAC9B,YAAI,KAAK,OAAO;AACd,gBAAM,KAAK;EAAK,KAAK,KAAK,EAAE;QAC9B;MACF,WAAW,OAAO,SAAS,UAAU;AACnC,cAAM,KAAK,KAAK,KAAK,UAAU,IAAI,CAAC,EAAE;MACxC,OAAO;AACL,cAAM,KAAK,KAAK,OAAO,IAAI,CAAC,EAAE;MAChC;IACF;AAEA,WAAO,MAAM,KAAK,GAAG;EACvB;EAEQ,IAAI,OAAiB,SAAiB,MAAc;AAC1D,QAAI,KAAK,UAAU,KAAK,GAAG;AAEzB,cAAQ,OAAO,MAAM,GAAG,KAAK,OAAO,OAAO,SAAS,IAAI,CAAC;CAAI;IAC/D;EACF;EAEA,MAAM,SAAiB,MAAc;AACnC,SAAK,IAAI,SAAS,SAAS,IAAI;EACjC;EAEA,KAAK,SAAiB,MAAc;AAClC,SAAK,IAAI,QAAQ,SAAS,IAAI;EAChC;EAEA,KAAK,SAAiB,MAAc;AAClC,SAAK,IAAI,QAAQ,SAAS,IAAI;EAChC;EAEA,MAAM,SAAiB,MAAc;AACnC,SAAK,IAAI,SAAS,SAAS,IAAI;EACjC;;AAMI,SAAU,aAAa,SAAsB;AACjD,SAAO,IAAI,OAAO,OAAO;AAC3B;;;ACvFA,SAAS,uBAAuB;;;AC8E1B,IAAO,eAAP,cAA4B,MAAK;EAGnB;EACA;EAHlB,YACE,SACgB,MACA,MAAc;AAE9B,UAAM,OAAO;AAHG,SAAA,OAAA;AACA,SAAA,OAAA;AAGhB,SAAK,OAAO;EACd;;AAiBI,IAAO,gBAAP,MAAoB;EAChB,SAAS;EACT,kBAAkB,oBAAI,IAAG;EAOzB;;;;;;;EAQR,YAAY,SAAiB;AAC3B,SAAK,UACH,YACC,CAAC,SAAgB;AAChB,cAAQ,OAAO,MAAM,IAAI;IAC3B;EACJ;;;;;;;EAQA,MAAM,IAAI,KAAW;AACnB,WAAQ,MAAM,KAAK,YAAY,eAAe,EAAE,IAAG,CAAE;EACvD;;;;;;;;;EAUA,MAAM,IAAI,KAAa,MAAe,WAAkB;AACtD,UAAM,SAAkC,EAAE,KAAK,KAAI;AACnD,QAAI,cAAc,QAAW;AAC3B,aAAO,YAAY;IACrB;AACA,WAAQ,MAAM,KAAK,YAAY,eAAe,MAAM;EACtD;;;;;;;EAQA,MAAM,OAAO,KAAW;AACtB,WAAQ,MAAM,KAAK,YAAY,kBAAkB,EAAE,IAAG,CAAE;EAC1D;;;;;;EAOA,MAAM,OAAI;AACR,WAAQ,MAAM,KAAK,YAAY,gBAAgB,CAAA,CAAE;EACnD;;;;;;EAOA,MAAM,QAAK;AACT,WAAQ,MAAM,KAAK,YAAY,iBAAiB,CAAA,CAAE;EACpD;;;;;;;EAQA,eAAe,MAAY;AACzB,UAAM,UAAU,KAAK,KAAI;AACzB,QAAI,CAAC;AAAS;AAEd,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,OAAO;IAC7B,QAAQ;AAEN;IACF;AAEA,UAAM,MAAM;AAGZ,QAAI,IAAI,WAAW,QAAW;AAE5B;IACF;AAEA,UAAM,KAAK,IAAI;AACf,QAAI,OAAO,UAAa,OAAO;AAAM;AAErC,UAAM,UAAU,KAAK,gBAAgB,IAAI,EAAqB;AAC9D,QAAI,CAAC;AAAS;AAEd,SAAK,gBAAgB,OAAO,EAAqB;AAEjD,QAAI,WAAW,OAAO,IAAI,OAAO;AAC/B,YAAM,MAAM,IAAI;AAChB,cAAQ,OAAO,IAAI,aAAa,IAAI,SAAS,IAAI,MAAM,IAAI,IAAI,CAAC;IAClE,OAAO;AACL,cAAQ,QAAQ,IAAI,MAAM;IAC5B;EACF;;;;EAKA,YAAS;AACP,eAAW,CAAC,EAAE,OAAO,KAAK,KAAK,iBAAiB;AAC9C,cAAQ,OAAO,IAAI,aAAa,0BAA0B,EAAE,CAAC;IAC/D;AACA,SAAK,gBAAgB,MAAK;EAC5B;;;;EAMQ,YAAY,QAAgB,QAAe;AACjD,UAAM,KAAK,KAAK;AAEhB,UAAM,UAA0B;MAC9B,SAAS;MACT;MACA;MACA;;AAGF,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAU;AACrC,WAAK,gBAAgB,IAAI,IAAI,EAAE,SAAS,OAAM,CAAE;AAEhD,UAAI;AACF,aAAK,QAAQ,GAAG,KAAK,UAAU,OAAO,CAAC;CAAI;MAC7C,SAAS,KAAK;AACZ,aAAK,gBAAgB,OAAO,EAAE;AAC9B,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,eAAO,IAAI,aAAa,2BAA2B,OAAO,IAAI,EAAE,CAAC;MACnE;IACF,CAAC;EACH;;;;AD5NF,SAAS,qBAAqB,QAAiB,QAAgB;AAC7D,MAAI,WAAW,QAAQ,WAAW,QAAW;AAC3C,WAAO,EAAE,OAAO,UAAU,SAAS,qBAAoB;EACzD;AACA,MAAI,OAAO,WAAW,UAAU;AAC9B,WAAO,EAAE,OAAO,UAAU,SAAS,2BAA0B;EAC/D;AAEA,QAAM,MAAM;AACZ,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,IAAI,KAAK;AACvB,QAAI,UAAU,UAAa,UAAU,MAAM;AACzC,aAAO,EAAE,OAAO,SAAS,GAAG,KAAK,eAAc;IACjD;AACA,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,EAAE,OAAO,SAAS,GAAG,KAAK,oBAAmB;IACtD;AACA,QAAI,MAAM,KAAI,MAAO,IAAI;AACvB,aAAO,EAAE,OAAO,SAAS,GAAG,KAAK,mBAAkB;IACrD;EACF;AAEA,SAAO;AACT;AAuDA,SAAS,mBAAmB,IAA4B,OAAsB;AAC5E,SAAO;IACL,SAAS;IACT;IACA,OAAO;MACL,MAAM,qBAAqB;MAC3B,SAAS,mBAAmB,MAAM,OAAO;MACzC,MAAM,EAAE,OAAO,MAAM,MAAK;;;AAGhC;AAsDA,SAAS,mBAAmB,SAA4B;AACtD,QAAM,EAAE,UAAAA,WAAU,cAAc,WAAW,QAAQ,OAAO,OAAM,IAAK;AACrE,QAAMC,UAAS,aAAa,EAAE,MAAMD,UAAS,MAAM,OAAO,SAAQ,CAAE;AACpE,QAAM,SAAS,QAAQ,GAAG,KAAK,YAAY;AAC3C,QAAME,WAAU,IAAI,cAAa;AAEjC,EAAAD,QAAO,KAAK,YAAY,MAAM,KAAKD,UAAS,WAAW,KAAKA,UAAS,OAAO,EAAE;AAE9E,QAAM,KAAK,gBAAgB;IACzB,OAAO,QAAQ;IACf,UAAU;GACX;AAED,KAAG,GAAG,QAAQ,CAAC,SAAQ;AACrB,SAAK,WAAW,MAAMA,WAAU,cAAc,QAAQC,SAAQC,QAAO;EACvE,CAAC;AAED,KAAG,GAAG,SAAS,MAAK;AAClB,IAAAD,QAAO,KAAK,6BAA6B;AACzC,IAAAC,SAAQ,UAAS;AACjB,YAAQ,KAAK,CAAC;EAChB,CAAC;AAED,UAAQ,GAAG,qBAAqB,CAAC,UAAS;AACxC,IAAAD,QAAO,MAAM,sBAAsB,KAAK;AACxC,YAAQ,KAAK,CAAC;EAChB,CAAC;AAED,UAAQ,GAAG,sBAAsB,CAAC,WAAU;AAC1C,IAAAA,QAAO,MAAM,uBAAuB,MAAM;EAC5C,CAAC;AACH;AAQA,SAAS,kBAAkB,KAA4B;AACrD,MAAI,IAAI,WAAW;AAAW,WAAO;AACrC,MAAI,IAAI,OAAO,UAAa,IAAI,OAAO;AAAM,WAAO;AACpD,SAAO,YAAY,OAAO,WAAW;AACvC;AAEA,eAAe,WACb,MACAD,WACA,cACA,QACAC,SACAC,UAAsB;AAEtB,QAAM,UAAU,KAAK,KAAI;AACzB,MAAI,CAAC;AAAS;AAKd,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,OAAO;EAC7B,QAAQ;EAER;AAEA,MAAI,UAAU,kBAAkB,MAAM,GAAG;AACvC,IAAAD,QAAO,MAAM,4BAA4B,EAAE,IAAI,OAAO,GAAE,CAAE;AAC1D,IAAAC,SAAQ,eAAe,OAAO;AAC9B;EACF;AAEA,MAAI,KAA6B;AAEjC,MAAI;AACF,UAAM,UAAW,UAAU,KAAK,MAAM,OAAO;AAC7C,SAAK,QAAQ;AAEb,IAAAD,QAAO,MAAM,qBAAqB,QAAQ,MAAM,IAAI,EAAE,IAAI,QAAQ,GAAE,CAAE;AAEtE,UAAM,WAAW,MAAM,cAAc,SAASD,WAAU,cAAc,QAAQC,SAAQC,QAAO;AAC7F,QAAI,aAAa,MAAM;AACrB,oBAAc,QAAQ;IACxB;EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,aAAa;AAChC,oBAAc;QACZ,SAAS;QACT,IAAI;QACJ,OAAO;UACL,MAAM,qBAAqB;UAC3B,SAAS;;OAEZ;IACH,WAAW,iBAAiB,aAAa;AACvC,oBAAc;QACZ,SAAS;QACT;QACA,OAAO,MAAM,eAAc;OAC5B;IACH,OAAO;AACL,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,MAAAD,QAAO,MAAM,kBAAkB,KAAK;AACpC,oBAAc;QACZ,SAAS;QACT;QACA,OAAO;UACL,MAAM,qBAAqB;UAC3B;;OAEH;IACH;EACF;AACF;AAEA,eAAe,cACb,SACAD,WACA,cACA,QACAC,SACAC,UAAsB;AAEtB,QAAM,EAAE,QAAQ,QAAQ,GAAE,IAAK;AAG/B,UAAQ,QAAQ;IACd,KAAK,cAAc;AACjB,YAAM,aAAc,UAAU,CAAA;AAE9B,iBAAW,UAAUA;AACrB,UAAI,cAAc;AAChB,cAAM,aAAa,UAAU;MAC/B;AACA,aAAO,EAAE,SAAS,OAAO,IAAI,QAAQF,UAAQ;IAC/C;IAEA,KAAK;AACH,aAAO,EAAE,SAAS,OAAO,IAAI,QAAQ,OAAM;IAE7C,KAAK,YAAY;AACf,MAAAC,QAAO,KAAK,oBAAoB;AAChC,MAAAC,SAAQ,UAAS;AACjB,YAAMC,YAA4B,EAAE,SAAS,OAAO,IAAI,QAAQ,KAAI;AACpE,cAAQ,OAAO,MAAM,GAAG,KAAK,UAAUA,SAAQ,CAAC;GAAM,MAAK;AACzD,gBAAQ,KAAK,CAAC;MAChB,CAAC;AAED,aAAO;IACT;EACF;AAGA,QAAM,WAAW,MAAM,OAAO,QAAQ,QAAQ,EAAE;AAChD,MAAI,aAAa,MAAM;AACrB,WAAO;EACT;AAGA,SAAO;IACL,SAAS;IACT;IACA,OAAO;MACL,MAAM,qBAAqB;MAC3B,SAAS,qBAAqB,MAAM;;;AAG1C;AAEA,SAAS,cAAc,UAAyB;AAC9C,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,QAAQ,CAAC;CAAI;AACtD;AAMA,SAAS,eAAe,IAA4B,SAAe;AACjE,SAAO;IACL,SAAS;IACT;IACA,OAAO;MACL,MAAM,qBAAqB;MAC3B;;;AAGN;AAEA,SAAS,QAAQ,IAA4B,QAAe;AAC1D,SAAO,EAAE,SAAS,OAAO,IAAI,OAAM;AACrC;AAuPM,SAAU,2BAA2B,SAAoC;AAC7E,QAAM,EAAE,UAAAC,WAAU,UAAAC,WAAU,cAAc,SAAQ,IAAK;AAEvD,QAAM,SAAuB,OAAO,QAAQ,QAAQ,OAAM;AACxD,YAAQ,QAAQ;MACd,KAAK;AACH,eAAO,QAAQ,IAAI,MAAMA,UAAS,IAAI,MAA+B,CAAC;MACxE,KAAK,iCAAiC;AACpC,YAAI,CAACA,UAAS;AACZ,iBAAO,eAAe,IAAI,4DAA4D;AACxF,eAAO,QAAQ,IAAI,MAAMA,UAAS,cAAc,MAA8B,CAAC;MACjF;MACA,KAAK,yBAAyB;AAC5B,YAAI,CAACA,UAAS;AACZ,iBAAO,eAAe,IAAI,oDAAoD;AAChF,eAAO,QAAQ,IAAI,MAAMA,UAAS,MAAK,CAAE;MAC3C;MACA,KAAK,2BAA2B;AAC9B,YAAI,CAACA,UAAS;AACZ,iBAAO,eAAe,IAAI,sDAAsD;AAClF,cAAM,MAAM,qBAAqB,QAAQ,CAAC,YAAY,CAAC;AACvD,YAAI;AAAK,iBAAO,mBAAmB,IAAI,GAAG;AAC1C,eAAO,QAAQ,IAAI,MAAMA,UAAS,QAAQ,MAAsC,CAAC;MACnF;MACA;AACE,eAAO;IACX;EACF;AAEA,qBAAmB,EAAE,UAAAD,WAAU,cAAc,UAAU,OAAO,kBAAkB,OAAM,CAAE;AAC1F;;;AE3oBA,IAAM,kBAAkB;AAMxB,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUrB,IAAM,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsCpC,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAa3B,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmDtB,IAAM,8BAAN,MAAkC;AAAA,EAC/B;AAAA,EAER,YAAY,aAAqB;AAC/B,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAc,MAAS,UAAkB,WAAiD;AACxF,WAAO,KAAK,aAAgB,UAAU,WAAW,IAAI;AAAA,EACvD;AAAA,EAEA,MAAc,aACZ,UACA,WACA,YACY;AACZ,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,iBAAiB;AAAA,QACtC,QAAQ;AAAA,QACR,QAAQ,YAAY,QAAQ,GAAM;AAAA,QAClC,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,eAAe,UAAU,KAAK,WAAW;AAAA,QAC3C;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,OAAO,UAAU,UAAU,CAAC;AAAA,MACrD,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB,MAAM,SAAS,gBAAgB;AAClE,cAAM,IAAI,SAAS,gDAAgD;AAAA,MACrE;AACA,YAAM;AAAA,IACR;AAEA,QAAI,SAAS,WAAW,KAAK;AAC3B,YAAM,IAAI,UAAU,4CAA4C;AAAA,IAClE;AAEA,QAAI,SAAS,WAAW,KAAK;AAC3B,YAAM,aAAa,SAAS,QAAQ,IAAI,aAAa;AACrD,YAAM,eAAe,aAAa,OAAO,SAAS,YAAY,EAAE,IAAI;AACpE,YAAM,cAAc,OAAO,MAAM,YAAY,IAAI,KAAK;AAEtD,UAAI,YAAY;AACd,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,cAAc,GAAI,CAAC;AACtE,eAAO,KAAK,aAAgB,UAAU,WAAW,KAAK;AAAA,MACxD;AAEA,YAAM,IAAI,eAAe,aAAa,6BAA6B;AAAA,IACrE;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,YAAM,IAAI;AAAA,QACR,sBAAsB,SAAS,MAAM,IAAI,SAAS,UAAU,GAAG,OAAO,MAAM,IAAI,KAAK,EAAE;AAAA,MACzF;AAAA,IACF;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAKlC,QAAI,KAAK,QAAQ,QAAQ;AACvB,YAAM,UAAU,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI;AAC3D,YAAM,IAAI,SAAS,0BAA0B,OAAO,EAAE;AAAA,IACxD;AAEA,QAAI,CAAC,KAAK,MAAM;AACd,YAAM,IAAI,SAAS,6BAA6B;AAAA,IAClD;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,cAA+B;AACnC,UAAM,OAAO,MAAM,KAAK,MAAgD,YAAY;AACpF,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA,EAGA,MAAM,YAAY,OAA6C;AAC7D,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,MAAsC,oBAAoB;AAAA,QAChF,QAAQ;AAAA,MACV,CAAC;AACD,aAAO,KAAK;AAAA,IACd,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,2BACJ,SACA,UAAU,IACV,WAAW,GAC2B;AACtC,UAAM,WAAwC,CAAC;AAC/C,QAAI,OAAO;AACX,QAAI,UAAU;AAEd,WAAO,WAAW,QAAQ,UAAU;AAClC,YAAM,OAAO,MAAM,KAAK,MASrB,6BAA6B,EAAE,SAAS,MAAM,QAAQ,CAAC;AAE1D,eAAS,KAAK,GAAG,KAAK,MAAM,gBAAgB,KAAK;AACjD,gBAAU,KAAK,MAAM,gBAAgB,SAAS;AAC9C;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,gBAAgB,QAAsC;AAC1D,UAAM,MAAM,oBAAI,IAAY;AAC5B,QAAI,OAAO;AACX,QAAI,UAAU;AAEd,WAAO,SAAS;AACd,YAAM,OAAO,MAAM,KAAK,MAKrB,sBAAsB,EAAE,QAAQ,MAAM,SAAS,GAAG,CAAC;AAEtD,iBAAW,SAAS,KAAK,KAAK,WAAW;AACvC,YAAI,IAAI,MAAM,OAAO;AAAA,MACvB;AAEA,gBAAU,KAAK,KAAK,SAAS;AAC7B;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAOO,SAAS,aAAa,OAAsD;AACjF,SAAO,MAAM,WAAW,MAAM,UAAU;AAC1C;AAGA,IAAM,gBAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AACd;AAEA,IAAM,iBAAiB;AAGhB,SAAS,UAAU,MAAyC;AACjE,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KACJ,QAAQ,gBAAgB,IAAI,EAC5B,QAAQ,YAAY,EAAE,EACtB,QAAQ,gBAAgB,CAAC,OAAO,SAAS,QAAQ;AAChD,QAAI,QAAS,QAAO,OAAO,aAAa,OAAO,SAAS,SAAS,EAAE,CAAC;AACpE,QAAI,IAAK,QAAO,OAAO,aAAa,OAAO,SAAS,KAAK,EAAE,CAAC;AAC5D,WAAO,cAAc,KAAK,KAAK;AAAA,EACjC,CAAC,EACA,KAAK;AACV;;;AC1TA;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,MAAQ;AAAA,EACR,KAAO;AAAA,EACP,MAAQ;AAAA,EACR,OAAS;AAAA,IACP;AAAA,IACA;AAAA,EACF;AAAA,EACA,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,WAAa;AAAA,EACf;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,KAAO;AAAA,IACP,OAAS;AAAA,IACT,OAAS;AAAA,IACT,MAAQ;AAAA,IACR,YAAY;AAAA,IACZ,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,cAAc;AAAA,IACd,gBAAkB;AAAA,EACpB;AAAA,EACA,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,QAAU;AAAA,EACV,SAAW;AAAA,EACX,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,cAAgB;AAAA,IACd,4BAA4B;AAAA,EAC9B;AAAA,EACA,iBAAmB;AAAA,IACjB,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,SAAW;AAAA,IACX,YAAc;AAAA,IACd,QAAU;AAAA,EACZ;AACF;;;AC9CO,IAAM,6BAA6B;AAEnC,IAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,SAAS,gBAAY;AAAA,EACrB,aACE;AAAA,EACF,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,cAAc;AAAA,IACZ,4BAA4B;AAAA,IAC5B,kBAAkB;AAAA,EACpB;AAAA,EACA,qBAAqB;AAAA,IACnB;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,aAAa;AAAA,MACb,MAAM;AAAA,MACN,UAAU;AAAA,MACV,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ,aAAa;AAAA,IACb,QAAQ;AAAA,MACN;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,aAAa;AAAA,QACb,MAAM;AAAA,QACN,UAAU;AAAA,QACV,SAAS;AAAA,MACX;AAAA,MACA;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,aAAa;AAAA,QACb,MAAM;AAAA,QACN,UAAU;AAAA,QACV,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EACA,kBAAkB;AAAA,IAChB,aAAa;AAAA,IACb,QAAQ;AAAA,MACN;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,aACE;AAAA,QACF,MAAM;AAAA,QACN,UAAU;AAAA,QACV,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,kBAAkB;AAAA,IAClB,UAAU;AAAA,IACV,QAAQ,CAAC;AAAA,IACT,MAAM;AAAA,EACR;AAAA,EACA,iBAAiB;AAAA,EACjB,wBACE;AAAA,EACF,uBACE;AACJ;;;ACvCA,IAAM,SAAS,aAAa,EAAE,MAAM,2BAA2B,OAAO,QAAQ,CAAC;AAG/E,IAAI,SAA6C;AACjD,IAAI,WAA0B;AAC9B,IAAI,qBAAqB;AACzB,IAAI,WAAW;AACf,IAAI,iBAAiB;AACrB,IAAI,UAAgC;AAG7B,SAAS,UAAU,GAA6C;AACrE,WAAS;AACX;AAGO,SAAS,kBAAkB,SAAwB;AACxD,mBAAiB;AACnB;AAGA,IAAM,wBAAwB;AAIvB,IAAM,eAAe,oBAAI,IAAY;AAK5C,eAAe,mBAAkC;AAC/C,MAAI,CAAC,QAAS;AACd,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,IAAI,qBAAqB;AACtD,QAAI,MAAM,QAAQ,OAAO,IAAI,GAAG;AAC9B,mBAAa,MAAM;AACnB,iBAAW,MAAM,OAAO,MAAM;AAC5B,YAAI,OAAO,OAAO,UAAU;AAC1B,uBAAa,IAAI,EAAE;AAAA,QACrB;AAAA,MACF;AACA,aAAO,MAAM,UAAU,aAAa,IAAI,6BAA6B;AAAA,IACvE;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,WAAO,KAAK,8CAA8C,GAAG,EAAE;AAAA,EACjE;AACF;AAKA,eAAe,mBAAkC;AAC/C,MAAI,CAAC,QAAS;AACd,MAAI;AACF,UAAM,QAAQ,IAAI,uBAAuB,CAAC,GAAG,YAAY,CAAC;AAAA,EAC5D,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,WAAO,KAAK,4CAA4C,GAAG,EAAE;AAAA,EAC/D;AACF;AAUA,eAAsB,kBACpB,SAC4E;AAC5E,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,wBAAwB;AAErD,QAAM,WAAW,oBAAI,IAAkE;AAEvF,aAAW,SAAS,SAAS;AAG3B,UAAM,aAAa,MAAM,aAAa;AAAA,MACpC,CAAC,MACC,EAAE,WAAW,8BAA8B,EAAE,WAAW,aAAa,EAAE,WAAW;AAAA,IACtF;AAEA,QAAI,YAAY;AACd,YAAM,KAAK,OAAO,SAAS,WAAW,YAAY,EAAE;AACpD,UAAI,CAAC,OAAO,MAAM,EAAE,GAAG;AACrB,iBAAS,IAAI,MAAM,UAAU;AAAA,UAC3B,WAAW;AAAA,UACX,OAAO,MAAM;AAAA,UACb,QAAQ,MAAM,cAAc;AAAA,QAC9B,CAAC;AACD;AAAA,MACF;AAAA,IACF;AAGA,QAAI,gBAAgB;AAClB,YAAM,SAAS,MAAM,OAAO,YAAY,MAAM,KAAK;AACnD,UAAI,QAAQ;AACV,iBAAS,IAAI,MAAM,UAAU;AAAA,UAC3B,WAAW,OAAO;AAAA,UAClB,OAAO,MAAM;AAAA,UACb,QAAQ,MAAM,cAAc;AAAA,QAC9B,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,gBAAgB,SAA6BE,WAAsC;AAEjG,QAAM,SAAS,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM;AACzC,UAAM,cAAc,EAAE,cAAc,MAAM,EAAE,cAAc;AAC1D,QAAI,eAAe,EAAG,QAAO;AAE7B,WAAO,EAAE,YAAY,EAAE;AAAA,EACzB,CAAC;AAED,SAAO,OAAO,MAAM,GAAGA,SAAQ;AACjC;AAMO,SAAS,iBAAiB,QAA6D;AAC5F,MAAI,CAAC,OAAQ,QAAO;AACpB,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAKO,SAAS,uBACd,OACA,cACA,cACA,YACkB;AAClB,QAAM,UAA4B,CAAC;AAEnC,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,oBAAqB;AAE/B,UAAM,QAAQ,KAAK;AACnB,UAAM,aAAa,OAAO,MAAM,EAAE;AAGlC,QAAI,WAAW,IAAI,UAAU,KAAK,aAAa,IAAI,UAAU,EAAG;AAEhE,UAAM,YAAY,aAAa,IAAI,MAAM,EAAE;AAG3C,UAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,QAAQ,GAAG,CAAC,IAAI;AACjE,UAAM,WAAW,MAAM,eAAe,MAAM,eAAe,MAAM;AACjE,UAAM,QAAQ,KAAK,OAAO,iBAAiB,MAAM,WAAW,OAAO,GAAG,IAAI;AAE1E,UAAM,SAAS,iBAAiB,MAAM,MAAM;AAC5C,UAAM,iBAAiB,MAAM,WAAW;AAExC,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,OAAO,aAAa,MAAM,KAAK;AAAA,MAC/B,UAAU,MAAM,WAAW,SAAS;AAAA,MACpC,SAAS,UAAU,MAAM,WAAW;AAAA,MACpC,QAAQ,MAAM,UAAU,CAAC;AAAA,MACzB,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,CAAC,CAAC;AAAA,MACrC,QAAQ,iCAAiC,YAAY;AAAA,MACrD,SAAS,CAAC,YAAY;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,MAAM,gBAAgB;AAAA,MAC9B,YAAY,MAAM,cAAc;AAAA,IAClC,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAMA,IAAM,WAAmC;AAAA,EACvC,MAAM,IAAI,QAAgE;AACxE,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAC9D;AAEA,QAAI,aAAa,MAAM;AACrB,iBAAW,MAAM,OAAO,YAAY;AACpC,aAAO,KAAK,2BAA2B,QAAQ,EAAE;AAAA,IACnD;AAEA,UAAM,EAAE,SAAS,OAAO,YAAY,gBAAgB,CAAC,EAAE,IAAI;AAC3D,UAAM,iBAAiB,KAAK,IAAI,SAAS,oBAAoB,EAAE;AAC/D,UAAM,aAAa,IAAI,IAAI,aAAa;AAGxC,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AACpC,aAAO,KAAK,mDAA8C;AAC1D,aAAO,EAAE,iBAAiB,CAAC,GAAG,cAAa,oBAAI,KAAK,GAAE,YAAY,GAAG,QAAQ,MAAM;AAAA,IACrF;AAGA,UAAM,eAAe,MAAM,OAAO,gBAAgB,QAAQ;AAC1D,WAAO,MAAM,YAAY,aAAa,IAAI,wBAAwB;AAGlE,UAAM,QAAQ,gBAAgB,SAAS,QAAQ;AAC/C,WAAO,MAAM,SAAS,MAAM,MAAM,iCAAiC,QAAQ,MAAM,EAAE;AAGnF,UAAM,WAAW,MAAM,kBAAkB,KAAK;AAC9C,WAAO,MAAM,YAAY,SAAS,IAAI,qBAAqB,MAAM,MAAM,QAAQ;AAG/E,UAAM,UAAU,oBAAI,IAA4B;AAEhD,eAAW,CAAC,EAAE,EAAE,WAAW,MAAM,CAAC,KAAK,UAAU;AAC/C,UAAI;AACF,cAAM,QAAQ,MAAM,OAAO,2BAA2B,WAAW,EAAE;AACnE,cAAM,OAAO,uBAAuB,OAAO,OAAO,cAAc,UAAU;AAE1E,mBAAW,OAAO,MAAM;AAEtB,gBAAM,WAAW,QAAQ,IAAI,IAAI,UAAU;AAC3C,cAAI,UAAU;AAEZ,kBAAM,gBAAgB,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,SAAS,SAAS,GAAG,IAAI,OAAO,CAAC,CAAC;AAExE,kBAAM,eAAe,KAAK,IAAI,SAAS,QAAQ,MAAM,CAAG;AACxD,oBAAQ,IAAI,IAAI,YAAY;AAAA,cAC1B,GAAG;AAAA,cACH,OAAO,KAAK,MAAM,eAAe,GAAG,IAAI;AAAA,cACxC,SAAS;AAAA,cACT,QACE,cAAc,SAAS,IACnB,wBAAwB,cAAc,KAAK,IAAI,CAAC,KAChD,SAAS;AAAA,YACjB,CAAC;AAAA,UACH,OAAO;AACL,oBAAQ,IAAI,IAAI,YAAY,GAAG;AAAA,UACjC;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,cAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU;AACrD,eAAO,KAAK,gDAAgD,SAAS,KAAK,GAAG,EAAE;AAAA,MACjF;AAAA,IACF;AAGA,UAAM,SAAS,CAAC,GAAG,QAAQ,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc;AAE9F,WAAO,KAAK,aAAa,OAAO,MAAM,yBAAyB,SAAS,IAAI,cAAc;AAE1F,WAAO;AAAA,MACL,iBAAiB;AAAA,MACjB,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,QAA8E;AAC1F,iBAAa,IAAI,OAAO,UAAU;AAClC,WAAO;AAAA,MACL,6BAA6B,OAAO,UAAU,aAAa,OAAO,UAAU,MAAM;AAAA,IACpF;AACA,UAAM,iBAAiB;AACvB,WAAO,EAAE,WAAW,KAAK;AAAA,EAC3B;AAAA,EAEA,MAAM,QAA8C;AAClD,UAAM,QAAQ,aAAa;AAC3B,iBAAa,MAAM;AACnB,WAAO,KAAK,WAAW,KAAK,4BAA4B;AACxD,UAAM,iBAAiB;AACvB,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AACF;AAMA,2BAA2B;AAAA,EACzB;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,MAAM,aAAa,QAA0B;AAC3C,UAAM,cAAc,OAAO,aAAa;AACxC,QAAI,aAAa;AACf,eAAS,IAAI,4BAA4B,WAAW;AACpD,aAAO,KAAK,8CAA8C;AAAA,IAC5D,OAAO;AACL,aAAO,KAAK,gEAAgE;AAAA,IAC9E;AAGA,UAAM,SAAS,OAAO,aAAa;AACnC,QAAI,OAAO,WAAW,UAAU;AAC9B,2BAAqB,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC;AACjE,aAAO,KAAK,+BAA+B,kBAAkB,EAAE;AAAA,IACjE;AAGA,UAAM,WAAW,OAAO,aAAa;AACrC,QAAI,OAAO,aAAa,UAAU;AAChC,iBAAW,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,QAAQ,GAAG,EAAE,CAAC;AACzD,aAAO,KAAK,qBAAqB,QAAQ,EAAE;AAAA,IAC7C;AAGA,UAAM,KAAK,OAAO;AAClB,QAAI,MAAM,OAAO,GAAG,mBAAmB,WAAW;AAChD,uBAAiB,GAAG;AACpB,aAAO,KAAK,2BAA2B,cAAc,EAAE;AAAA,IACzD;AAGA,cAAU,OAAO;AACjB,UAAM,iBAAiB;AAAA,EACzB;AACF,CAAC;AAED,OAAO,KAAK,wCAAwC;",
|
|
6
|
-
"names": ["manifest", "logger", "storage", "response", "manifest", "provider"
|
|
4
|
+
"sourcesContent": [null, null, null, null, null, "/**\n * AniList GraphQL API client for recommendations\n *\n * Uses AniList's recommendations and user list data to generate\n * personalized manga suggestions.\n */\n\nimport { ApiError, AuthError, RateLimitError } from \"@ashdev/codex-plugin-sdk\";\n\nconst ANILIST_API_URL = \"https://graphql.anilist.co\";\n\n// =============================================================================\n// GraphQL Queries\n// =============================================================================\n\nconst VIEWER_QUERY = `\n query {\n Viewer {\n id\n name\n }\n }\n`;\n\n/** Get recommendations for a specific manga */\nconst MEDIA_RECOMMENDATIONS_QUERY = `\n query ($mediaId: Int!, $page: Int, $perPage: Int) {\n Media(id: $mediaId, type: MANGA) {\n id\n title {\n romaji\n english\n }\n recommendations(page: $page, perPage: $perPage, sort: RATING_DESC) {\n pageInfo {\n hasNextPage\n }\n nodes {\n rating\n mediaRecommendation {\n id\n title {\n romaji\n english\n }\n coverImage {\n large\n }\n description(asHtml: false)\n genres\n averageScore\n popularity\n siteUrl\n status\n volumes\n }\n }\n }\n }\n }\n`;\n\n/** Search for a manga by title to find its AniList ID */\nconst SEARCH_MANGA_QUERY = `\n query ($search: String!) {\n Media(search: $search, type: MANGA) {\n id\n title {\n romaji\n english\n }\n }\n }\n`;\n\n/** Get the user's manga list to know what they've already seen */\nconst USER_MANGA_IDS_QUERY = `\n query ($userId: Int!, $page: Int, $perPage: Int) {\n Page(page: $page, perPage: $perPage) {\n pageInfo {\n hasNextPage\n currentPage\n }\n mediaList(userId: $userId, type: MANGA) {\n mediaId\n }\n }\n }\n`;\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** AniList media status values */\nexport type AniListMediaStatus =\n | \"FINISHED\"\n | \"RELEASING\"\n | \"NOT_YET_RELEASED\"\n | \"CANCELLED\"\n | \"HIATUS\";\n\nexport interface AniListRecommendationNode {\n rating: number;\n mediaRecommendation: {\n id: number;\n title: { romaji?: string; english?: string };\n coverImage: { large?: string };\n description: string | null;\n genres: string[];\n averageScore: number | null;\n popularity: number | null;\n siteUrl: string;\n status: AniListMediaStatus | null;\n volumes: number | null;\n } | null;\n}\n\ninterface SearchResult {\n id: number;\n title: { romaji?: string; english?: string };\n}\n\n// =============================================================================\n// Client\n// =============================================================================\n\nexport class AniListRecommendationClient {\n private accessToken: string;\n\n constructor(accessToken: string) {\n this.accessToken = accessToken;\n }\n\n private async query<T>(queryStr: string, variables?: Record<string, unknown>): Promise<T> {\n return this.executeQuery<T>(queryStr, variables, true);\n }\n\n private async executeQuery<T>(\n queryStr: string,\n variables: Record<string, unknown> | undefined,\n allowRetry: boolean,\n ): Promise<T> {\n let response: Response;\n try {\n response = await fetch(ANILIST_API_URL, {\n method: \"POST\",\n signal: AbortSignal.timeout(30_000),\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n Authorization: `Bearer ${this.accessToken}`,\n },\n body: JSON.stringify({ query: queryStr, variables }),\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === \"TimeoutError\") {\n throw new ApiError(\"AniList API request timed out after 30 seconds\");\n }\n throw error;\n }\n\n if (response.status === 401) {\n throw new AuthError(\"AniList access token is invalid or expired\");\n }\n\n if (response.status === 429) {\n const retryAfter = response.headers.get(\"Retry-After\");\n const retrySeconds = retryAfter ? Number.parseInt(retryAfter, 10) : 60;\n const waitSeconds = Number.isNaN(retrySeconds) ? 60 : retrySeconds;\n\n if (allowRetry) {\n await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));\n return this.executeQuery<T>(queryStr, variables, false);\n }\n\n throw new RateLimitError(waitSeconds, \"AniList rate limit exceeded\");\n }\n\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `AniList API error: ${response.status} ${response.statusText}${body ? ` - ${body}` : \"\"}`,\n );\n }\n\n const json = (await response.json()) as {\n data?: T;\n errors?: Array<{ message: string }>;\n };\n\n if (json.errors?.length) {\n const message = json.errors.map((e) => e.message).join(\"; \");\n throw new ApiError(`AniList GraphQL error: ${message}`);\n }\n\n if (!json.data) {\n throw new ApiError(\"AniList returned empty data\");\n }\n\n return json.data;\n }\n\n /** Get the authenticated viewer's ID */\n async getViewerId(): Promise<number> {\n const data = await this.query<{ Viewer: { id: number; name: string } }>(VIEWER_QUERY);\n return data.Viewer.id;\n }\n\n /** Search for a manga by title and return its AniList ID */\n async searchManga(title: string): Promise<SearchResult | null> {\n try {\n const data = await this.query<{ Media: SearchResult | null }>(SEARCH_MANGA_QUERY, {\n search: title,\n });\n return data.Media;\n } catch {\n return null;\n }\n }\n\n /** Get community recommendations for a specific manga (up to maxPages pages) */\n async getRecommendationsForMedia(\n mediaId: number,\n perPage = 10,\n maxPages = 3,\n ): Promise<AniListRecommendationNode[]> {\n const allNodes: AniListRecommendationNode[] = [];\n let page = 1;\n let hasMore = true;\n\n while (hasMore && page <= maxPages) {\n const data = await this.query<{\n Media: {\n id: number;\n title: { romaji?: string; english?: string };\n recommendations: {\n pageInfo: { hasNextPage: boolean };\n nodes: AniListRecommendationNode[];\n };\n };\n }>(MEDIA_RECOMMENDATIONS_QUERY, { mediaId, page, perPage });\n\n allNodes.push(...data.Media.recommendations.nodes);\n hasMore = data.Media.recommendations.pageInfo.hasNextPage;\n page++;\n }\n\n return allNodes;\n }\n\n /** Get all manga IDs in the user's list (for deduplication) */\n async getUserMangaIds(userId: number): Promise<Set<number>> {\n const ids = new Set<number>();\n let page = 1;\n let hasMore = true;\n\n while (hasMore) {\n const data = await this.query<{\n Page: {\n pageInfo: { hasNextPage: boolean; currentPage: number };\n mediaList: Array<{ mediaId: number }>;\n };\n }>(USER_MANGA_IDS_QUERY, { userId, page, perPage: 50 });\n\n for (const entry of data.Page.mediaList) {\n ids.add(entry.mediaId);\n }\n\n hasMore = data.Page.pageInfo.hasNextPage;\n page++;\n }\n\n return ids;\n }\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Get the best title from an AniList title object */\nexport function getBestTitle(title: { romaji?: string; english?: string }): string {\n return title.english || title.romaji || \"Unknown\";\n}\n\n/** Common HTML entities to decode */\nconst HTML_ENTITIES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n \""\": '\"',\n \"'\": \"'\",\n \"'\": \"'\",\n \" \": \" \",\n \"—\": \"\\u2014\",\n \"–\": \"\\u2013\",\n \"…\": \"\\u2026\",\n};\n\nconst ENTITY_PATTERN = /&(?:#(\\d+)|#x([0-9a-fA-F]+)|[a-zA-Z]+);/g;\n\n/** Strip HTML tags and decode HTML entities */\nexport function stripHtml(html: string | null): string | undefined {\n if (!html) return undefined;\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<[^>]*>/g, \"\")\n .replace(ENTITY_PATTERN, (match, decimal, hex) => {\n if (decimal) return String.fromCharCode(Number.parseInt(decimal, 10));\n if (hex) return String.fromCharCode(Number.parseInt(hex, 16));\n return HTML_ENTITIES[match] ?? match;\n })\n .trim();\n}\n", "{\n \"name\": \"@ashdev/codex-plugin-recommendations-anilist\",\n \"version\": \"1.10.8\",\n \"description\": \"AniList recommendation provider plugin for Codex - generates personalized manga recommendations based on your reading history\",\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/recommendations-anilist\"\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 --passWithNoTests\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"npm run lint && npm run build\"\n },\n \"keywords\": [\n \"codex\",\n \"plugin\",\n \"anilist\",\n \"recommendations\",\n \"manga\"\n ],\n \"author\": \"Codex\",\n \"license\": \"MIT\",\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"dependencies\": {\n \"@ashdev/codex-plugin-sdk\": \"^1.10.8\"\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 { PluginManifest } from \"@ashdev/codex-plugin-sdk\";\nimport packageJson from \"../package.json\" with { type: \"json\" };\n\n/** Canonical external ID source for AniList (`api:<service>` convention) */\nexport const EXTERNAL_ID_SOURCE_ANILIST = \"api:anilist\" as const;\n\nexport const manifest = {\n name: \"recommendations-anilist\",\n displayName: \"AniList Recommendations\",\n version: packageJson.version,\n description:\n \"Personalized manga recommendations from AniList based on your reading history and ratings.\",\n author: \"Codex\",\n homepage: \"https://github.com/AshDevFr/codex\",\n protocolVersion: \"1.0\",\n capabilities: {\n userRecommendationProvider: true,\n externalIdSource: EXTERNAL_ID_SOURCE_ANILIST,\n },\n requiredCredentials: [\n {\n key: \"access_token\",\n label: \"AniList Access Token\",\n description: \"OAuth access token for AniList API\",\n type: \"password\" as const,\n required: true,\n sensitive: true,\n },\n ],\n configSchema: {\n description: \"Recommendation configuration\",\n fields: [],\n },\n userConfigSchema: {\n description: \"Per-user recommendation settings\",\n fields: [\n {\n key: \"searchFallback\",\n label: \"Search Fallback\",\n description:\n \"When a series has no AniList ID, search by title to find a match. Disable for strict matching only.\",\n type: \"boolean\" as const,\n required: false,\n default: true,\n },\n ],\n },\n oauth: {\n authorizationUrl: \"https://anilist.co/api/v2/oauth/authorize\",\n tokenUrl: \"https://anilist.co/api/v2/oauth/token\",\n scopes: [],\n pkce: false,\n },\n userDescription: \"Personalized manga recommendations powered by AniList community data\",\n adminSetupInstructions:\n \"To enable OAuth login, create an AniList API client at https://anilist.co/settings/developer. Set the redirect URL to {your-codex-url}/api/v1/user/plugins/oauth/callback. Enter the Client ID below. Without OAuth configured, users can still connect by pasting a personal access token.\",\n userSetupInstructions:\n \"Connect your AniList account via OAuth, or paste a personal access token. To generate a token, visit https://anilist.co/settings/developer, create a client with redirect URL https://anilist.co/api/v2/oauth/pin, then authorize it to receive your token.\",\n} as const satisfies PluginManifest & {\n capabilities: { userRecommendationProvider: true };\n};\n", "/**\n * AniList Recommendations Plugin for Codex\n *\n * Generates personalized manga recommendations by:\n * 1. Matching user's library entries to AniList manga IDs\n * 2. Fetching community recommendations for highly-rated titles\n * 3. Scoring and deduplicating results\n * 4. Returning the top recommendations\n *\n * Communicates via JSON-RPC over stdio using the Codex plugin SDK.\n */\n\nimport {\n createLogger,\n createRecommendationPlugin,\n type InitializeParams,\n type PluginStorage,\n type Recommendation,\n type RecommendationClearResponse,\n type RecommendationDismissRequest,\n type RecommendationDismissResponse,\n type RecommendationProvider,\n type RecommendationRequest,\n type RecommendationResponse,\n type SeriesStatus,\n type UserLibraryEntry,\n} from \"@ashdev/codex-plugin-sdk\";\nimport {\n type AniListMediaStatus,\n AniListRecommendationClient,\n type AniListRecommendationNode,\n getBestTitle,\n stripHtml,\n} from \"./anilist.js\";\nimport { EXTERNAL_ID_SOURCE_ANILIST, manifest } from \"./manifest.js\";\n\nconst logger = createLogger({ name: \"recommendations-anilist\", level: \"debug\" });\n\n// Plugin state (set during initialization)\nlet client: AniListRecommendationClient | null = null;\nlet viewerId: number | null = null;\nlet searchFallback = true;\nlet storage: PluginStorage | null = null;\n\n/** Set the AniList client (exported for testing) */\nexport function setClient(c: AniListRecommendationClient | null): void {\n client = c;\n}\n\n/** Set the searchFallback flag (exported for testing) */\nexport function setSearchFallback(enabled: boolean): void {\n searchFallback = enabled;\n}\n\n/** Storage key for persisted dismissed recommendation IDs */\nconst DISMISSED_STORAGE_KEY = \"dismissed_ids\";\n\n// In-memory cache of dismissed IDs (synced with storage).\n// Loaded from storage on initialize, updated on dismiss/clear.\nexport const dismissedIds = new Set<string>();\n\n/**\n * Load dismissed IDs from persistent storage into the in-memory cache.\n */\nasync function loadDismissedIds(): Promise<void> {\n if (!storage) return;\n try {\n const result = await storage.get(DISMISSED_STORAGE_KEY);\n if (Array.isArray(result.data)) {\n dismissedIds.clear();\n for (const id of result.data) {\n if (typeof id === \"string\") {\n dismissedIds.add(id);\n }\n }\n logger.debug(`Loaded ${dismissedIds.size} dismissed IDs from storage`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n logger.warn(`Failed to load dismissed IDs from storage: ${msg}`);\n }\n}\n\n/**\n * Persist the current dismissed IDs set to storage.\n */\nasync function saveDismissedIds(): Promise<void> {\n if (!storage) return;\n try {\n await storage.set(DISMISSED_STORAGE_KEY, [...dismissedIds]);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n logger.warn(`Failed to save dismissed IDs to storage: ${msg}`);\n }\n}\n\n// =============================================================================\n// Recommendation Generation\n// =============================================================================\n\n/**\n * Find AniList IDs for library entries.\n * Tries external_ids first, falls back to title search.\n */\nexport async function resolveAniListIds(\n entries: UserLibraryEntry[],\n): Promise<Map<string, { anilistId: number; title: string; rating: number }>> {\n if (!client) throw new Error(\"Plugin not initialized\");\n\n const resolved = new Map<string, { anilistId: number; title: string; rating: number }>();\n\n for (const entry of entries) {\n // Check if we already have an AniList external ID\n // Prefer api:anilist (new convention), fall back to legacy source names\n const anilistExt = entry.externalIds?.find(\n (e) =>\n e.source === EXTERNAL_ID_SOURCE_ANILIST || e.source === \"anilist\" || e.source === \"AniList\",\n );\n\n if (anilistExt) {\n const id = Number.parseInt(anilistExt.externalId, 10);\n if (!Number.isNaN(id)) {\n resolved.set(entry.seriesId, {\n anilistId: id,\n title: entry.title,\n rating: entry.userRating ?? 0,\n });\n continue;\n }\n }\n\n // Fall back to title search (when enabled)\n if (searchFallback) {\n const result = await client.searchManga(entry.title);\n if (result) {\n resolved.set(entry.seriesId, {\n anilistId: result.id,\n title: entry.title,\n rating: entry.userRating ?? 0,\n });\n }\n }\n }\n\n return resolved;\n}\n\n/**\n * Map AniList media status to Codex SeriesStatus.\n * AniList values: FINISHED, RELEASING, NOT_YET_RELEASED, CANCELLED, HIATUS\n */\nexport function mapAniListStatus(status: AniListMediaStatus | null): SeriesStatus | undefined {\n if (!status) return undefined;\n switch (status) {\n case \"RELEASING\":\n return \"ongoing\";\n case \"FINISHED\":\n return \"ended\";\n case \"HIATUS\":\n return \"hiatus\";\n case \"CANCELLED\":\n return \"abandoned\";\n case \"NOT_YET_RELEASED\":\n return \"unknown\";\n default:\n return undefined;\n }\n}\n\n/**\n * Convert AniList recommendation nodes into Recommendation objects.\n */\nexport function convertRecommendations(\n nodes: AniListRecommendationNode[],\n basedOnTitle: string,\n userMangaIds: Set<number>,\n excludeIds: Set<string>,\n): Recommendation[] {\n const results: Recommendation[] = [];\n\n for (const node of nodes) {\n if (!node.mediaRecommendation) continue;\n\n const media = node.mediaRecommendation;\n const externalId = String(media.id);\n\n // Skip if excluded or dismissed\n if (excludeIds.has(externalId) || dismissedIds.has(externalId)) continue;\n\n const inLibrary = userMangaIds.has(media.id);\n\n // Compute a relevance score based on community rating and AniList average score\n const communityScore = Math.max(0, Math.min(node.rating, 100)) / 100;\n const avgScore = media.averageScore ? media.averageScore / 100 : 0.5;\n const score = Math.round((communityScore * 0.6 + avgScore * 0.4) * 100) / 100;\n\n const status = mapAniListStatus(media.status);\n const totalBookCount = media.volumes ?? undefined;\n\n results.push({\n externalId,\n externalUrl: media.siteUrl,\n title: getBestTitle(media.title),\n coverUrl: media.coverImage.large ?? undefined,\n summary: stripHtml(media.description),\n genres: media.genres ?? [],\n score: Math.max(0, Math.min(score, 1)),\n reason: `Recommended because you liked ${basedOnTitle}`,\n basedOn: [basedOnTitle],\n inLibrary,\n status,\n totalBookCount,\n rating: media.averageScore ?? undefined,\n popularity: media.popularity ?? undefined,\n });\n }\n\n return results;\n}\n\n// =============================================================================\n// Provider Implementation\n// =============================================================================\n\nconst provider: RecommendationProvider = {\n async get(params: RecommendationRequest): Promise<RecommendationResponse> {\n if (!client) {\n throw new Error(\"Plugin not initialized - no AniList client\");\n }\n\n if (viewerId === null) {\n viewerId = await client.getViewerId();\n logger.info(`Authenticated as viewer ${viewerId}`);\n }\n\n const { library, limit, excludeIds: rawExcludeIds = [] } = params;\n const effectiveLimit = Math.min(limit ?? 20, 50);\n const excludeIds = new Set(rawExcludeIds);\n\n // Library entries are pre-curated seeds from Codex server (rated + recent reads).\n // Return early if no seeds provided.\n if (!library || library.length === 0) {\n logger.info(\"Empty library \u2014 returning no recommendations\");\n return { recommendations: [], generatedAt: new Date().toISOString(), cached: false };\n }\n\n // Get user's existing manga IDs for dedup\n const userMangaIds = await client.getUserMangaIds(viewerId);\n logger.debug(`User has ${userMangaIds.size} manga in AniList list`);\n\n // Resolve AniList IDs for seed entries (library is already curated by Codex)\n logger.debug(`Using ${library.length} seed entries`);\n const resolved = await resolveAniListIds(library);\n logger.debug(`Resolved ${resolved.size} AniList IDs from ${library.length} seeds`);\n\n // Fetch recommendations for each seed\n const allRecs = new Map<string, Recommendation>();\n\n for (const [, { anilistId, title }] of resolved) {\n try {\n const nodes = await client.getRecommendationsForMedia(anilistId, 10);\n const recs = convertRecommendations(nodes, title, userMangaIds, excludeIds);\n\n for (const rec of recs) {\n // If we've seen this recommendation before, merge basedOn and keep higher score\n const existing = allRecs.get(rec.externalId);\n if (existing) {\n // Merge basedOn titles\n const mergedBasedOn = [...new Set([...existing.basedOn, ...rec.basedOn])];\n // Boost score slightly for multiply-recommended titles\n const boostedScore = Math.min(existing.score + 0.05, 1.0);\n allRecs.set(rec.externalId, {\n ...existing,\n score: Math.round(boostedScore * 100) / 100,\n basedOn: mergedBasedOn,\n reason:\n mergedBasedOn.length > 1\n ? `Recommended based on ${mergedBasedOn.join(\", \")}`\n : existing.reason,\n });\n } else {\n allRecs.set(rec.externalId, rec);\n }\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n logger.warn(`Failed to get recommendations for AniList ID ${anilistId}: ${msg}`);\n }\n }\n\n // Sort by score descending and take top N\n const sorted = [...allRecs.values()].sort((a, b) => b.score - a.score).slice(0, effectiveLimit);\n\n logger.info(`Generated ${sorted.length} recommendations from ${resolved.size} seed titles`);\n\n return {\n recommendations: sorted,\n generatedAt: new Date().toISOString(),\n cached: false,\n };\n },\n\n async dismiss(params: RecommendationDismissRequest): Promise<RecommendationDismissResponse> {\n dismissedIds.add(params.externalId);\n logger.debug(\n `Dismissed recommendation: ${params.externalId} (reason: ${params.reason ?? \"none\"})`,\n );\n await saveDismissedIds();\n return { dismissed: true };\n },\n\n async clear(): Promise<RecommendationClearResponse> {\n const count = dismissedIds.size;\n dismissedIds.clear();\n logger.info(`Cleared ${count} dismissed recommendations`);\n await saveDismissedIds();\n return { cleared: true };\n },\n};\n\n// =============================================================================\n// Plugin Initialization\n// =============================================================================\n\ncreateRecommendationPlugin({\n manifest,\n provider,\n logLevel: \"debug\",\n async onInitialize(params: InitializeParams) {\n const accessToken = params.credentials?.access_token;\n if (accessToken) {\n client = new AniListRecommendationClient(accessToken);\n logger.info(\"AniList client initialized with access token\");\n } else {\n logger.warn(\"No access token provided - recommendation operations will fail\");\n }\n\n // Read searchFallback from userConfig (default: true \u2014 preserve existing behavior)\n const uc = params.userConfig;\n if (uc && typeof uc.searchFallback === \"boolean\") {\n searchFallback = uc.searchFallback;\n logger.info(`Search fallback set to: ${searchFallback}`);\n }\n\n // Capture the storage client and restore persisted dismissed IDs\n storage = params.storage;\n await loadDismissedIds();\n },\n});\n\nlogger.info(\"AniList recommendations plugin started\");\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["manifest", "logger", "storage", "response", "manifest", "provider"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashdev/codex-plugin-recommendations-anilist",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.8",
|
|
4
4
|
"description": "AniList recommendation provider plugin for Codex - generates personalized manga recommendations based on your reading history",
|
|
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.10.
|
|
42
|
+
"@ashdev/codex-plugin-sdk": "^1.10.8"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@biomejs/biome": "^2.3.13",
|