@ashdev/codex-plugin-recommendations-anilist 1.15.1 → 1.16.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 +117 -4
- package/dist/index.js.map +2 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -522,10 +522,20 @@ var MEDIA_RECOMMENDATIONS_QUERY = `
|
|
|
522
522
|
}
|
|
523
523
|
description(asHtml: false)
|
|
524
524
|
genres
|
|
525
|
+
tags {
|
|
526
|
+
name
|
|
527
|
+
rank
|
|
528
|
+
category
|
|
529
|
+
}
|
|
525
530
|
averageScore
|
|
526
531
|
popularity
|
|
527
532
|
siteUrl
|
|
528
533
|
status
|
|
534
|
+
format
|
|
535
|
+
countryOfOrigin
|
|
536
|
+
startDate {
|
|
537
|
+
year
|
|
538
|
+
}
|
|
529
539
|
volumes
|
|
530
540
|
}
|
|
531
541
|
}
|
|
@@ -630,7 +640,7 @@ var AniListRecommendationClient = class {
|
|
|
630
640
|
}
|
|
631
641
|
}
|
|
632
642
|
/** Get community recommendations for a specific manga (up to maxPages pages) */
|
|
633
|
-
async getRecommendationsForMedia(mediaId, perPage = 10, maxPages =
|
|
643
|
+
async getRecommendationsForMedia(mediaId, perPage = 10, maxPages = 5) {
|
|
634
644
|
const allNodes = [];
|
|
635
645
|
let page = 1;
|
|
636
646
|
let hasMore = true;
|
|
@@ -686,7 +696,7 @@ function stripHtml(html) {
|
|
|
686
696
|
// package.json
|
|
687
697
|
var package_default = {
|
|
688
698
|
name: "@ashdev/codex-plugin-recommendations-anilist",
|
|
689
|
-
version: "1.
|
|
699
|
+
version: "1.16.0",
|
|
690
700
|
description: "AniList recommendation provider plugin for Codex - generates personalized manga recommendations based on your reading history",
|
|
691
701
|
main: "dist/index.js",
|
|
692
702
|
bin: "dist/index.js",
|
|
@@ -725,7 +735,7 @@ var package_default = {
|
|
|
725
735
|
node: ">=22.0.0"
|
|
726
736
|
},
|
|
727
737
|
dependencies: {
|
|
728
|
-
"@ashdev/codex-plugin-sdk": "^1.
|
|
738
|
+
"@ashdev/codex-plugin-sdk": "^1.16.0"
|
|
729
739
|
},
|
|
730
740
|
devDependencies: {
|
|
731
741
|
"@biomejs/biome": "^2.4.4",
|
|
@@ -774,6 +784,41 @@ var manifest = {
|
|
|
774
784
|
type: "boolean",
|
|
775
785
|
required: false,
|
|
776
786
|
default: true
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
key: "allowedCountries",
|
|
790
|
+
label: "Country of Origin Filter",
|
|
791
|
+
description: 'Comma-separated ISO country codes to include (e.g. "JP" for manga, "KR" for manhwa, "CN" for manhua). Leave empty for no filter.',
|
|
792
|
+
type: "string",
|
|
793
|
+
required: false,
|
|
794
|
+
default: "",
|
|
795
|
+
example: "JP,KR"
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
key: "excludedGenres",
|
|
799
|
+
label: "Excluded Genres",
|
|
800
|
+
description: 'Comma-separated genres to exclude from recommendations (e.g. "Hentai,Ecchi").',
|
|
801
|
+
type: "string",
|
|
802
|
+
required: false,
|
|
803
|
+
default: "",
|
|
804
|
+
example: "Hentai"
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
key: "excludedFormats",
|
|
808
|
+
label: "Excluded Formats",
|
|
809
|
+
description: 'Comma-separated formats to exclude (e.g. "NOVEL,ONE_SHOT"). Valid values: MANGA, NOVEL, ONE_SHOT.',
|
|
810
|
+
type: "string",
|
|
811
|
+
required: false,
|
|
812
|
+
default: "",
|
|
813
|
+
example: "NOVEL"
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
key: "minAniListScore",
|
|
817
|
+
label: "Minimum AniList Score",
|
|
818
|
+
description: "Minimum average score (0-100) on AniList to include a recommendation. Set to 0 to disable.",
|
|
819
|
+
type: "number",
|
|
820
|
+
required: false,
|
|
821
|
+
default: 0
|
|
777
822
|
}
|
|
778
823
|
]
|
|
779
824
|
},
|
|
@@ -790,9 +835,28 @@ var manifest = {
|
|
|
790
835
|
|
|
791
836
|
// src/index.ts
|
|
792
837
|
var logger = createLogger({ name: "recommendations-anilist", level: "debug" });
|
|
838
|
+
var DEFAULT_FILTERS = {
|
|
839
|
+
allowedCountries: /* @__PURE__ */ new Set(),
|
|
840
|
+
excludedGenres: /* @__PURE__ */ new Set(),
|
|
841
|
+
excludedFormats: /* @__PURE__ */ new Set(),
|
|
842
|
+
minAniListScore: 0
|
|
843
|
+
};
|
|
844
|
+
function parseCommaSet(value) {
|
|
845
|
+
if (typeof value !== "string" || value.trim() === "") return /* @__PURE__ */ new Set();
|
|
846
|
+
return new Set(
|
|
847
|
+
value.split(",").map((s) => s.trim().toUpperCase()).filter((s) => s.length > 0)
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
function parseCommaSetPreserveCase(value) {
|
|
851
|
+
if (typeof value !== "string" || value.trim() === "") return /* @__PURE__ */ new Set();
|
|
852
|
+
return new Set(
|
|
853
|
+
value.split(",").map((s) => s.trim()).filter((s) => s.length > 0)
|
|
854
|
+
);
|
|
855
|
+
}
|
|
793
856
|
var client = null;
|
|
794
857
|
var viewerId = null;
|
|
795
858
|
var searchFallback = true;
|
|
859
|
+
var filters = { ...DEFAULT_FILTERS };
|
|
796
860
|
var storage = null;
|
|
797
861
|
function setClient(c) {
|
|
798
862
|
client = c;
|
|
@@ -800,6 +864,17 @@ function setClient(c) {
|
|
|
800
864
|
function setSearchFallback(enabled) {
|
|
801
865
|
searchFallback = enabled;
|
|
802
866
|
}
|
|
867
|
+
function setFilters(f) {
|
|
868
|
+
filters = f;
|
|
869
|
+
}
|
|
870
|
+
function resetFilters() {
|
|
871
|
+
filters = {
|
|
872
|
+
allowedCountries: /* @__PURE__ */ new Set(),
|
|
873
|
+
excludedGenres: /* @__PURE__ */ new Set(),
|
|
874
|
+
excludedFormats: /* @__PURE__ */ new Set(),
|
|
875
|
+
minAniListScore: 0
|
|
876
|
+
};
|
|
877
|
+
}
|
|
803
878
|
var DISMISSED_STORAGE_KEY = "dismissed_ids";
|
|
804
879
|
var dismissedIds = /* @__PURE__ */ new Set();
|
|
805
880
|
async function loadDismissedIds() {
|
|
@@ -884,6 +959,19 @@ function convertRecommendations(nodes, basedOnTitle, userMangaIds, excludeIds) {
|
|
|
884
959
|
const media = node.mediaRecommendation;
|
|
885
960
|
const externalId = String(media.id);
|
|
886
961
|
if (excludeIds.has(externalId) || dismissedIds.has(externalId)) continue;
|
|
962
|
+
if (filters.allowedCountries.size > 0 && (!media.countryOfOrigin || !filters.allowedCountries.has(media.countryOfOrigin.toUpperCase()))) {
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
if (filters.excludedFormats.size > 0 && media.format && filters.excludedFormats.has(media.format.toUpperCase())) {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
if (filters.excludedGenres.size > 0 && media.genres) {
|
|
969
|
+
const hasExcludedGenre = media.genres.some((g) => filters.excludedGenres.has(g));
|
|
970
|
+
if (hasExcludedGenre) continue;
|
|
971
|
+
}
|
|
972
|
+
if (filters.minAniListScore > 0 && (media.averageScore ?? 0) < filters.minAniListScore) {
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
887
975
|
const inLibrary = userMangaIds.has(media.id);
|
|
888
976
|
const communityScore = Math.max(0, Math.min(node.rating, 100)) / 100;
|
|
889
977
|
const avgScore = media.averageScore ? media.averageScore / 100 : 0.5;
|
|
@@ -897,11 +985,15 @@ function convertRecommendations(nodes, basedOnTitle, userMangaIds, excludeIds) {
|
|
|
897
985
|
coverUrl: media.coverImage.large ?? void 0,
|
|
898
986
|
summary: stripHtml(media.description),
|
|
899
987
|
genres: media.genres ?? [],
|
|
988
|
+
tags: media.tags?.map((t) => ({ name: t.name, rank: t.rank, category: t.category })),
|
|
900
989
|
score: Math.max(0, Math.min(score, 1)),
|
|
901
990
|
reason: `Recommended because you liked ${basedOnTitle}`,
|
|
902
991
|
basedOn: [basedOnTitle],
|
|
903
992
|
inLibrary,
|
|
904
993
|
status,
|
|
994
|
+
format: media.format ?? void 0,
|
|
995
|
+
countryOfOrigin: media.countryOfOrigin ?? void 0,
|
|
996
|
+
startYear: media.startDate?.year ?? void 0,
|
|
905
997
|
totalBookCount,
|
|
906
998
|
rating: media.averageScore ?? void 0,
|
|
907
999
|
popularity: media.popularity ?? void 0
|
|
@@ -919,7 +1011,7 @@ var provider = {
|
|
|
919
1011
|
logger.info(`Authenticated as viewer ${viewerId}`);
|
|
920
1012
|
}
|
|
921
1013
|
const { library, limit, excludeIds: rawExcludeIds = [] } = params;
|
|
922
|
-
const effectiveLimit = Math.min(limit ?? 20,
|
|
1014
|
+
const effectiveLimit = Math.min(limit ?? 20, 100);
|
|
923
1015
|
const excludeIds = new Set(rawExcludeIds);
|
|
924
1016
|
if (!library || library.length === 0) {
|
|
925
1017
|
logger.info("Empty library \u2014 returning no recommendations");
|
|
@@ -996,6 +1088,25 @@ createRecommendationPlugin({
|
|
|
996
1088
|
searchFallback = uc.searchFallback;
|
|
997
1089
|
logger.info(`Search fallback set to: ${searchFallback}`);
|
|
998
1090
|
}
|
|
1091
|
+
if (uc) {
|
|
1092
|
+
filters = {
|
|
1093
|
+
allowedCountries: parseCommaSet(uc.allowedCountries),
|
|
1094
|
+
excludedGenres: parseCommaSetPreserveCase(uc.excludedGenres),
|
|
1095
|
+
excludedFormats: parseCommaSet(uc.excludedFormats),
|
|
1096
|
+
minAniListScore: typeof uc.minAniListScore === "number" ? Math.max(0, Math.min(uc.minAniListScore, 100)) : 0
|
|
1097
|
+
};
|
|
1098
|
+
const activeFilters = [];
|
|
1099
|
+
if (filters.allowedCountries.size > 0)
|
|
1100
|
+
activeFilters.push(`countries=[${[...filters.allowedCountries].join(",")}]`);
|
|
1101
|
+
if (filters.excludedGenres.size > 0)
|
|
1102
|
+
activeFilters.push(`excludedGenres=[${[...filters.excludedGenres].join(",")}]`);
|
|
1103
|
+
if (filters.excludedFormats.size > 0)
|
|
1104
|
+
activeFilters.push(`excludedFormats=[${[...filters.excludedFormats].join(",")}]`);
|
|
1105
|
+
if (filters.minAniListScore > 0) activeFilters.push(`minScore=${filters.minAniListScore}`);
|
|
1106
|
+
if (activeFilters.length > 0) {
|
|
1107
|
+
logger.info(`Recommendation filters: ${activeFilters.join(", ")}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
999
1110
|
storage = params.storage;
|
|
1000
1111
|
await loadDismissedIds();
|
|
1001
1112
|
}
|
|
@@ -1005,8 +1116,10 @@ export {
|
|
|
1005
1116
|
convertRecommendations,
|
|
1006
1117
|
dismissedIds,
|
|
1007
1118
|
mapAniListStatus,
|
|
1119
|
+
resetFilters,
|
|
1008
1120
|
resolveAniListIds,
|
|
1009
1121
|
setClient,
|
|
1122
|
+
setFilters,
|
|
1010
1123
|
setSearchFallback
|
|
1011
1124
|
};
|
|
1012
1125
|
//# sourceMappingURL=index.js.map
|
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.15.1\",\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.15.1\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.4.4\",\n \"@types/node\": \"^22.0.0\",\n \"esbuild\": \"^0.27.3\",\n \"typescript\": \"^5.9.3\",\n \"vitest\": \"^4.0.18\"\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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",
|
|
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 tags {\n name\n rank\n category\n }\n averageScore\n popularity\n siteUrl\n status\n format\n countryOfOrigin\n startDate {\n year\n }\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\n/** AniList media format values (manga-relevant subset) */\nexport type AniListMediaFormat = \"MANGA\" | \"NOVEL\" | \"ONE_SHOT\";\n\n/** AniList tag on a media entry */\nexport interface AniListTag {\n name: string;\n rank: number;\n category: string;\n}\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 tags: AniListTag[];\n averageScore: number | null;\n popularity: number | null;\n siteUrl: string;\n status: AniListMediaStatus | null;\n format: AniListMediaFormat | null;\n countryOfOrigin: string | null;\n startDate: { year: number | null } | 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 = 5,\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.16.0\",\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.16.0\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.4.4\",\n \"@types/node\": \"^22.0.0\",\n \"esbuild\": \"^0.27.3\",\n \"typescript\": \"^5.9.3\",\n \"vitest\": \"^4.0.18\"\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 key: \"allowedCountries\",\n label: \"Country of Origin Filter\",\n description:\n 'Comma-separated ISO country codes to include (e.g. \"JP\" for manga, \"KR\" for manhwa, \"CN\" for manhua). Leave empty for no filter.',\n type: \"string\" as const,\n required: false,\n default: \"\",\n example: \"JP,KR\",\n },\n {\n key: \"excludedGenres\",\n label: \"Excluded Genres\",\n description:\n 'Comma-separated genres to exclude from recommendations (e.g. \"Hentai,Ecchi\").',\n type: \"string\" as const,\n required: false,\n default: \"\",\n example: \"Hentai\",\n },\n {\n key: \"excludedFormats\",\n label: \"Excluded Formats\",\n description:\n 'Comma-separated formats to exclude (e.g. \"NOVEL,ONE_SHOT\"). Valid values: MANGA, NOVEL, ONE_SHOT.',\n type: \"string\" as const,\n required: false,\n default: \"\",\n example: \"NOVEL\",\n },\n {\n key: \"minAniListScore\",\n label: \"Minimum AniList Score\",\n description:\n \"Minimum average score (0-100) on AniList to include a recommendation. Set to 0 to disable.\",\n type: \"number\" as const,\n required: false,\n default: 0,\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// =============================================================================\n// Filter Configuration\n// =============================================================================\n\n/** Plugin-side filters applied during recommendation generation */\nexport interface RecommendationFilters {\n /** Allowed country codes (empty = no filter) */\n allowedCountries: Set<string>;\n /** Genres to exclude */\n excludedGenres: Set<string>;\n /** Formats to exclude (e.g. \"NOVEL\", \"ONE_SHOT\") */\n excludedFormats: Set<string>;\n /** Minimum AniList average score (0-100, 0 = disabled) */\n minAniListScore: number;\n}\n\nconst DEFAULT_FILTERS: RecommendationFilters = {\n allowedCountries: new Set(),\n excludedGenres: new Set(),\n excludedFormats: new Set(),\n minAniListScore: 0,\n};\n\n/** Parse a comma-separated string into a Set of trimmed, uppercased values */\nfunction parseCommaSet(value: unknown): Set<string> {\n if (typeof value !== \"string\" || value.trim() === \"\") return new Set();\n return new Set(\n value\n .split(\",\")\n .map((s) => s.trim().toUpperCase())\n .filter((s) => s.length > 0),\n );\n}\n\n/** Parse a comma-separated string into a Set of trimmed values (case-preserved) */\nfunction parseCommaSetPreserveCase(value: unknown): Set<string> {\n if (typeof value !== \"string\" || value.trim() === \"\") return new Set();\n return new Set(\n value\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0),\n );\n}\n\n// Plugin state (set during initialization)\nlet client: AniListRecommendationClient | null = null;\nlet viewerId: number | null = null;\nlet searchFallback = true;\nlet filters: RecommendationFilters = { ...DEFAULT_FILTERS };\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/** Set the recommendation filters (exported for testing) */\nexport function setFilters(f: RecommendationFilters): void {\n filters = f;\n}\n\n/** Reset filters to defaults (exported for testing) */\nexport function resetFilters(): void {\n filters = {\n allowedCountries: new Set(),\n excludedGenres: new Set(),\n excludedFormats: new Set(),\n minAniListScore: 0,\n };\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 * Applies plugin-side filters (from user config) to exclude unwanted results.\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 // Apply plugin-side filters from user config\n if (\n filters.allowedCountries.size > 0 &&\n (!media.countryOfOrigin || !filters.allowedCountries.has(media.countryOfOrigin.toUpperCase()))\n ) {\n continue;\n }\n\n if (\n filters.excludedFormats.size > 0 &&\n media.format &&\n filters.excludedFormats.has(media.format.toUpperCase())\n ) {\n continue;\n }\n\n if (filters.excludedGenres.size > 0 && media.genres) {\n const hasExcludedGenre = media.genres.some((g) => filters.excludedGenres.has(g));\n if (hasExcludedGenre) continue;\n }\n\n if (filters.minAniListScore > 0 && (media.averageScore ?? 0) < filters.minAniListScore) {\n continue;\n }\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 tags: media.tags?.map((t) => ({ name: t.name, rank: t.rank, category: t.category })),\n score: Math.max(0, Math.min(score, 1)),\n reason: `Recommended because you liked ${basedOnTitle}`,\n basedOn: [basedOnTitle],\n inLibrary,\n status,\n format: media.format ?? undefined,\n countryOfOrigin: media.countryOfOrigin ?? undefined,\n startYear: media.startDate?.year ?? undefined,\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, 100);\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 // Read recommendation filters from userConfig\n if (uc) {\n filters = {\n allowedCountries: parseCommaSet(uc.allowedCountries),\n excludedGenres: parseCommaSetPreserveCase(uc.excludedGenres),\n excludedFormats: parseCommaSet(uc.excludedFormats),\n minAniListScore:\n typeof uc.minAniListScore === \"number\"\n ? Math.max(0, Math.min(uc.minAniListScore, 100))\n : 0,\n };\n const activeFilters: string[] = [];\n if (filters.allowedCountries.size > 0)\n activeFilters.push(`countries=[${[...filters.allowedCountries].join(\",\")}]`);\n if (filters.excludedGenres.size > 0)\n activeFilters.push(`excludedGenres=[${[...filters.excludedGenres].join(\",\")}]`);\n if (filters.excludedFormats.size > 0)\n activeFilters.push(`excludedFormats=[${[...filters.excludedFormats].join(\",\")}]`);\n if (filters.minAniListScore > 0) activeFilters.push(`minScore=${filters.minAniListScore}`);\n if (activeFilters.length > 0) {\n logger.info(`Recommendation filters: ${activeFilters.join(\", \")}`);\n }\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
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.
|
|
3
|
+
"version": "1.16.0",
|
|
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.
|
|
42
|
+
"@ashdev/codex-plugin-sdk": "^1.16.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@biomejs/biome": "^2.4.4",
|