anilist-mcp
Version:
AniList MCP server for accessing AniList API data
120 lines (119 loc) • 4.27 kB
JavaScript
/**
* Utility functions to filter media responses from AniList API
* to reduce token usage and prevent crashes from large JSON payloads
*/
/**
* Filters a single media object to include only essential fields
* Removes large arrays like character lists, staff lists, relations, recommendations
*/
function filterSingleMedia(media) {
// Create a filtered object with only essential fields
const filtered = {
id: media.id,
idMal: media.idMal,
title: media.title,
format: media.format,
status: media.status,
description: media.description,
startDate: media.startDate,
endDate: media.endDate,
countryOfOrigin: media.countryOfOrigin,
isLicensed: media.isLicensed,
hashtag: media.hashtag,
updatedAt: media.updatedAt,
coverImage: media.coverImage,
bannerImage: media.bannerImage,
genres: media.genres,
synonyms: media.synonyms,
averageScore: media.averageScore,
meanScore: media.meanScore,
popularity: media.popularity,
favourites: media.favourites,
isAdult: media.isAdult,
siteUrl: media.siteUrl,
};
// Add anime-specific fields (type guard for AnimeEntry)
if ("episodes" in media && media.episodes !== undefined) {
filtered.episodes = media.episodes;
filtered.season = media.season;
filtered.seasonYear = media.seasonYear;
filtered.duration = media.duration;
filtered.source = media.source;
}
// Add manga-specific fields (type guard for MangaEntry)
if ("chapters" in media && media.chapters !== undefined) {
filtered.chapters = media.chapters;
filtered.volumes = media.volumes;
}
// Include nextAiringEpisode for anime
if (media.nextAiringEpisode && Array.isArray(media.nextAiringEpisode)) {
filtered.nextAiringEpisode = media.nextAiringEpisode
.slice(0, 1) // Only include the next airing episode
.map((airing) => ({
airingAt: airing.airingAt,
timeUntilAiring: airing.timeUntilAiring,
episode: airing.episode,
}));
}
// Include limited tags (top 5 by relevance)
if (media.tags && Array.isArray(media.tags)) {
filtered.tags = media.tags.slice(0, 5).map((tag) => ({
id: tag.id,
name: tag.name,
isMediaSpoiler: tag.isMediaSpoiler,
}));
}
// Include limited studios (top 3 main studios)
if (media.studios && Array.isArray(media.studios)) {
filtered.studios = media.studios
.slice(0, 3)
.map((studio) => ({
id: studio.id,
name: studio.name,
isAnimationStudio: studio.isAnimationStudio,
}));
}
// Include external links (limited to top 5)
if (media.externalLinks && Array.isArray(media.externalLinks)) {
filtered.externalLinks = media.externalLinks.slice(0, 5);
}
// Include limited streaming episodes (top 3)
if (media.streamingEpisodes && Array.isArray(media.streamingEpisodes)) {
filtered.streamingEpisodes = media.streamingEpisodes
.slice(0, 3)
.map((ep) => ({
title: ep.title,
url: ep.url,
}));
}
// Include top 3 rankings
if (media.rankings && Array.isArray(media.rankings)) {
filtered.rankings = media.rankings
.slice(0, 3)
.map((ranking) => ({
rank: ranking.rank,
type: ranking.type,
context: ranking.context,
year: ranking.year,
season: ranking.season,
}));
}
// Remove undefined/null values to reduce payload size
Object.keys(filtered).forEach((key) => {
const typedKey = key;
if (filtered[typedKey] === undefined || filtered[typedKey] === null) {
delete filtered[typedKey];
}
});
return filtered;
}
/**
* Filters media response(s) to include only essential fields
* Handles both single media objects and arrays of media
*/
export function filterMedia(media) {
if (Array.isArray(media)) {
return media.map((m) => filterSingleMedia(m));
}
return filterSingleMedia(media);
}