aiostreams / packages /core /src /utils /id-parser.ts
f4b404's picture
Upload folder using huggingface_hub
f55c62a verified
Raw
History Blame Contribute Delete
5.54 kB
export const ID_TYPES = [
'animePlanetId',
'animecountdownId',
'anidbId',
'anilistId',
'anisearchId',
'imdbId',
'kitsuId',
'livechartId',
'malId',
'notifyMoeId',
'simklId',
'themoviedbId',
'thetvdbId',
'traktId',
] as const;
export type IdType = (typeof ID_TYPES)[number];
export type ExternalIdType =
| 'anime-planet_id'
| 'animecountdown_id'
| 'anidb_id'
| 'anilist_id'
| 'anisearch_id'
| 'imdb_id'
| 'kitsu_id'
| 'livechart_id'
| 'mal_id'
| 'notify.moe_id'
| 'simkl_id'
| 'themoviedb_id'
| 'thetvdb_id';
export interface ParsedId {
type: IdType;
value: string | number;
fullId: string;
externalType: ExternalIdType;
mediaType: string;
season?: string;
episode?: string;
generator: (
value: string | number,
season?: string,
episode?: string
) => string;
}
interface IdParserDefinition {
type: IdType;
externalType: ExternalIdType;
prefixes: string[];
// Regex should have named capture groups:
// - id: the base ID (required)
// - season: the season number (optional)
// - episode: the episode number (optional)
regex: RegExp;
format: (id: string) => string | number;
generator: (
value: string | number,
season?: string,
episode?: string
) => string;
}
export class IdParser {
private static readonly ID_PARSERS: IdParserDefinition[] = [
{
type: 'imdbId',
externalType: 'imdb_id',
prefixes: ['tt', 'imdb'],
regex: /^(?:tt|imdb)[:-]?(?<id>\d+)(?::(?<season>\d+):(?<episode>\d+))?$/,
format: (id) => `tt${id}`,
generator: (value, season, episode) => `${value}:${season}:${episode}`,
},
{
type: 'malId',
externalType: 'mal_id',
prefixes: ['mal'],
regex: /^mal[:-]?(?<id>\d+)(?::(?<episode>\d+))?$/,
format: (id) => Number(id),
generator: (value, season, episode) => `mal:${value}:${episode}`,
},
{
type: 'thetvdbId',
externalType: 'thetvdb_id',
prefixes: ['tvdb'],
regex: /^tvdb[:-]?(?<id>\d+)(?::(?<season>\d+):(?<episode>\d+))?$/,
format: (id) => Number(id),
generator: (value, season, episode) =>
`tvdb:${value}:${season}:${episode}`,
},
{
type: 'themoviedbId',
externalType: 'themoviedb_id',
prefixes: ['tmdb'],
regex: /^tmdb[:-]?(?<id>\d+)(?::(?<season>\d+):(?<episode>\d+))?$/,
format: (id) => Number(id),
generator: (value, season, episode) =>
`tmdb:${value}:${season}:${episode}`,
},
{
type: 'kitsuId',
externalType: 'kitsu_id',
prefixes: ['kitsu'],
regex: /^kitsu[:-]?(?<id>\d+)(?::(?<episode>\d+))?$/,
format: (id) => Number(id),
generator: (value, season, episode) => `kitsu:${value}:${episode}`,
},
{
type: 'anilistId',
externalType: 'anilist_id',
prefixes: ['anilist'],
regex: /^anilist[:-]?(?<id>\d+)(?::(?<episode>\d+))?$/,
format: (id) => Number(id),
generator: (value, season, episode) => `anilist:${value}:${episode}`,
},
{
type: 'anidbId',
externalType: 'anidb_id',
prefixes: ['anidb', 'anidb_id', 'anidbid'],
regex: /^(?:anidb|anidb_id|anidbid)[:-]?(?<id>\d+)(?::(?<episode>\d+))?$/,
format: (id) => Number(id),
generator: (value, season, episode) => `anidb:${value}:${episode}`,
},
{
type: 'animePlanetId',
externalType: 'anime-planet_id',
prefixes: ['animeplanet', 'ap'],
regex: /^(?:animeplanet|ap)[:-]?(?<id>\d+)$/,
format: (id) => Number(id),
generator: (value, season, episode) => `animeplanet:${value}:${episode}`,
},
{
type: 'animecountdownId',
externalType: 'animecountdown_id',
prefixes: ['acd'],
regex: /^acd[:-]?(?<id>\d+)$/,
format: (id) => Number(id),
generator: (value, season, episode) => `acd:${value}:${episode}`,
},
{
type: 'anisearchId',
externalType: 'anisearch_id',
prefixes: ['anisearch'],
regex: /^anisearch[:-]?(?<id>\d+)$/,
format: (id) => Number(id),
generator: (value, season, episode) => `anisearch:${value}:${episode}`,
},
{
type: 'notifyMoeId',
externalType: 'notify.moe_id',
prefixes: ['notifymoe', 'nm'],
regex: /^(?:notifymoe|nm)[:-]?(?<id>[a-zA-Z0-9]+)$/,
format: (id) => id,
generator: (value, season, episode) => `notifymoe:${value}:${episode}`,
},
{
type: 'simklId',
externalType: 'simkl_id',
prefixes: ['simkl'],
regex: /^simkl[:-]?(?<id>\d+)$/,
format: (id) => Number(id),
generator: (value, season, episode) => `simkl:${value}:${episode}`,
},
];
constructor() {}
public static getPrefixes(types: IdType[]): string[] {
return IdParser.ID_PARSERS.filter((p) => types.includes(p.type)).flatMap(
(p) => p.prefixes
);
}
public static parse(stremioId: string, mediaType: string): ParsedId | null {
for (const parser of IdParser.ID_PARSERS) {
const match = stremioId.match(parser.regex);
if (match?.groups) {
const { id, season, episode } = match.groups;
const parsedId: ParsedId = {
type: parser.type,
value: parser.format(id),
fullId: stremioId,
externalType: parser.externalType,
mediaType,
generator: parser.generator,
};
if (season) parsedId.season = season;
if (episode) parsedId.episode = episode;
return parsedId;
}
}
return null;
}
}