@media-engine/providers 1.8.1 → 1.10.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/LICENSE +21 -0
- package/README.md +172 -164
- package/README.ru.md +178 -170
- package/dist/aderom-streaming/index.d.ts +8 -0
- package/dist/aderom-streaming/index.js +168 -0
- package/dist/aderom-streaming/index.js.map +1 -0
- package/dist/cinemeta/index.js +15 -6
- package/dist/cinemeta/index.js.map +1 -1
- package/dist/identity/aderom.d.ts +7 -0
- package/dist/identity/aderom.js +49 -0
- package/dist/identity/aderom.js.map +1 -0
- package/dist/identity/anime.d.ts +9 -0
- package/dist/identity/anime.js +155 -0
- package/dist/identity/anime.js.map +1 -0
- package/dist/identity/kinobd.d.ts +7 -0
- package/dist/identity/kinobd.js +54 -0
- package/dist/identity/kinobd.js.map +1 -0
- package/dist/identity/wikidata.d.ts +7 -0
- package/dist/identity/wikidata.js +117 -0
- package/dist/identity/wikidata.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/initem-streaming/index.d.ts +7 -0
- package/dist/initem-streaming/index.js +93 -0
- package/dist/initem-streaming/index.js.map +1 -0
- package/dist/package-version.d.ts +2 -2
- package/dist/package-version.js +1 -1
- package/dist/package-version.js.map +1 -1
- package/dist/shikimori/index.js +52 -10
- package/dist/shikimori/index.js.map +1 -1
- package/dist/tmdb/index.d.ts +7 -0
- package/dist/tmdb/index.js +206 -0
- package/dist/tmdb/index.js.map +1 -0
- package/dist/veoveo-streaming/client.js +2 -1
- package/dist/veoveo-streaming/client.js.map +1 -1
- package/dist/veoveo-streaming/config.js +2 -1
- package/dist/veoveo-streaming/config.js.map +1 -1
- package/dist/veoveo-streaming/index.js +3 -2
- package/dist/veoveo-streaming/index.js.map +1 -1
- package/dist/veoveo-streaming/mapping.js +1 -1
- package/dist/veoveo-streaming/mapping.js.map +1 -1
- package/dist/videohub-streaming/client.js +4 -29
- package/dist/videohub-streaming/client.js.map +1 -1
- package/dist/videohub-streaming/config.js +2 -1
- package/dist/videohub-streaming/config.js.map +1 -1
- package/dist/videohub-streaming/index.js +6 -20
- package/dist/videohub-streaming/index.js.map +1 -1
- package/dist/videohub-streaming/mapping.d.ts +1 -2
- package/dist/videohub-streaming/mapping.js +0 -49
- package/dist/videohub-streaming/mapping.js.map +1 -1
- package/package.json +5 -6
package/README.ru.md
CHANGED
|
@@ -1,170 +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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- `
|
|
65
|
-
- `
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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 Кинопоиска и возвращает короткоживущие прямые MP4 для фильмов или одной точной
|
|
122
|
+
серии сериала. VeoVeo и VideoHUB принимают аниме только с подтверждённым `animeKind: "movie"`;
|
|
123
|
+
эпизодное аниме остаётся на совместимых с аниме провайдерах. Ответ для аниме-фильма сохраняет
|
|
124
|
+
`type: "anime"`. Ссылки VideoHUB привязаны к 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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"}
|
package/dist/cinemeta/index.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
229
|
-
//
|
|
234
|
+
// Rejects ambiguous untyped identities instead of choosing the first endpoint result.
|
|
235
|
+
// Отклоняет неоднозначную идентичность вместо выбора первого ответа endpoint.
|
|
230
236
|
function resolveDetailsOutcomes(outcomes) {
|
|
231
|
-
const found = outcomes.
|
|
232
|
-
if (found) {
|
|
233
|
-
return
|
|
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);
|