@media-engine/providers 0.1.0 → 1.0.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.
Files changed (214) hide show
  1. package/README.md +149 -242
  2. package/README.ru.md +204 -0
  3. package/dist/aniliberty-streaming/client.d.ts +33 -0
  4. package/dist/aniliberty-streaming/client.js +188 -0
  5. package/dist/aniliberty-streaming/client.js.map +1 -0
  6. package/dist/aniliberty-streaming/config.d.ts +24 -0
  7. package/dist/aniliberty-streaming/config.js +61 -0
  8. package/dist/aniliberty-streaming/config.js.map +1 -0
  9. package/dist/aniliberty-streaming/index.d.ts +4 -0
  10. package/dist/aniliberty-streaming/index.js +45 -0
  11. package/dist/aniliberty-streaming/index.js.map +1 -0
  12. package/dist/aniliberty-streaming/mapping.d.ts +3 -0
  13. package/dist/aniliberty-streaming/mapping.js +87 -0
  14. package/dist/aniliberty-streaming/mapping.js.map +1 -0
  15. package/dist/aniliberty-streaming/matching.d.ts +4 -0
  16. package/dist/aniliberty-streaming/matching.js +44 -0
  17. package/dist/aniliberty-streaming/matching.js.map +1 -0
  18. package/dist/anilist/graphql.d.ts +2 -0
  19. package/dist/anilist/graphql.js +135 -0
  20. package/dist/anilist/graphql.js.map +1 -0
  21. package/dist/anilist/index.js +11 -6
  22. package/dist/anilist/index.js.map +1 -1
  23. package/dist/bitsearch-torrent/client.d.ts +19 -0
  24. package/dist/bitsearch-torrent/client.js +181 -0
  25. package/dist/bitsearch-torrent/client.js.map +1 -0
  26. package/dist/bitsearch-torrent/config.d.ts +26 -0
  27. package/dist/bitsearch-torrent/config.js +74 -0
  28. package/dist/bitsearch-torrent/config.js.map +1 -0
  29. package/dist/bitsearch-torrent/index.d.ts +4 -0
  30. package/dist/bitsearch-torrent/index.js +35 -0
  31. package/dist/bitsearch-torrent/index.js.map +1 -0
  32. package/dist/bitsearch-torrent/mapping.d.ts +3 -0
  33. package/dist/bitsearch-torrent/mapping.js +53 -0
  34. package/dist/bitsearch-torrent/mapping.js.map +1 -0
  35. package/dist/bitsearch-torrent/matching.d.ts +3 -0
  36. package/dist/bitsearch-torrent/matching.js +13 -0
  37. package/dist/bitsearch-torrent/matching.js.map +1 -0
  38. package/dist/bitsearch-torrent/quota.d.ts +9 -0
  39. package/dist/bitsearch-torrent/quota.js +53 -0
  40. package/dist/bitsearch-torrent/quota.js.map +1 -0
  41. package/dist/cinemeta/index.js +53 -35
  42. package/dist/cinemeta/index.js.map +1 -1
  43. package/dist/ddbb-streaming/client.d.ts +24 -0
  44. package/dist/ddbb-streaming/client.js +98 -0
  45. package/dist/ddbb-streaming/client.js.map +1 -0
  46. package/dist/ddbb-streaming/config.d.ts +31 -0
  47. package/dist/ddbb-streaming/config.js +67 -0
  48. package/dist/ddbb-streaming/config.js.map +1 -0
  49. package/dist/ddbb-streaming/index.d.ts +4 -0
  50. package/dist/ddbb-streaming/index.js +53 -0
  51. package/dist/ddbb-streaming/index.js.map +1 -0
  52. package/dist/ddbb-streaming/mapping.d.ts +8 -0
  53. package/dist/ddbb-streaming/mapping.js +164 -0
  54. package/dist/ddbb-streaming/mapping.js.map +1 -0
  55. package/dist/ddbb-streaming/validation.d.ts +3 -0
  56. package/dist/ddbb-streaming/validation.js +84 -0
  57. package/dist/ddbb-streaming/validation.js.map +1 -0
  58. package/dist/experimental-streaming/index.js +55 -0
  59. package/dist/experimental-streaming/index.js.map +1 -1
  60. package/dist/flixhq-streaming/client.d.ts +12 -0
  61. package/dist/flixhq-streaming/client.js +103 -0
  62. package/dist/flixhq-streaming/client.js.map +1 -0
  63. package/dist/flixhq-streaming/index.d.ts +2 -2
  64. package/dist/flixhq-streaming/index.js +67 -106
  65. package/dist/flixhq-streaming/index.js.map +1 -1
  66. package/dist/imdb-dataset/index.d.ts +20 -5
  67. package/dist/imdb-dataset/index.js +83 -197
  68. package/dist/imdb-dataset/index.js.map +1 -1
  69. package/dist/imdb-dataset/memory-storage.d.ts +7 -0
  70. package/dist/imdb-dataset/memory-storage.js +168 -0
  71. package/dist/imdb-dataset/memory-storage.js.map +1 -0
  72. package/dist/imdb-dataset/sqlite-import.d.ts +24 -0
  73. package/dist/imdb-dataset/sqlite-import.js +298 -0
  74. package/dist/imdb-dataset/sqlite-import.js.map +1 -0
  75. package/dist/imdb-dataset/sqlite-runtime.d.ts +3 -0
  76. package/dist/imdb-dataset/sqlite-runtime.js +11 -0
  77. package/dist/imdb-dataset/sqlite-runtime.js.map +1 -0
  78. package/dist/imdb-dataset/sqlite-schema.d.ts +6 -0
  79. package/dist/imdb-dataset/sqlite-schema.js +88 -0
  80. package/dist/imdb-dataset/sqlite-schema.js.map +1 -0
  81. package/dist/imdb-dataset/sqlite-storage.d.ts +12 -0
  82. package/dist/imdb-dataset/sqlite-storage.js +208 -0
  83. package/dist/imdb-dataset/sqlite-storage.js.map +1 -0
  84. package/dist/imdb-dataset/storage.d.ts +33 -0
  85. package/dist/imdb-dataset/storage.js +2 -0
  86. package/dist/imdb-dataset/storage.js.map +1 -0
  87. package/dist/index.d.ts +7 -0
  88. package/dist/index.js +7 -0
  89. package/dist/index.js.map +1 -1
  90. package/dist/jacred-torrent/client.d.ts +24 -0
  91. package/dist/jacred-torrent/client.js +254 -0
  92. package/dist/jacred-torrent/client.js.map +1 -0
  93. package/dist/jacred-torrent/config.d.ts +24 -0
  94. package/dist/jacred-torrent/config.js +70 -0
  95. package/dist/jacred-torrent/config.js.map +1 -0
  96. package/dist/jacred-torrent/index.d.ts +4 -0
  97. package/dist/jacred-torrent/index.js +45 -0
  98. package/dist/jacred-torrent/index.js.map +1 -0
  99. package/dist/jacred-torrent/mapping.d.ts +3 -0
  100. package/dist/jacred-torrent/mapping.js +129 -0
  101. package/dist/jacred-torrent/mapping.js.map +1 -0
  102. package/dist/jacred-torrent/matching.d.ts +3 -0
  103. package/dist/jacred-torrent/matching.js +19 -0
  104. package/dist/jacred-torrent/matching.js.map +1 -0
  105. package/dist/kinobd/index.js +15 -33
  106. package/dist/kinobd/index.js.map +1 -1
  107. package/dist/kinobd-streaming/availability.d.ts +3 -0
  108. package/dist/kinobd-streaming/availability.js +179 -0
  109. package/dist/kinobd-streaming/availability.js.map +1 -0
  110. package/dist/kinobd-streaming/candidates.d.ts +32 -0
  111. package/dist/kinobd-streaming/candidates.js +282 -0
  112. package/dist/kinobd-streaming/candidates.js.map +1 -0
  113. package/dist/kinobd-streaming/config.d.ts +64 -0
  114. package/dist/kinobd-streaming/config.js +129 -0
  115. package/dist/kinobd-streaming/config.js.map +1 -0
  116. package/dist/kinobd-streaming/index.d.ts +3 -30
  117. package/dist/kinobd-streaming/index.js +2 -904
  118. package/dist/kinobd-streaming/index.js.map +1 -1
  119. package/dist/kinobd-streaming/players.d.ts +24 -0
  120. package/dist/kinobd-streaming/players.js +270 -0
  121. package/dist/kinobd-streaming/players.js.map +1 -0
  122. package/dist/kinobd-streaming/request-budget.d.ts +15 -0
  123. package/dist/kinobd-streaming/request-budget.js +61 -0
  124. package/dist/kinobd-streaming/request-budget.js.map +1 -0
  125. package/dist/kinobd-streaming/validation.d.ts +11 -0
  126. package/dist/kinobd-streaming/validation.js +170 -0
  127. package/dist/kinobd-streaming/validation.js.map +1 -0
  128. package/dist/magnetz-torrent/client.d.ts +16 -0
  129. package/dist/magnetz-torrent/client.js +235 -0
  130. package/dist/magnetz-torrent/client.js.map +1 -0
  131. package/dist/magnetz-torrent/config.d.ts +27 -0
  132. package/dist/magnetz-torrent/config.js +73 -0
  133. package/dist/magnetz-torrent/config.js.map +1 -0
  134. package/dist/magnetz-torrent/index.d.ts +4 -0
  135. package/dist/magnetz-torrent/index.js +35 -0
  136. package/dist/magnetz-torrent/index.js.map +1 -0
  137. package/dist/magnetz-torrent/mapping.d.ts +3 -0
  138. package/dist/magnetz-torrent/mapping.js +52 -0
  139. package/dist/magnetz-torrent/mapping.js.map +1 -0
  140. package/dist/magnetz-torrent/matching.d.ts +3 -0
  141. package/dist/magnetz-torrent/matching.js +5 -0
  142. package/dist/magnetz-torrent/matching.js.map +1 -0
  143. package/dist/magnetz-torrent/request-gate.d.ts +9 -0
  144. package/dist/magnetz-torrent/request-gate.js +48 -0
  145. package/dist/magnetz-torrent/request-gate.js.map +1 -0
  146. package/dist/package-version.d.ts +2 -0
  147. package/dist/package-version.js +5 -0
  148. package/dist/package-version.js.map +1 -0
  149. package/dist/shared/abort.d.ts +3 -0
  150. package/dist/shared/abort.js +30 -0
  151. package/dist/shared/abort.js.map +1 -0
  152. package/dist/shared/http.d.ts +13 -2
  153. package/dist/shared/http.js +77 -75
  154. package/dist/shared/http.js.map +1 -1
  155. package/dist/shared/index.d.ts +2 -0
  156. package/dist/shared/index.js +2 -0
  157. package/dist/shared/index.js.map +1 -1
  158. package/dist/shared/mapping.d.ts +5 -0
  159. package/dist/shared/mapping.js +32 -0
  160. package/dist/shared/mapping.js.map +1 -0
  161. package/dist/shared/options.d.ts +1 -0
  162. package/dist/shared/options.js +10 -0
  163. package/dist/shared/options.js.map +1 -0
  164. package/dist/shared/output-url.d.ts +2 -0
  165. package/dist/shared/output-url.js +50 -0
  166. package/dist/shared/output-url.js.map +1 -0
  167. package/dist/shared/rate-limit.d.ts +11 -0
  168. package/dist/shared/rate-limit.js +52 -0
  169. package/dist/shared/rate-limit.js.map +1 -0
  170. package/dist/shared/response-body.d.ts +6 -0
  171. package/dist/shared/response-body.js +109 -0
  172. package/dist/shared/response-body.js.map +1 -0
  173. package/dist/shared/retry.d.ts +10 -0
  174. package/dist/shared/retry.js +31 -0
  175. package/dist/shared/retry.js.map +1 -0
  176. package/dist/shared/safe-fetch.d.ts +14 -0
  177. package/dist/shared/safe-fetch.js +274 -0
  178. package/dist/shared/safe-fetch.js.map +1 -0
  179. package/dist/shared/torrent-release-matching.d.ts +7 -0
  180. package/dist/shared/torrent-release-matching.js +143 -0
  181. package/dist/shared/torrent-release-matching.js.map +1 -0
  182. package/dist/shared/torrent-release-metadata.d.ts +4 -0
  183. package/dist/shared/torrent-release-metadata.js +95 -0
  184. package/dist/shared/torrent-release-metadata.js.map +1 -0
  185. package/dist/shikimori/index.js +19 -11
  186. package/dist/shikimori/index.js.map +1 -1
  187. package/dist/tvmaze/index.d.ts +11 -0
  188. package/dist/tvmaze/index.js +274 -0
  189. package/dist/tvmaze/index.js.map +1 -0
  190. package/dist/wikidata/cache.d.ts +11 -0
  191. package/dist/wikidata/cache.js +30 -0
  192. package/dist/wikidata/cache.js.map +1 -0
  193. package/dist/wikidata/candidates.d.ts +12 -0
  194. package/dist/wikidata/candidates.js +67 -0
  195. package/dist/wikidata/candidates.js.map +1 -0
  196. package/dist/wikidata/client.d.ts +40 -0
  197. package/dist/wikidata/client.js +224 -0
  198. package/dist/wikidata/client.js.map +1 -0
  199. package/dist/wikidata/index.d.ts +3 -0
  200. package/dist/wikidata/index.js +34 -111
  201. package/dist/wikidata/index.js.map +1 -1
  202. package/dist/yts-torrent/client.d.ts +25 -0
  203. package/dist/yts-torrent/client.js +163 -0
  204. package/dist/yts-torrent/client.js.map +1 -0
  205. package/dist/yts-torrent/config.d.ts +22 -0
  206. package/dist/yts-torrent/config.js +57 -0
  207. package/dist/yts-torrent/config.js.map +1 -0
  208. package/dist/yts-torrent/index.d.ts +4 -0
  209. package/dist/yts-torrent/index.js +42 -0
  210. package/dist/yts-torrent/index.js.map +1 -0
  211. package/dist/yts-torrent/mapping.d.ts +4 -0
  212. package/dist/yts-torrent/mapping.js +107 -0
  213. package/dist/yts-torrent/mapping.js.map +1 -0
  214. package/package.json +23 -6
