@media-engine/providers 1.8.0 → 1.9.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/README.ru.md CHANGED
@@ -1,164 +1,178 @@
1
- # @media-engine/providers
2
-
3
- [English](https://github.com/Yaneart/media-engine/blob/main/packages/providers/README.md) | **Русский**
4
-
5
- Это готовые источники данных для Media Engine. Установите пакет, если хотите искать в реальных
6
- публичных каталогах и не писать адаптеры самостоятельно.
7
-
8
- ## Установка
9
-
10
- ```bash
11
- npm install @media-engine/core @media-engine/providers
12
- ```
13
-
14
- Для встроенных провайдеров ваши API-ключи не нужны.
15
-
16
- ## Поиск метаданных
17
-
18
- ```ts
19
- import { MediaEngine } from "@media-engine/core";
20
- import {
21
- aniListProvider,
22
- cinemetaProvider,
23
- kinobdProvider,
24
- shikimoriProvider,
25
- } from "@media-engine/providers";
26
-
27
- const media = new MediaEngine({
28
- providers: [
29
- kinobdProvider(),
30
- cinemetaProvider(),
31
- shikimoriProvider(),
32
- aniListProvider(),
33
- ],
34
- });
35
-
36
- const result = await media.search({ title: "Ван-Пис" });
37
- ```
38
-
39
- Подключайте только те источники, которые нужны вашему приложению. Движок сам вызовет подходящие
40
- провайдеры и объединит совпадающие результаты.
41
-
42
- Shikimori и AniList также поддерживают явные связи между аниме:
43
-
44
- ```ts
45
- const relations = await media.getRelatedMedia({
46
- shikimori: "52991",
47
- type: "anime",
48
- });
49
- ```
50
-
51
- Ответ сохраняет официальные типы связей — приквел, сиквел, побочную историю и другие — без
52
- группировки по эвристикам названия франшизы.
53
-
54
- Готовые провайдеры метаданных:
55
-
56
- - `kinobdProvider()` и `cinemetaProvider()` для фильмов и сериалов;
57
- - `shikimoriProvider()` и `aniListProvider()` для аниме;
58
- - `tvMazeProvider()` и `wikidataProvider()` как дополнительные источники идентичности;
59
- - `imdbDatasetProvider()` для IMDb-датасета, которым управляет ваше приложение.
60
-
61
- Данные TVmaze требуют указания источника. Сохраняйте и показывайте ссылку TVmaze, которая приходит
62
- в результате. Подробности есть в [лицензии API TVmaze](https://www.tvmaze.com/api#licensing).
63
-
64
- Опциональные инструменты для SQLite-индекса IMDb требуют Node.js 22.13 или новее. Для остального
65
- пакета достаточно Node.js 20.
66
-
67
- ## Поиск вариантов просмотра
68
-
69
- ```ts
70
- import {
71
- flixHqStreamingProvider,
72
- kinobdStreamingProvider,
73
- } from "@media-engine/providers";
74
-
75
- const media = new MediaEngine({
76
- streamingProviders: [kinobdStreamingProvider(), flixHqStreamingProvider()],
77
- });
78
-
79
- const result = await media.getAvailability({
80
- type: "series",
81
- title: "Игра престолов",
82
- seasonNumber: 1,
83
- episodeNumber: 1,
84
- });
85
-
86
- for await (const snapshot of media.getAvailabilityProgressively({
87
- type: "series",
88
- ids: { kinopoisk: "435" },
89
- seasonNumber: 1,
90
- episodeNumber: 1,
91
- })) {
92
- updatePlayerSources(snapshot.availability?.options ?? []);
93
- }
94
- ```
95
-
96
- Пакет также экспортирует `ddbbStreamingProvider()`, `aniLibertyStreamingProvider()`,
97
- `filmixStreamingProvider()`, `veoVeoStreamingProvider()`, `videoHubStreamingProvider()`,
98
- `rutubeStreamingProvider()` и
99
- `experimentalStreamingProvider()`.
100
- Подключайте их явно, если они
101
- подходят вашему приложению. Гостевой режим Filmix ограничен 480p и отбрасывает известные заглушки о
102
- блокировке и служебные видео. Собственный device token повышает предел до 720p. Для авторизации
103
- обязателен HTTPS `baseUrl`, если приложение явно не включило `allowInsecureHttpToken` для локальной
104
- проверки совместимости. Этот режим отправляет токен без TLS и не должен использоваться в публичном
105
- развёртывании. Ссылки на видео CDN используют HTTPS. VeoVeo использует DDBB только для получения публичного
106
- content ID, отбрасывает iframe-токен и возвращает прямой подписанный HTTPS HLS.
107
- VideoHUB ищет по ID Кинопоиска и одним запросом playlist возвращает каталог сезонов и серий аниме.
108
- Для воспроизведения сериала или аниме нужна точная серия: аниме можно запросить по абсолютному номеру
109
- либо по паре сезон/серия; ответ сохраняет обе идентичности. Он возвращает
110
- короткоживущие прямые MP4 в нескольких качествах. Ссылки привязаны к User-Agent проигрывающего
111
- клиента и могут быть привязаны к внешнему IP. Передавайте точный User-Agent клиента через
112
- `MediaEngineOperationOptions.playbackUserAgent`; для небраузерных клиентов нужное значение также
113
- сохраняется в `access.headers` каждого варианта.
114
- VideoHUB поддерживает прогрессивную доступность: каждый готовый перевод можно показать, не ожидая
115
- более медленных lookup. Ограниченный warm-cache раздельно хранит плейлисты и ещё действующие
116
- подписанные video resolutions, никогда не выдаёт подписанные ссылки stale и сохраняет их исходный
117
- срок действия.
118
- Rutube выполняет ограниченный точный поиск фильма по названию и году и возвращает только
119
- официальный публичный embed-плеер Rutube. Прямые media-URL не извлекаются и не проксируются;
120
- сериалы намеренно не поддерживаются.
121
-
122
- Эти провайдеры возвращают сторонние ссылки или потоки. Media Engine не хранит видео, а внешний
123
- плеер может быть недоступен в конкретной стране, сети или браузере.
124
-
125
- ## Поиск torrent-раздач
126
-
127
- Torrent-провайдеры всегда подключаются явно:
128
-
129
- ```ts
130
- import {
131
- bitsearchTorrentProvider,
132
- jacRedTorrentProvider,
133
- magnetzTorrentProvider,
134
- ytsTorrentProvider,
135
- } from "@media-engine/providers";
136
-
137
- const media = new MediaEngine({
138
- torrentProviders: [
139
- ytsTorrentProvider(),
140
- jacRedTorrentProvider(),
141
- bitsearchTorrentProvider(),
142
- magnetzTorrentProvider(),
143
- ],
144
- });
145
-
146
- const result = await media.discoverTorrents({
147
- type: "movie",
148
- title: "Начало",
149
- year: 2010,
150
- ids: { imdb: "tt1375666" },
151
- });
152
- ```
153
-
154
- Поиск только возвращает список кандидатов и данные для дальнейшей передачи. Он не загружает
155
- torrent metadata, не обращается к trackers, не подключается к раздаче и не воспроизводит файл.
156
-
157
- Публичные источники могут меняться, ограничивать запросы или временно не работать. Оставляйте кэш
158
- движка включённым и будьте готовы к частичным результатам. Настройки и границы безопасности каждого
159
- источника описаны в
160
- [документации провайдеров](https://github.com/Yaneart/media-engine/blob/main/docs/providers.md).
161
-
162
- ## Лицензия
163
-
164
- MIT
1
+ # @media-engine/providers
2
+
3
+ [English](https://github.com/Yaneart/media-engine/blob/main/packages/providers/README.md) | **Русский**
4
+
5
+ Это готовые источники данных для Media Engine. Установите пакет, если хотите искать в реальных
6
+ публичных каталогах и не писать адаптеры самостоятельно.
7
+
8
+ ## Установка
9
+
10
+ ```bash
11
+ npm install @media-engine/core @media-engine/providers
12
+ ```
13
+
14
+ Для встроенных провайдеров ваши API-ключи не нужны.
15
+
16
+ ## Поиск метаданных
17
+
18
+ ```ts
19
+ import { MediaEngine } from "@media-engine/core";
20
+ import {
21
+ aniListProvider,
22
+ cinemetaProvider,
23
+ kinobdProvider,
24
+ tmdbProvider,
25
+ shikimoriProvider,
26
+ } from "@media-engine/providers";
27
+
28
+ const media = new MediaEngine({
29
+ providers: [
30
+ kinobdProvider(),
31
+ tmdbProvider(),
32
+ cinemetaProvider(),
33
+ shikimoriProvider(),
34
+ aniListProvider(),
35
+ ],
36
+ });
37
+
38
+ const result = await media.search({ title: "Ван-Пис" });
39
+ ```
40
+
41
+ Подключайте только те источники, которые нужны вашему приложению. Движок сам вызовет подходящие
42
+ провайдеры и объединит совпадающие результаты.
43
+
44
+ Shikimori и AniList также поддерживают явные связи между аниме:
45
+
46
+ В деталях Shikimori первый доступный полноразмерный кадр нормализуется как горизонтальный
47
+ `backdrop`, а остальные кадры сохраняются как `still`. Более широкий баннер AniList остаётся
48
+ резервным вариантом.
49
+ Для объединённых постеров аниме Core предпочитает более качественный `coverImage.extraLarge` из
50
+ AniList, если провайдеры не передали размеры изображений.
51
+
52
+ ```ts
53
+ const relations = await media.getRelatedMedia({
54
+ shikimori: "52991",
55
+ type: "anime",
56
+ });
57
+ ```
58
+
59
+ Ответ сохраняет официальные типы связей — приквел, сиквел, побочную историю и другие — без
60
+ группировки по эвристикам названия франшизы.
61
+
62
+ Готовые провайдеры метаданных:
63
+
64
+ - `tmdbProvider()` для локализованных данных фильмов и сериалов через публичный Stremio addon;
65
+ - `kinobdProvider()` и `cinemetaProvider()` как независимые источники фильмов и сериалов;
66
+ - `shikimoriProvider()` и `aniListProvider()` для аниме;
67
+ - `tvMazeProvider()` и `wikidataProvider()` как дополнительные источники идентичности;
68
+ - `imdbDatasetProvider()` для IMDb-датасета, которым управляет ваше приложение.
69
+
70
+ Данные TVmaze требуют указания источника. Сохраняйте и показывайте ссылку TVmaze, которая приходит
71
+ в результате. Подробности есть в [лицензии API TVmaze](https://www.tvmaze.com/api#licensing).
72
+
73
+ `tmdbProvider()` использует публичный [TMDB Stremio addon](https://github.com/mrcanelas/tmdb-addon)
74
+ и не требует пользовательского токена. Адрес сервиса можно заменить через `baseUrl`; при его сбое
75
+ Cinemeta продолжает работать. Приложению, показывающему данные TMDB, нужно соблюдать
76
+ [требования к указанию источника](https://developer.themoviedb.org/docs/faq).
77
+
78
+ Опциональные инструменты для SQLite-индекса IMDb требуют Node.js 22.13 или новее. Для остального
79
+ пакета достаточно Node.js 20.
80
+
81
+ ## Поиск вариантов просмотра
82
+
83
+ ```ts
84
+ import {
85
+ flixHqStreamingProvider,
86
+ kinobdStreamingProvider,
87
+ } from "@media-engine/providers";
88
+
89
+ const media = new MediaEngine({
90
+ streamingProviders: [kinobdStreamingProvider(), flixHqStreamingProvider()],
91
+ });
92
+
93
+ const result = await media.getAvailability({
94
+ type: "series",
95
+ title: "Игра престолов",
96
+ seasonNumber: 1,
97
+ episodeNumber: 1,
98
+ });
99
+
100
+ for await (const snapshot of media.getAvailabilityProgressively({
101
+ type: "series",
102
+ ids: { kinopoisk: "435" },
103
+ seasonNumber: 1,
104
+ episodeNumber: 1,
105
+ })) {
106
+ updatePlayerSources(snapshot.availability?.options ?? []);
107
+ }
108
+ ```
109
+
110
+ Пакет также экспортирует `ddbbStreamingProvider()`, `aniLibertyStreamingProvider()`,
111
+ `filmixStreamingProvider()`, `veoVeoStreamingProvider()`, `videoHubStreamingProvider()`,
112
+ `rutubeStreamingProvider()` и
113
+ `experimentalStreamingProvider()`.
114
+ Подключайте их явно, если они
115
+ подходят вашему приложению. Гостевой режим Filmix ограничен 480p и отбрасывает известные заглушки о
116
+ блокировке и служебные видео. Собственный device token повышает предел до 720p. Для авторизации
117
+ обязателен HTTPS `baseUrl`, если приложение явно не включило `allowInsecureHttpToken` для локальной
118
+ проверки совместимости. Этот режим отправляет токен без TLS и не должен использоваться в публичном
119
+ развёртывании. Ссылки на видео CDN используют HTTPS. VeoVeo использует DDBB только для получения публичного
120
+ content ID, отбрасывает iframe-токен и возвращает прямой подписанный HTTPS HLS.
121
+ VideoHUB ищет по ID Кинопоиска и одним запросом playlist возвращает каталог сезонов и серий аниме.
122
+ Для воспроизведения сериала или аниме нужна точная серия: аниме можно запросить по абсолютному номеру
123
+ либо по паре сезон/серия; ответ сохраняет обе идентичности. Он возвращает
124
+ короткоживущие прямые MP4 в нескольких качествах. Ссылки привязаны к User-Agent проигрывающего
125
+ клиента и могут быть привязаны к внешнему IP. Передавайте точный User-Agent клиента через
126
+ `MediaEngineOperationOptions.playbackUserAgent`; для небраузерных клиентов нужное значение также
127
+ сохраняется в `access.headers` каждого варианта.
128
+ VideoHUB поддерживает прогрессивную доступность: каждый готовый перевод можно показать, не ожидая
129
+ более медленных lookup. Ограниченный warm-cache раздельно хранит плейлисты и ещё действующие
130
+ подписанные video resolutions, никогда не выдаёт подписанные ссылки stale и сохраняет их исходный
131
+ срок действия.
132
+ Rutube выполняет ограниченный точный поиск фильма по названию и году и возвращает только
133
+ официальный публичный embed-плеер Rutube. Прямые media-URL не извлекаются и не проксируются;
134
+ сериалы намеренно не поддерживаются.
135
+
136
+ Эти провайдеры возвращают сторонние ссылки или потоки. Media Engine не хранит видео, а внешний
137
+ плеер может быть недоступен в конкретной стране, сети или браузере.
138
+
139
+ ## Поиск torrent-раздач
140
+
141
+ Torrent-провайдеры всегда подключаются явно:
142
+
143
+ ```ts
144
+ import {
145
+ bitsearchTorrentProvider,
146
+ jacRedTorrentProvider,
147
+ magnetzTorrentProvider,
148
+ ytsTorrentProvider,
149
+ } from "@media-engine/providers";
150
+
151
+ const media = new MediaEngine({
152
+ torrentProviders: [
153
+ ytsTorrentProvider(),
154
+ jacRedTorrentProvider(),
155
+ bitsearchTorrentProvider(),
156
+ magnetzTorrentProvider(),
157
+ ],
158
+ });
159
+
160
+ const result = await media.discoverTorrents({
161
+ type: "movie",
162
+ title: "Начало",
163
+ year: 2010,
164
+ ids: { imdb: "tt1375666" },
165
+ });
166
+ ```
167
+
168
+ Поиск только возвращает список кандидатов и данные для дальнейшей передачи. Он не загружает
169
+ torrent metadata, не обращается к trackers, не подключается к раздаче и не воспроизводит файл.
170
+
171
+ Публичные источники могут меняться, ограничивать запросы или временно не работать. Оставляйте кэш
172
+ движка включённым и будьте готовы к частичным результатам. Настройки и границы безопасности каждого
173
+ источника описаны в
174
+ [документации провайдеров](https://github.com/Yaneart/media-engine/blob/main/docs/providers.md).
175
+
176
+ ## Лицензия
177
+
178
+ MIT
@@ -0,0 +1,8 @@
1
+ import { type StreamingProvider } from "@media-engine/core";
2
+ import { type ProviderFetch } from "../shared/index.js";
3
+ export interface AderomStreamingProviderOptions {
4
+ apiBaseUrl?: string;
5
+ embedBaseUrl?: string;
6
+ fetch?: ProviderFetch;
7
+ }
8
+ export declare function aderomStreamingProvider(options?: AderomStreamingProviderOptions): StreamingProvider;
@@ -0,0 +1,168 @@
1
+ import { ProviderError } from "@media-engine/core";
2
+ import { fetchJson, normalizeProviderOutputUrl } from "../shared/index.js";
3
+ import { createHardenedProviderFetch } from "../shared/safe-fetch.js";
4
+ import { MEDIA_ENGINE_DEFAULT_USER_AGENT } from "../package-version.js";
5
+ const KINOPOISK_ID = /^[1-9]\d{0,11}$/u;
6
+ const IMDB_ID = /^tt\d{7,9}$/u;
7
+ const WIKIDATA_API_URL = "https://www.wikidata.org/w/api.php";
8
+ // Aderom indexes dubbed series and anime. Its published iframe keeps voice and episode selection inside the player.
9
+ export function aderomStreamingProvider(options = {}) {
10
+ const name = "aderom-streaming";
11
+ const apiBaseUrl = readBaseUrl(options.apiBaseUrl ?? "https://aderom.net");
12
+ const embedBaseUrl = readBaseUrl(options.embedBaseUrl ?? "https://gencit.info");
13
+ const fetchImpl = options.fetch ?? createHardenedProviderFetch({ provider: name, maxRedirects: 3 });
14
+ return {
15
+ name,
16
+ kind: "streaming",
17
+ capabilities: {
18
+ mediaTypes: ["series", "anime"],
19
+ lookup: { byTitle: false, byExternalIds: ["kinopoisk", "imdb"], byEpisode: false },
20
+ features: ["embed", "translations"],
21
+ },
22
+ async getAvailability(query, context) {
23
+ if (query.providers && !query.providers.includes(name))
24
+ return null;
25
+ if (query.type !== "series" && query.type !== "anime")
26
+ return null;
27
+ if (query.seasonNumber !== undefined ||
28
+ query.episodeNumber !== undefined ||
29
+ query.absoluteEpisodeNumber !== undefined)
30
+ return null;
31
+ const kinopoiskId = query.ids?.kinopoisk ??
32
+ query.kinopoisk ??
33
+ (await resolveKinopoiskId(query.ids?.imdb ?? query.imdb, context, fetchImpl));
34
+ if (!kinopoiskId || !KINOPOISK_ID.test(kinopoiskId))
35
+ return null;
36
+ const sourceUrl = new URL(`/api/${kinopoiskId}`, apiBaseUrl).href;
37
+ const payload = await fetchJson({
38
+ provider: name,
39
+ url: sourceUrl,
40
+ context,
41
+ fetch: fetchImpl,
42
+ maxRetries: 0,
43
+ maxResponseBytes: 256 * 1024,
44
+ });
45
+ if (isRecord(payload) && payload.error === "not found")
46
+ return null;
47
+ if (!isRecord(payload) ||
48
+ String(payload.kinopoisk_id) !== kinopoiskId ||
49
+ typeof payload.title !== "string" ||
50
+ !payload.title.trim() ||
51
+ typeof payload.category !== "string" ||
52
+ !Array.isArray(payload.translation)) {
53
+ throw invalidResponse(name);
54
+ }
55
+ const category = payload.category.toLowerCase();
56
+ if (query.type === "anime" ? !category.includes("аниме") : !category.includes("сериал"))
57
+ return null;
58
+ if (!payload.translation.some((entry) => isRecord(entry) && typeof entry.name === "string" && entry.name.trim()))
59
+ return null;
60
+ const embedUrl = normalizeProviderOutputUrl(new URL(`/tvb/${kinopoiskId}`, embedBaseUrl).href);
61
+ if (!embedUrl)
62
+ throw invalidResponse(name);
63
+ const response = await fetchImpl(embedUrl, { signal: context.signal });
64
+ await response.body?.cancel();
65
+ if (response.status === 404)
66
+ return null;
67
+ if (!response.ok) {
68
+ throw new ProviderError({
69
+ provider: name,
70
+ code: "PROVIDER_UNAVAILABLE",
71
+ retryable: response.status >= 500,
72
+ message: `Aderom embed returned HTTP ${response.status}.`,
73
+ });
74
+ }
75
+ const ids = { ...query.ids, kinopoisk: kinopoiskId };
76
+ return {
77
+ query,
78
+ item: { type: query.type, title: payload.title.trim(), year: readYear(payload.year), ids },
79
+ options: [
80
+ {
81
+ id: `${name}:${kinopoiskId}`,
82
+ provider: name,
83
+ player: { kind: "embed", label: "Aderom", providerPlayerId: kinopoiskId },
84
+ access: { url: embedUrl },
85
+ availability: "available",
86
+ sourceUrl,
87
+ },
88
+ ],
89
+ sourceProviders: [{ provider: name, url: sourceUrl, ids }],
90
+ checkedAt: new Date().toISOString(),
91
+ };
92
+ },
93
+ };
94
+ }
95
+ async function resolveKinopoiskId(imdbId, context, fetchImpl) {
96
+ if (!imdbId || !IMDB_ID.test(imdbId))
97
+ return undefined;
98
+ const searchUrl = new URL(WIKIDATA_API_URL);
99
+ searchUrl.searchParams.set("action", "query");
100
+ searchUrl.searchParams.set("list", "search");
101
+ searchUrl.searchParams.set("srsearch", `haswbstatement:P345=${imdbId}`);
102
+ searchUrl.searchParams.set("srnamespace", "0");
103
+ searchUrl.searchParams.set("srlimit", "2");
104
+ searchUrl.searchParams.set("srprop", "");
105
+ searchUrl.searchParams.set("format", "json");
106
+ const search = await requestWikidata(searchUrl, context, fetchImpl);
107
+ if (!isRecord(search) || !isRecord(search.query) || !Array.isArray(search.query.search))
108
+ throw invalidResponse("aderom-streaming");
109
+ if (search.query.search.length !== 1)
110
+ return undefined;
111
+ const entity = search.query.search[0];
112
+ if (!isRecord(entity) || typeof entity.title !== "string" || !/^Q[1-9]\d*$/u.test(entity.title))
113
+ throw invalidResponse("aderom-streaming");
114
+ const claimsUrl = new URL(WIKIDATA_API_URL);
115
+ claimsUrl.searchParams.set("action", "wbgetclaims");
116
+ claimsUrl.searchParams.set("entity", entity.title);
117
+ claimsUrl.searchParams.set("property", "P2603");
118
+ claimsUrl.searchParams.set("format", "json");
119
+ const payload = await requestWikidata(claimsUrl, context, fetchImpl);
120
+ if (!isRecord(payload) || !isRecord(payload.claims))
121
+ throw invalidResponse("aderom-streaming");
122
+ if (payload.claims.P2603 === undefined)
123
+ return undefined;
124
+ if (!Array.isArray(payload.claims.P2603))
125
+ throw invalidResponse("aderom-streaming");
126
+ const ids = new Set(payload.claims.P2603.map((claim) => {
127
+ if (!isRecord(claim) || !isRecord(claim.mainsnak) || !isRecord(claim.mainsnak.datavalue))
128
+ return undefined;
129
+ return claim.mainsnak.datavalue.value;
130
+ }));
131
+ const id = [...ids][0];
132
+ return ids.size === 1 && typeof id === "string" && KINOPOISK_ID.test(id) ? id : undefined;
133
+ }
134
+ function requestWikidata(url, context, fetchImpl) {
135
+ return fetchJson({
136
+ provider: "aderom-streaming",
137
+ url: url.href,
138
+ init: {
139
+ headers: { accept: "application/json", "user-agent": MEDIA_ENGINE_DEFAULT_USER_AGENT },
140
+ },
141
+ context,
142
+ fetch: fetchImpl,
143
+ maxRetries: 0,
144
+ maxResponseBytes: 64 * 1024,
145
+ });
146
+ }
147
+ function readBaseUrl(value) {
148
+ const url = new URL(value);
149
+ if (url.protocol !== "https:" || url.username || url.password)
150
+ throw new TypeError("Aderom base URLs must be credential-free HTTPS URLs.");
151
+ return url.origin;
152
+ }
153
+ function isRecord(value) {
154
+ return typeof value === "object" && value !== null && !Array.isArray(value);
155
+ }
156
+ function readYear(value) {
157
+ const year = typeof value === "number" ? value : Number(value);
158
+ return Number.isInteger(year) && year >= 1888 && year <= 2200 ? year : undefined;
159
+ }
160
+ function invalidResponse(provider) {
161
+ return new ProviderError({
162
+ provider,
163
+ code: "PROVIDER_INVALID_RESPONSE",
164
+ retryable: false,
165
+ message: "Aderom returned an invalid or mismatched media record.",
166
+ });
167
+ }
168
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/aderom-streaming/index.ts"],"names":[],"mappings":"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"}
@@ -7,11 +7,11 @@ const DEFAULT_BASE_URL = "https://graphql.anilist.co";
7
7
  const DEFAULT_SEARCH_LIMIT = 10;
8
8
  const MAX_SEARCH_RESULTS = 250;
9
9
  const SEARCH_PAGE_SIZE = 50;
10
- const MEDIA_FIELDS = `
11
- id idMal title { romaji english native } synonyms format status episodes duration
12
- startDate { year month day } endDate { year month day }
13
- description(asHtml: false) averageScore popularity isAdult countryOfOrigin
14
- coverImage { extraLarge large } bannerImage genres siteUrl
10
+ const MEDIA_FIELDS = `
11
+ id idMal title { romaji english native } synonyms format status episodes duration
12
+ startDate { year month day } endDate { year month day }
13
+ description(asHtml: false) averageScore popularity isAdult countryOfOrigin
14
+ coverImage { extraLarge large } bannerImage genres siteUrl
15
15
  `;
16
16
  // Creates a public no-token AniList anime metadata provider.
17
17
  // Создает публичный no-token AniList anime metadata provider.
@@ -66,11 +66,11 @@ async function searchAniList(config, query, context) {
66
66
  const pageSize = Math.min(targetLimit, SEARCH_PAGE_SIZE);
67
67
  const itemsById = new Map();
68
68
  for (let page = 1; page <= Math.ceil(targetLimit / pageSize); page += 1) {
69
- const response = await request(config, `query ($page: Int!, $search: String, $perPage: Int!, $isAdult: Boolean, $year: Int, $genre: String, $minimumScore: Int) {
70
- Page(page: $page, perPage: $perPage) {
71
- pageInfo { hasNextPage }
72
- media(search: $search, type: ANIME, seasonYear: $year, genre: $genre, averageScore_greater: $minimumScore, isAdult: $isAdult, sort: ${sort}) { ${MEDIA_FIELDS} }
73
- }
69
+ const response = await request(config, `query ($page: Int!, $search: String, $perPage: Int!, $isAdult: Boolean, $year: Int, $genre: String, $minimumScore: Int) {
70
+ Page(page: $page, perPage: $perPage) {
71
+ pageInfo { hasNextPage }
72
+ media(search: $search, type: ANIME, seasonYear: $year, genre: $genre, averageScore_greater: $minimumScore, isAdult: $isAdult, sort: ${sort}) { ${MEDIA_FIELDS} }
73
+ }
74
74
  }`, {
75
75
  page,
76
76
  search: query.title,
@@ -117,10 +117,10 @@ async function getAniListRelatedMedia(config, query, context) {
117
117
  const ids = query.ids ?? {};
118
118
  if (!ids.aniList && !ids.myAnimeList)
119
119
  return null;
120
- const response = await request(config, `query ($id: Int, $idMal: Int) {
121
- Media(id: $id, idMal: $idMal, type: ANIME) {
122
- relations { edges { relationType node { type ${MEDIA_FIELDS} } } }
123
- }
120
+ const response = await request(config, `query ($id: Int, $idMal: Int) {
121
+ Media(id: $id, idMal: $idMal, type: ANIME) {
122
+ relations { edges { relationType node { type ${MEDIA_FIELDS} } } }
123
+ }
124
124
  }`, {
125
125
  id: ids.aniList ? Number(ids.aniList) : undefined,
126
126
  idMal: !ids.aniList && ids.myAnimeList ? Number(ids.myAnimeList) : undefined,
@@ -215,7 +215,13 @@ async function loadDetailsType(config, imdbId, type, context) {
215
215
  const url = new URL(`${config.baseUrl}/meta/${toCinemetaType(type)}/${imdbId}.json`);
216
216
  try {
217
217
  const response = await requestJson(config, url, context);
218
- const details = response.meta ? metaToDetails(config, response.meta, type) : null;
218
+ const meta = response.meta;
219
+ const details = meta &&
220
+ (!meta.type || meta.type === type) &&
221
+ (!meta.id || meta.id === imdbId) &&
222
+ (!meta.imdb_id || meta.imdb_id === imdbId)
223
+ ? metaToDetails(config, meta, type)
224
+ : null;
219
225
  return details ? { status: "found", details } : { status: "not_found" };
220
226
  }
221
227
  catch (error) {
@@ -225,12 +231,15 @@ async function loadDetailsType(config, imdbId, type, context) {
225
231
  : { status: "failed", error };
226
232
  }
227
233
  }
228
- // Returns any available details and propagates degradation only when nothing was found.
229
- // Возвращает найденные details и пробрасывает деградацию, только если результата нет.
234
+ // Rejects ambiguous untyped identities instead of choosing the first endpoint result.
235
+ // Отклоняет неоднозначную идентичность вместо выбора первого ответа endpoint.
230
236
  function resolveDetailsOutcomes(outcomes) {
231
- const found = outcomes.find((outcome) => outcome.status === "found");
232
- if (found) {
233
- return found.details;
237
+ const found = outcomes.filter((outcome) => outcome.status === "found");
238
+ if (found.length > 1) {
239
+ return null;
240
+ }
241
+ if (found[0]) {
242
+ return found[0].details;
234
243
  }
235
244
  const failures = outcomes.filter((outcome) => outcome.status === "failed");
236
245
  const retryable = failures.find((outcome) => outcome.error instanceof ProviderError && outcome.error.retryable);