package/README.ru.md ADDED
@@ -0,0 +1,204 @@
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
+ ```bash
10
+ npm install @media-engine/core @media-engine/providers
11
+ ```
12
+
13
+ ## Небольшой пример
14
+
15
+ ```ts
16
+ import { MediaEngine } from "@media-engine/core";
17
+ import {
18
+ aniListProvider,
19
+ ddbbStreamingProvider,
20
+ flixHqStreamingProvider,
21
+ kinobdProvider,
22
+ kinobdStreamingProvider,
23
+ shikimoriProvider,
24
+ tvMazeProvider,
25
+ } from "@media-engine/providers";
26
+
27
+ const media = new MediaEngine({
28
+ providers: [kinobdProvider(), shikimoriProvider(), aniListProvider(), tvMazeProvider()],
29
+ });
30
+
31
+ const result = await media.search({ title: "Ван-Пис" });
32
+ ```
33
+
34
+ Подключайте только те провайдеры, которые нужны вашему приложению. Media Engine сам вызовет подходящие и объединит совпадающие ответы.
35
+
36
+ ## Провайдеры метаданных
37
+
38
+ - `kinobdProvider()` — локализованные данные фильмов и сериалов;
39
+ - `cinemetaProvider()` — данные фильмов и сериалов, связанные с IMDb;
40
+ - `shikimoriProvider()` — поиск и детали аниме;
41
+ - `aniListProvider()` — международные названия аниме, популярность и изображения;
42
+ - `tvMazeProvider()` — резервные IMDb identity сериалов и локализованные aliases;
43
+ - `wikidataProvider()` — резервная проверка identity и обогащение открытыми структурированными данными;
44
+ - `imdbDatasetProvider()` — локальные данные IMDb в виде небольших in-memory TSV fixtures или через индексированный storage adapter приложения.
45
+
46
+ Для встроенных провайдеров не нужен ваш API-ключ. TMDB ID может встретиться в результате, но сам пакет не обращается к TMDB API.
47
+
48
+ Данные TVmaze распространяются по лицензии CC BY-SA. Провайдер сохраняет ссылку на страницу сериала TVmaze в source attribution; приложению следует сохранять и показывать эту ссылку. См. [условия API TVmaze](https://www.tvmaze.com/api#licensing).
49
+
50
+ Резервный поиск Wikidata загружает не больше трёх релевантных title entities через selected-property запрос и по умолчанию кеширует entity/IMDb mappings на шесть часов в process-local LRU на 256 записей. `entityLimit` ограничен диапазоном 1–10, `cacheTtlMs` — 0–7 дней, `cacheMaxEntries` — 2–2048; нулевой TTL отключает локальный cache провайдера.
51
+
52
+ Backward-compatible IMDb TSV adapter полностью разбирает входные строки в память и предназначен для небольших наборов и fixtures. Для полного датасета приложение может передать экспортируемый синхронный/асинхронный контракт `ImdbDatasetStorage` с прямым ID lookup и ограниченным поиском по нормализованному title; другим пользователям пакета database dependency не добавляется.
53
+
54
+ Встроенный persisted adapter может потоково собрать plain или gzip IMDb TSV в версионированный SQLite/FTS index с атомарной заменой. `buildImdbDatasetSqliteIndex()` создаёт индекс, а `openImdbDatasetSqliteStorage()` открывает его read-only для `imdbDatasetProvider({ storage })`. Этот опциональный путь лениво использует встроенный `node:sqlite` и требует Node.js 22.13 или новее; импорт пакета и небольшой in-memory adapter сохраняют базовую совместимость с Node.js 20.
55
+
56
+ Ожидаемые сбои внешних источников возвращаются как типизированные `ProviderError`, а исходный HTTP status доступен через `getProviderHttpStatus`. Cinemeta при IMDb-запросе без типа возвращает `null`, только когда отсутствие подтверждено и для фильма, и для сериала; временный сбой одной ветки остаётся retryable, если другая ветка не вернула пригодные детали. AniList также отличает GraphQL rate limit и сбой сервера от ошибок валидации или некорректного ответа, поэтому Media Engine не кеширует неполные метаданные как здоровый результат.
57
+
58
+ Общий `fetchJson` по умолчанию читает потоково не больше 4 МиБ перед разбором JSON и принимает положительный `maxResponseBytes` для индивидуального лимита провайдера. Объявленное или фактическое превышение отменяет body и возвращает non-retryable `PROVIDER_RESPONSE_TOO_LARGE`; некорректный JSON в пределах лимита остаётся `PROVIDER_INVALID_RESPONSE`.
59
+
60
+ Низкоуровневые адаптеры могут передать `ProviderHttpScheduler` через
61
+ `FetchJsonOptions.scheduler`, когда нужен детерминированный контроль retry- и total-timeout
62
+ таймеров, прежде всего в тестах. Обычные provider-вызовы не задают его и используют системные
63
+ таймеры.
64
+
65
+ ## Провайдеры плееров
66
+
67
+ - `kinobdStreamingProvider()` — варианты плееров для фильмов, сериалов и аниме;
68
+ - `flixHqStreamingProvider()` — международные варианты для фильмов и выбранных эпизодов сериалов;
69
+ - `ddbbStreamingProvider()` — opt-in поиск по Kinopoisk/IMDb через независимый маршрут DDBB;
70
+ - `aniLibertyStreamingProvider()` — opt-in точный поиск аниме по названию/году с прямыми HLS-сериями;
71
+ - `experimentalStreamingProvider()` — данные, настроенные вашим приложением для тестов и разработки интерфейса.
72
+
73
+ ```ts
74
+ const media = new MediaEngine({
75
+ streamingProviders: [
76
+ kinobdStreamingProvider(),
77
+ flixHqStreamingProvider(),
78
+ ],
79
+ });
80
+
81
+ const result = await media.getAvailability({
82
+ type: "series",
83
+ title: "Game of Thrones",
84
+ seasonNumber: 1,
85
+ episodeNumber: 1,
86
+ });
87
+ ```
88
+
89
+ Это ссылки на сторонние плееры, а не видео, размещённые Media Engine. Доступность зависит от внешнего источника и окружения пользователя.
90
+
91
+ API репозитория включает `ddbbStreamingProvider()` после повторного reliability/diversity
92
+ checkpoint; прямые пользователи пакета по-прежнему явно выбирают собственный список провайдеров. Он
93
+ принимает только ID Kinopoisk или IMDb, возвращает общие embed-плееры для фильмов/сериалов/anime и не
94
+ заявляет точное сопоставление сезона/эпизода. Diversity-first mapping сначала сохраняет по одному
95
+ основному варианту каждого возвращённого плеера, а затем добавляет уникальные translation URL.
96
+ Nullable отсутствующие плееры дают пустой результат; подтверждённые 404/410 и устойчивые deletion
97
+ markers удаляются, а transient ошибки проверки сохраняются как `unknown`.
98
+
99
+ ```ts
100
+ const media = new MediaEngine({
101
+ streamingProviders: [
102
+ kinobdStreamingProvider(),
103
+ flixHqStreamingProvider(),
104
+ ddbbStreamingProvider(), // явное opt-in подключение
105
+ aniLibertyStreamingProvider(), // явное opt-in подключение
106
+ ],
107
+ });
108
+ ```
109
+
110
+ После того же checkpoint API репозитория включает и `aniLibertyStreamingProvider()`. AniLiberty не
111
+ публикует для релизов MAL, AniList или Shikimori ID, поэтому адаптер требует одновременно название и
112
+ год, принимает только одно точное нормализованное совпадение и повторно проверяет загруженный релиз
113
+ перед возвратом потоков. Он поддерживает общую карту серий и точный `absoluteEpisodeNumber`, но не
114
+ угадывает season/episode. Каждый безопасный first-party URL 480p/720p/1080p возвращается как прямой
115
+ HLS, а географическая и copyright-блокировка релиза сохраняются в availability status.
116
+
117
+ Live-проверка удаляет вариант плеера только после HTTP 404/410 или устойчивого маркера удаления. Rate limit, ошибка сервера, сетевой сбой или timeout проверки сохраняют найденный вариант с `availability: "unknown"`, чтобы engine показал деградацию и повторил проверку вместо кеширования временно урезанного результата.
118
+
119
+ KinoBD по умолчанию ограничивает один availability lookup 24 дочерними HTTP-попытками и проверяет не больше восьми найденных плееров тремя workers. Публичная настройка также ограничена сверху (`childRequestLimit` до 64, `playerValidationLimit` до 16 и `playerValidationConcurrency` до 4). Вложенная iframe-проверка начинается, только если фиксированный deadline провайдера ещё позволяет выделить полное окно validation. Callback `onPlayerAudit` получает дополнительные `metrics`: найденные и проверенные плееры, пропуски по limit/budget, transient unknown, подтверждённые удаления и использованные дочерние запросы.
120
+
121
+ Навигация по FlixHQ не может покинуть настроенный origin, в том числе через redirects. Для внешних player/subtitle проверяются все A/AAAA-адреса, отклоняются private, local, reserved, multicast и смешанные public/private назначения, каждый ограниченный redirect hop валидируется, а соединение закрепляется за проверенным адресом. Пользовательский provider `fetch` считается явно доверенной transport-инъекцией для контролируемых тестов или self-hosted окружений и должен обеспечивать эквивалентную сетевую политику.
122
+
123
+ DDBB ограничивает размер JSON-ответа, количество output options и live validations, concurrency,
124
+ размер validation body и timeout каждого плеера. Его default transport применяет ту же hardened
125
+ политику DNS, redirects и connection pinning к endpoint DDBB и возвращённым плеерам. Пользовательский
126
+ `fetch` остаётся явной доверенной границей для тестов/self-hosted окружений. Варианты за пределом
127
+ ограниченного числа проверок остаются видимыми со статусом `availability: "unknown"` и никогда не
128
+ показываются как успешно проверенные. Alloha представлен одним main option, потому что озвучку
129
+ можно переключить внутри самого плеера; при отсутствии main iframe точкой входа становится не
130
+ больше одного безопасного translation URL. Остальные семейства плееров сохраняют отдельные
131
+ варианты переводов.
132
+
133
+ AniLiberty ограничивает число search-кандидатов и эпизодов релиза, размер JSON, retry и общее время
134
+ провайдера через общие engine/provider-механизмы. Default transport применяет hardened DNS,
135
+ redirect и connection-pinning политику к API-вызовам. Прямые HLS-цели проходят общую browser-facing
136
+ проверку URL; playback network policy остаётся ответственностью потребляющего приложения.
137
+
138
+ Перед публикацией artwork, player или subtitle URL встроенные провайдеры применяют одну output-политику: разрешены только HTTP(S)-цели без credentials, исходных управляющих символов и literal local/private/reserved адресов. Валидные пути и CDN query-параметры, включая временные подписи, сохраняются. Эта browser-facing проверка не заменяет DNS-валидацию или media proxy приложения.
139
+
140
+ ## Источники torrent discovery
141
+
142
+ `ytsTorrentProvider()` — явно подключаемый no-key источник фильмов. Он принимает точный IMDb ID или
143
+ одно точное совпадение названия и года и возвращает нормализованные magnet-кандидаты вроде
144
+ 720p/1080p/2160p с размером, сведениями о релизе и best-effort счётчиками пиров.
145
+
146
+ `jacRedTorrentProvider()` — явно подключаемый no-key русскоязычный/мультиязычный источник для
147
+ точного поиска фильмов, сериалов и аниме по названию и году. Он умеет фильтровать указанный сезон,
148
+ но намеренно не отвечает на запрос конкретной обычной или абсолютной серии. Название, год,
149
+ категория, сезон, info hash, source URL, сведения о релизе и счётчики пиров ограничиваются и
150
+ перепроверяются. Каждый результат также сохраняет ограниченный tracker ID JacRed в
151
+ `catalogSource`; известные значения получают подписи BitRu, RuTracker, RuTor, Kinozal, NNM-Club и
152
+ Knaben без ложной гарантии языка аудиодорожки. `baseUrl` и `searchPath` настраиваются, потому что фактический first-party маршрут
153
+ сейчас отличается от опубликованного.
154
+
155
+ `bitsearchTorrentProvider()` — явно подключаемый no-key источник с широким международным каталогом.
156
+ Он требует название и год, закрепляет категории movie/TV/anime и перед возвратом кандидата заново
157
+ проверяет точное название релиза, явно указанный год, тип медиа, запрошенный сезон и обычный или
158
+ абсолютный номер эпизода. Провайдер читает только ограниченный search-ответ, запоминает исчерпание
159
+ анонимной квоты из `X-RateLimit-*` и создаёт один канонический magnet на проверенный info hash.
160
+ Публичная анонимная квота сейчас составляет 200 запросов в сутки на IP, поэтому приложению следует
161
+ оставлять engine cache включённым и не выполнять speculative-вызовы.
162
+
163
+ `magnetzTorrentProvider()` — явно подключаемый no-key международный meta-search источник. Он делает
164
+ один ограниченный запрос первой страницы, затем локально перепроверяет точное название, год, сезон
165
+ и маркеры обычного или абсолютного эпизода. Провайдер не запускает отдельные detail-запросы для
166
+ каждого результата, создаёт один канонический magnet на проверенный info hash, сохраняет фактические
167
+ peer-счётчики и attribution и выдерживает секунду между началами запросов: live-сервис возвращал
168
+ короткие burst-429 даже при ненулевом остатке в rate-limit header.
169
+
170
+ ```ts
171
+ const media = new MediaEngine({
172
+ torrentProviders: [
173
+ ytsTorrentProvider(),
174
+ jacRedTorrentProvider(),
175
+ bitsearchTorrentProvider(),
176
+ magnetzTorrentProvider(),
177
+ ],
178
+ providerTimeouts: {
179
+ "yts-torrent": 15_000,
180
+ "jacred-torrent": 20_000,
181
+ "bitsearch-torrent": 15_000,
182
+ "magnetz-torrent": 15_000,
183
+ },
184
+ });
185
+
186
+ const torrents = await media.discoverTorrents({
187
+ type: "movie",
188
+ title: "Inception",
189
+ year: 2010,
190
+ ids: { imdb: "tt1375666" },
191
+ });
192
+ ```
193
+
194
+ Провайдер не загружает torrent metadata, не обращается к trackers, не подключается к swarm и не
195
+ воспроизводит видео. После общего multi-source checkpoint torrent-провайдеры остались выключенными
196
+ в API defaults: у Bitsearch небольшая суточная анонимная квота, Magnetz чувствителен к burst-нагрузке,
197
+ а у JacRed сохраняется редкий длинный timeout tail. Приложение явно выбирает нужное подмножество и
198
+ может контролировать его командой `pnpm smoke:torrents`, сохраняя собственный request budget.
199
+
200
+ Настройки, ограничения и правила безопасности кратко описаны в [документации провайдеров](https://github.com/Yaneart/media-engine/blob/main/docs/providers.md).
201
+
202
+ ## Лицензия
203
+
204
+ MIT
@@ -0,0 +1,33 @@
1
+ import { type ProviderContext } from "@media-engine/core";
2
+ import type { AniLibertyStreamingConfig } from "./config.js";
3
+ export interface AniLibertyReleaseName {
4
+ main?: string;
5
+ english?: string;
6
+ alternative?: string;
7
+ }
8
+ export interface AniLibertyReleaseSummary {
9
+ id: number;
10
+ year: number;
11
+ name: AniLibertyReleaseName;
12
+ alias?: string;
13
+ blockedByGeo: boolean;
14
+ blockedByCopyrights: boolean;
15
+ }
16
+ export interface AniLibertyEpisode {
17
+ id: string;
18
+ name?: string;
19
+ ordinal: number;
20
+ hls480?: string;
21
+ hls720?: string;
22
+ hls1080?: string;
23
+ }
24
+ export interface AniLibertyRelease extends AniLibertyReleaseSummary {
25
+ episodes: AniLibertyEpisode[];
26
+ }
27
+ export declare function searchAniLibertyReleases(config: AniLibertyStreamingConfig, title: string, context: ProviderContext): Promise<AniLibertyReleaseSummary[]>;
28
+ export declare function loadAniLibertyRelease(config: AniLibertyStreamingConfig, releaseId: number, context: ProviderContext): Promise<{
29
+ release: AniLibertyRelease;
30
+ sourceUrl: string;
31
+ } | null>;
32
+ export declare function parseAniLibertySearchResponse(provider: string, value: unknown, limit: number): AniLibertyReleaseSummary[];
33
+ export declare function parseAniLibertyRelease(provider: string, value: unknown, episodeLimit: number): AniLibertyRelease;
@@ -0,0 +1,188 @@
1
+ import { ProviderError } from "@media-engine/core";
2
+ import { fetchJson, getProviderHttpStatus } from "../shared/index.js";
3
+ const SEARCH_INCLUDE = [
4
+ "id",
5
+ "year",
6
+ "name",
7
+ "alias",
8
+ "is_blocked_by_geo",
9
+ "is_blocked_by_copyrights",
10
+ ].join(",");
11
+ const RELEASE_INCLUDE = [
12
+ SEARCH_INCLUDE,
13
+ "episodes.id",
14
+ "episodes.name",
15
+ "episodes.ordinal",
16
+ "episodes.hls_480",
17
+ "episodes.hls_720",
18
+ "episodes.hls_1080",
19
+ ].join(",");
20
+ export async function searchAniLibertyReleases(config, title, context) {
21
+ const url = new URL("/api/v1/app/search/releases", `${config.baseUrl}/`);
22
+ url.searchParams.set("query", title);
23
+ url.searchParams.set("include", SEARCH_INCLUDE);
24
+ const payload = await fetchJson({
25
+ provider: config.name,
26
+ url,
27
+ context,
28
+ fetch: config.fetch,
29
+ rateLimitGate: config.rateLimitGate,
30
+ maxResponseBytes: config.maxResponseBytes,
31
+ init: { headers: createHeaders(config) },
32
+ });
33
+ return parseAniLibertySearchResponse(config.name, payload, config.searchResultLimit);
34
+ }
35
+ export async function loadAniLibertyRelease(config, releaseId, context) {
36
+ const sourceUrl = new URL(`/api/v1/anime/releases/${releaseId}`, `${config.baseUrl}/`);
37
+ const requestUrl = new URL(sourceUrl);
38
+ requestUrl.searchParams.set("include", RELEASE_INCLUDE);
39
+ try {
40
+ const payload = await fetchJson({
41
+ provider: config.name,
42
+ url: requestUrl,
43
+ context,
44
+ fetch: config.fetch,
45
+ rateLimitGate: config.rateLimitGate,
46
+ maxResponseBytes: config.maxResponseBytes,
47
+ init: { headers: createHeaders(config) },
48
+ });
49
+ return {
50
+ release: parseAniLibertyRelease(config.name, payload, config.episodeLimit),
51
+ sourceUrl: sourceUrl.href,
52
+ };
53
+ }
54
+ catch (error) {
55
+ if (getProviderHttpStatus(error) === 404)
56
+ return null;
57
+ throw error;
58
+ }
59
+ }
60
+ export function parseAniLibertySearchResponse(provider, value, limit) {
61
+ if (!Array.isArray(value)) {
62
+ throw invalidResponse(provider);
63
+ }
64
+ const releases = value.slice(0, limit).flatMap((entry) => {
65
+ const release = parseReleaseSummary(entry);
66
+ return release ? [release] : [];
67
+ });
68
+ if (value.length > 0 && releases.length === 0) {
69
+ throw invalidResponse(provider);
70
+ }
71
+ return releases;
72
+ }
73
+ export function parseAniLibertyRelease(provider, value, episodeLimit) {
74
+ const summary = parseReleaseSummary(value);
75
+ if (!summary || !isRecord(value) || !Array.isArray(value.episodes)) {
76
+ throw invalidResponse(provider);
77
+ }
78
+ const episodes = value.episodes.slice(0, episodeLimit).flatMap((entry) => {
79
+ const episode = parseEpisode(entry);
80
+ return episode ? [episode] : [];
81
+ });
82
+ if (value.episodes.length > 0 && episodes.length === 0) {
83
+ throw invalidResponse(provider);
84
+ }
85
+ return { ...summary, episodes };
86
+ }
87
+ function parseReleaseSummary(value) {
88
+ if (!isRecord(value) || !isRecord(value.name))
89
+ return undefined;
90
+ const id = readInteger(value.id, 1, Number.MAX_SAFE_INTEGER);
91
+ const year = readInteger(value.year, 1_800, 3_000);
92
+ const main = readNullableString(value.name.main, 500);
93
+ const english = readNullableString(value.name.english, 500);
94
+ const alternative = readNullableString(value.name.alternative, 2_000);
95
+ const alias = readNullableString(value.alias, 500);
96
+ const blockedByGeo = readOptionalBoolean(value.is_blocked_by_geo);
97
+ const blockedByCopyrights = readOptionalBoolean(value.is_blocked_by_copyrights);
98
+ if (id === undefined ||
99
+ year === undefined ||
100
+ main === false ||
101
+ english === false ||
102
+ alternative === false ||
103
+ alias === false ||
104
+ blockedByGeo === undefined ||
105
+ blockedByCopyrights === undefined ||
106
+ (!main && !english)) {
107
+ return undefined;
108
+ }
109
+ return {
110
+ id,
111
+ year,
112
+ name: {
113
+ ...(main ? { main } : {}),
114
+ ...(english ? { english } : {}),
115
+ ...(alternative ? { alternative } : {}),
116
+ },
117
+ ...(alias ? { alias } : {}),
118
+ blockedByGeo,
119
+ blockedByCopyrights,
120
+ };
121
+ }
122
+ function parseEpisode(value) {
123
+ if (!isRecord(value))
124
+ return undefined;
125
+ const id = readRequiredString(value.id, 200);
126
+ const ordinal = readFiniteNumber(value.ordinal, 0, 100_000);
127
+ const name = readNullableString(value.name, 500);
128
+ const hls480 = readNullableString(value.hls_480, 8_192);
129
+ const hls720 = readNullableString(value.hls_720, 8_192);
130
+ const hls1080 = readNullableString(value.hls_1080, 8_192);
131
+ if (!id ||
132
+ ordinal === undefined ||
133
+ name === false ||
134
+ hls480 === false ||
135
+ hls720 === false ||
136
+ hls1080 === false) {
137
+ return undefined;
138
+ }
139
+ return {
140
+ id,
141
+ ordinal,
142
+ ...(name ? { name } : {}),
143
+ ...(hls480 ? { hls480 } : {}),
144
+ ...(hls720 ? { hls720 } : {}),
145
+ ...(hls1080 ? { hls1080 } : {}),
146
+ };
147
+ }
148
+ function createHeaders(config) {
149
+ return {
150
+ Accept: "application/json",
151
+ "User-Agent": config.userAgent,
152
+ };
153
+ }
154
+ function readInteger(value, min, max) {
155
+ return Number.isSafeInteger(value) && Number(value) >= min && Number(value) <= max
156
+ ? Number(value)
157
+ : undefined;
158
+ }
159
+ function readFiniteNumber(value, min, max) {
160
+ return typeof value === "number" && Number.isFinite(value) && value >= min && value <= max
161
+ ? value
162
+ : undefined;
163
+ }
164
+ function readRequiredString(value, maxLength) {
165
+ return typeof value === "string" ? value.trim().slice(0, maxLength) || undefined : undefined;
166
+ }
167
+ function readNullableString(value, maxLength) {
168
+ if (value === null || value === undefined)
169
+ return undefined;
170
+ return typeof value === "string" ? value.trim().slice(0, maxLength) || undefined : false;
171
+ }
172
+ function readOptionalBoolean(value) {
173
+ if (value === null || value === undefined)
174
+ return false;
175
+ return typeof value === "boolean" ? value : undefined;
176
+ }
177
+ function isRecord(value) {
178
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
179
+ }
180
+ function invalidResponse(provider) {
181
+ return new ProviderError({
182
+ provider,
183
+ code: "PROVIDER_INVALID_RESPONSE",
184
+ message: `Provider "${provider}" returned an invalid AniLiberty response.`,
185
+ retryable: false,
186
+ });
187
+ }
188
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/aniliberty-streaming/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { StreamingProviderCapabilities } from "@media-engine/core";
2
+ import { ProviderRateLimitGate, type ProviderFetch } from "../shared/index.js";
3
+ export interface AniLibertyStreamingProviderOptions {
4
+ name?: string;
5
+ version?: string;
6
+ baseUrl?: string;
7
+ fetch?: ProviderFetch;
8
+ maxResponseBytes?: number;
9
+ searchResultLimit?: number;
10
+ episodeLimit?: number;
11
+ userAgent?: string;
12
+ }
13
+ export interface AniLibertyStreamingConfig {
14
+ name: string;
15
+ baseUrl: string;
16
+ fetch: ProviderFetch;
17
+ rateLimitGate: ProviderRateLimitGate;
18
+ maxResponseBytes: number;
19
+ searchResultLimit: number;
20
+ episodeLimit: number;
21
+ userAgent: string;
22
+ }
23
+ export declare function createAniLibertyConfig(options: AniLibertyStreamingProviderOptions): AniLibertyStreamingConfig;
24
+ export declare function createAniLibertyCapabilities(): StreamingProviderCapabilities;
@@ -0,0 +1,61 @@
1
+ import { MEDIA_ENGINE_DEFAULT_USER_AGENT } from "../package-version.js";
2
+ import { ProviderRateLimitGate } from "../shared/index.js";
3
+ import { resolveBoundedIntegerOption } from "../shared/options.js";
4
+ import { createHardenedProviderFetch } from "../shared/safe-fetch.js";
5
+ const DEFAULT_PROVIDER_NAME = "aniliberty-streaming";
6
+ const DEFAULT_BASE_URL = "https://aniliberty.top";
7
+ const DEFAULT_MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
8
+ const DEFAULT_SEARCH_RESULT_LIMIT = 50;
9
+ const DEFAULT_EPISODE_LIMIT = 200;
10
+ export function createAniLibertyConfig(options) {
11
+ const name = normalizeProviderName(options.name ?? DEFAULT_PROVIDER_NAME);
12
+ return {
13
+ name,
14
+ baseUrl: normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL),
15
+ fetch: options.fetch ?? createHardenedProviderFetch({ provider: name, maxRedirects: 3 }),
16
+ rateLimitGate: new ProviderRateLimitGate(),
17
+ maxResponseBytes: resolveBoundedIntegerOption(options.maxResponseBytes, DEFAULT_MAX_RESPONSE_BYTES, "AniLiberty streaming maxResponseBytes", 1_024, 16 * 1024 * 1024),
18
+ searchResultLimit: resolveBoundedIntegerOption(options.searchResultLimit, DEFAULT_SEARCH_RESULT_LIMIT, "AniLiberty streaming searchResultLimit", 1, 100),
19
+ episodeLimit: resolveBoundedIntegerOption(options.episodeLimit, DEFAULT_EPISODE_LIMIT, "AniLiberty streaming episodeLimit", 1, 1_000),
20
+ userAgent: options.userAgent?.trim() || MEDIA_ENGINE_DEFAULT_USER_AGENT,
21
+ };
22
+ }
23
+ export function createAniLibertyCapabilities() {
24
+ return {
25
+ mediaTypes: ["anime"],
26
+ lookup: {
27
+ byTitle: true,
28
+ byExternalIds: [],
29
+ byEpisode: true,
30
+ },
31
+ features: ["hls", "translations", "qualities", "episode_mapping"],
32
+ };
33
+ }
34
+ function normalizeProviderName(value) {
35
+ const name = value.trim();
36
+ if (!name) {
37
+ throw new TypeError("AniLiberty streaming provider name is required.");
38
+ }
39
+ return name;
40
+ }
41
+ function normalizeBaseUrl(value) {
42
+ let url;
43
+ try {
44
+ url = new URL(value.trim());
45
+ }
46
+ catch (error) {
47
+ throw new TypeError("AniLiberty streaming baseUrl must be a valid HTTP(S) URL.", {
48
+ cause: error,
49
+ });
50
+ }
51
+ if ((url.protocol !== "http:" && url.protocol !== "https:") ||
52
+ url.username !== "" ||
53
+ url.password !== "") {
54
+ throw new TypeError("AniLiberty streaming baseUrl must be a credential-free HTTP(S) URL.");
55
+ }
56
+ url.pathname = "/";
57
+ url.search = "";
58
+ url.hash = "";
59
+ return url.href.replace(/\/$/u, "");
60
+ }
61
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/aniliberty-streaming/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { StreamingProvider } from "@media-engine/core";
2
+ import { type AniLibertyStreamingProviderOptions } from "./config.js";
3
+ export type { AniLibertyStreamingProviderOptions } from "./config.js";
4
+ export declare function aniLibertyStreamingProvider(options?: AniLibertyStreamingProviderOptions): StreamingProvider;
@@ -0,0 +1,45 @@
1
+ import { rethrowIfProviderAborted } from "../shared/abort.js";
2
+ import { loadAniLibertyRelease, searchAniLibertyReleases } from "./client.js";
3
+ import { createAniLibertyCapabilities, createAniLibertyConfig, } from "./config.js";
4
+ import { mapAniLibertyAvailability } from "./mapping.js";
5
+ import { matchesAniLibertyRelease, selectAniLibertyRelease } from "./matching.js";
6
+ export function aniLibertyStreamingProvider(options = {}) {
7
+ const config = createAniLibertyConfig(options);
8
+ return {
9
+ name: config.name,
10
+ version: options.version,
11
+ kind: "streaming",
12
+ capabilities: createAniLibertyCapabilities(),
13
+ async getAvailability(query, context) {
14
+ if (query.providers && !query.providers.includes(config.name))
15
+ return null;
16
+ if (!canResolveQuery(query))
17
+ return null;
18
+ try {
19
+ const candidates = await searchAniLibertyReleases(config, query.title, context);
20
+ const candidate = selectAniLibertyRelease(candidates, query);
21
+ if (!candidate)
22
+ return null;
23
+ const loaded = await loadAniLibertyRelease(config, candidate.id, context);
24
+ if (!loaded ||
25
+ loaded.release.id !== candidate.id ||
26
+ !matchesAniLibertyRelease(loaded.release, query)) {
27
+ return null;
28
+ }
29
+ return mapAniLibertyAvailability(config.name, loaded.release, query, loaded.sourceUrl);
30
+ }
31
+ catch (error) {
32
+ rethrowIfProviderAborted(context, error);
33
+ throw error;
34
+ }
35
+ },
36
+ };
37
+ }
38
+ function canResolveQuery(query) {
39
+ return (query.type === "anime" &&
40
+ Boolean(query.title?.trim()) &&
41
+ Number.isInteger(query.year) &&
42
+ query.seasonNumber === undefined &&
43
+ query.episodeNumber === undefined);
44
+ }
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/aniliberty-streaming/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,GAEvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAIlF,MAAM,UAAU,2BAA2B,CACzC,UAA8C,EAAE;IAEhD,MAAM,MAAM,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAE/C,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,WAAW;QACjB,YAAY,EAAE,4BAA4B,EAAE;QAC5C,KAAK,CAAC,eAAe,CAAC,KAAK,EAAE,OAAO;YAClC,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC3E,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YAEzC,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,MAAM,wBAAwB,CAAC,MAAM,EAAE,KAAK,CAAC,KAAM,EAAE,OAAO,CAAC,CAAC;gBACjF,MAAM,SAAS,GAAG,uBAAuB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;gBAC7D,IAAI,CAAC,SAAS;oBAAE,OAAO,IAAI,CAAC;gBAE5B,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;gBAC1E,IACE,CAAC,MAAM;oBACP,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE;oBAClC,CAAC,wBAAwB,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,EAChD,CAAC;oBACD,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,OAAO,yBAAyB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;YACzF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,wBAAwB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;gBACzC,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,KAA0D;IACjF,OAAO,CACL,KAAK,CAAC,IAAI,KAAK,OAAO;QACtB,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;QAC5B,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;QAC5B,KAAK,CAAC,YAAY,KAAK,SAAS;QAChC,KAAK,CAAC,aAAa,KAAK,SAAS,CAClC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { MediaAvailability } from "@media-engine/core";
2
+ import type { AniLibertyRelease } from "./client.js";
3
+ export declare function mapAniLibertyAvailability(provider: string, release: AniLibertyRelease, query: MediaAvailability["query"], sourceUrl: string): MediaAvailability | null;