@media-engine/providers 0.1.1 → 1.1.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 (259) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +85 -25
  3. package/README.ru.md +90 -26
  4. package/dist/aniliberty-streaming/client.d.ts +33 -0
  5. package/dist/aniliberty-streaming/client.js +188 -0
  6. package/dist/aniliberty-streaming/client.js.map +1 -0
  7. package/dist/aniliberty-streaming/config.d.ts +24 -0
  8. package/dist/aniliberty-streaming/config.js +61 -0
  9. package/dist/aniliberty-streaming/config.js.map +1 -0
  10. package/dist/aniliberty-streaming/index.d.ts +4 -0
  11. package/dist/aniliberty-streaming/index.js +45 -0
  12. package/dist/aniliberty-streaming/index.js.map +1 -0
  13. package/dist/aniliberty-streaming/mapping.d.ts +3 -0
  14. package/dist/aniliberty-streaming/mapping.js +87 -0
  15. package/dist/aniliberty-streaming/mapping.js.map +1 -0
  16. package/dist/aniliberty-streaming/matching.d.ts +4 -0
  17. package/dist/aniliberty-streaming/matching.js +44 -0
  18. package/dist/aniliberty-streaming/matching.js.map +1 -0
  19. package/dist/anilist/graphql.d.ts +2 -0
  20. package/dist/anilist/graphql.js +135 -0
  21. package/dist/anilist/graphql.js.map +1 -0
  22. package/dist/anilist/index.js +8 -6
  23. package/dist/anilist/index.js.map +1 -1
  24. package/dist/bitsearch-torrent/client.d.ts +19 -0
  25. package/dist/bitsearch-torrent/client.js +181 -0
  26. package/dist/bitsearch-torrent/client.js.map +1 -0
  27. package/dist/bitsearch-torrent/config.d.ts +26 -0
  28. package/dist/bitsearch-torrent/config.js +74 -0
  29. package/dist/bitsearch-torrent/config.js.map +1 -0
  30. package/dist/bitsearch-torrent/index.d.ts +4 -0
  31. package/dist/bitsearch-torrent/index.js +35 -0
  32. package/dist/bitsearch-torrent/index.js.map +1 -0
  33. package/dist/bitsearch-torrent/mapping.d.ts +3 -0
  34. package/dist/bitsearch-torrent/mapping.js +53 -0
  35. package/dist/bitsearch-torrent/mapping.js.map +1 -0
  36. package/dist/bitsearch-torrent/matching.d.ts +3 -0
  37. package/dist/bitsearch-torrent/matching.js +13 -0
  38. package/dist/bitsearch-torrent/matching.js.map +1 -0
  39. package/dist/bitsearch-torrent/quota.d.ts +9 -0
  40. package/dist/bitsearch-torrent/quota.js +53 -0
  41. package/dist/bitsearch-torrent/quota.js.map +1 -0
  42. package/dist/cinemeta/index.js +37 -12
  43. package/dist/cinemeta/index.js.map +1 -1
  44. package/dist/ddbb-streaming/client.d.ts +24 -0
  45. package/dist/ddbb-streaming/client.js +98 -0
  46. package/dist/ddbb-streaming/client.js.map +1 -0
  47. package/dist/ddbb-streaming/config.d.ts +31 -0
  48. package/dist/ddbb-streaming/config.js +67 -0
  49. package/dist/ddbb-streaming/config.js.map +1 -0
  50. package/dist/ddbb-streaming/index.d.ts +4 -0
  51. package/dist/ddbb-streaming/index.js +53 -0
  52. package/dist/ddbb-streaming/index.js.map +1 -0
  53. package/dist/ddbb-streaming/mapping.d.ts +8 -0
  54. package/dist/ddbb-streaming/mapping.js +164 -0
  55. package/dist/ddbb-streaming/mapping.js.map +1 -0
  56. package/dist/ddbb-streaming/validation.d.ts +3 -0
  57. package/dist/ddbb-streaming/validation.js +84 -0
  58. package/dist/ddbb-streaming/validation.js.map +1 -0
  59. package/dist/experimental-streaming/index.js +55 -0
  60. package/dist/experimental-streaming/index.js.map +1 -1
  61. package/dist/filmix-streaming/client.d.ts +36 -0
  62. package/dist/filmix-streaming/client.js +241 -0
  63. package/dist/filmix-streaming/client.js.map +1 -0
  64. package/dist/filmix-streaming/config.d.ts +42 -0
  65. package/dist/filmix-streaming/config.js +93 -0
  66. package/dist/filmix-streaming/config.js.map +1 -0
  67. package/dist/filmix-streaming/index.d.ts +4 -0
  68. package/dist/filmix-streaming/index.js +48 -0
  69. package/dist/filmix-streaming/index.js.map +1 -0
  70. package/dist/filmix-streaming/mapping.d.ts +3 -0
  71. package/dist/filmix-streaming/mapping.js +109 -0
  72. package/dist/filmix-streaming/mapping.js.map +1 -0
  73. package/dist/filmix-streaming/matching.d.ts +4 -0
  74. package/dist/filmix-streaming/matching.js +41 -0
  75. package/dist/filmix-streaming/matching.js.map +1 -0
  76. package/dist/flixhq-streaming/client.d.ts +12 -0
  77. package/dist/flixhq-streaming/client.js +103 -0
  78. package/dist/flixhq-streaming/client.js.map +1 -0
  79. package/dist/flixhq-streaming/index.d.ts +2 -2
  80. package/dist/flixhq-streaming/index.js +58 -104
  81. package/dist/flixhq-streaming/index.js.map +1 -1
  82. package/dist/imdb-dataset/index.d.ts +20 -5
  83. package/dist/imdb-dataset/index.js +82 -184
  84. package/dist/imdb-dataset/index.js.map +1 -1
  85. package/dist/imdb-dataset/memory-storage.d.ts +7 -0
  86. package/dist/imdb-dataset/memory-storage.js +168 -0
  87. package/dist/imdb-dataset/memory-storage.js.map +1 -0
  88. package/dist/imdb-dataset/sqlite-import.d.ts +24 -0
  89. package/dist/imdb-dataset/sqlite-import.js +301 -0
  90. package/dist/imdb-dataset/sqlite-import.js.map +1 -0
  91. package/dist/imdb-dataset/sqlite-runtime.d.ts +3 -0
  92. package/dist/imdb-dataset/sqlite-runtime.js +11 -0
  93. package/dist/imdb-dataset/sqlite-runtime.js.map +1 -0
  94. package/dist/imdb-dataset/sqlite-schema.d.ts +6 -0
  95. package/dist/imdb-dataset/sqlite-schema.js +88 -0
  96. package/dist/imdb-dataset/sqlite-schema.js.map +1 -0
  97. package/dist/imdb-dataset/sqlite-storage.d.ts +12 -0
  98. package/dist/imdb-dataset/sqlite-storage.js +208 -0
  99. package/dist/imdb-dataset/sqlite-storage.js.map +1 -0
  100. package/dist/imdb-dataset/storage.d.ts +33 -0
  101. package/dist/imdb-dataset/storage.js +2 -0
  102. package/dist/imdb-dataset/storage.js.map +1 -0
  103. package/dist/index.d.ts +11 -0
  104. package/dist/index.js +11 -0
  105. package/dist/index.js.map +1 -1
  106. package/dist/jacred-torrent/client.d.ts +24 -0
  107. package/dist/jacred-torrent/client.js +254 -0
  108. package/dist/jacred-torrent/client.js.map +1 -0
  109. package/dist/jacred-torrent/config.d.ts +24 -0
  110. package/dist/jacred-torrent/config.js +70 -0
  111. package/dist/jacred-torrent/config.js.map +1 -0
  112. package/dist/jacred-torrent/index.d.ts +4 -0
  113. package/dist/jacred-torrent/index.js +45 -0
  114. package/dist/jacred-torrent/index.js.map +1 -0
  115. package/dist/jacred-torrent/mapping.d.ts +3 -0
  116. package/dist/jacred-torrent/mapping.js +129 -0
  117. package/dist/jacred-torrent/mapping.js.map +1 -0
  118. package/dist/jacred-torrent/matching.d.ts +3 -0
  119. package/dist/jacred-torrent/matching.js +19 -0
  120. package/dist/jacred-torrent/matching.js.map +1 -0
  121. package/dist/kinobd/index.js +5 -6
  122. package/dist/kinobd/index.js.map +1 -1
  123. package/dist/kinobd-streaming/availability.js +52 -29
  124. package/dist/kinobd-streaming/availability.js.map +1 -1
  125. package/dist/kinobd-streaming/candidates.d.ts +3 -2
  126. package/dist/kinobd-streaming/candidates.js +89 -37
  127. package/dist/kinobd-streaming/candidates.js.map +1 -1
  128. package/dist/kinobd-streaming/config.d.ts +15 -0
  129. package/dist/kinobd-streaming/config.js +21 -17
  130. package/dist/kinobd-streaming/config.js.map +1 -1
  131. package/dist/kinobd-streaming/index.d.ts +1 -1
  132. package/dist/kinobd-streaming/index.js.map +1 -1
  133. package/dist/kinobd-streaming/players.d.ts +2 -1
  134. package/dist/kinobd-streaming/players.js +7 -7
  135. package/dist/kinobd-streaming/players.js.map +1 -1
  136. package/dist/kinobd-streaming/request-budget.d.ts +15 -0
  137. package/dist/kinobd-streaming/request-budget.js +61 -0
  138. package/dist/kinobd-streaming/request-budget.js.map +1 -0
  139. package/dist/kinobd-streaming/validation.d.ts +5 -3
  140. package/dist/kinobd-streaming/validation.js +78 -49
  141. package/dist/kinobd-streaming/validation.js.map +1 -1
  142. package/dist/magnetz-torrent/client.d.ts +16 -0
  143. package/dist/magnetz-torrent/client.js +235 -0
  144. package/dist/magnetz-torrent/client.js.map +1 -0
  145. package/dist/magnetz-torrent/config.d.ts +27 -0
  146. package/dist/magnetz-torrent/config.js +73 -0
  147. package/dist/magnetz-torrent/config.js.map +1 -0
  148. package/dist/magnetz-torrent/index.d.ts +4 -0
  149. package/dist/magnetz-torrent/index.js +35 -0
  150. package/dist/magnetz-torrent/index.js.map +1 -0
  151. package/dist/magnetz-torrent/mapping.d.ts +3 -0
  152. package/dist/magnetz-torrent/mapping.js +52 -0
  153. package/dist/magnetz-torrent/mapping.js.map +1 -0
  154. package/dist/magnetz-torrent/matching.d.ts +3 -0
  155. package/dist/magnetz-torrent/matching.js +5 -0
  156. package/dist/magnetz-torrent/matching.js.map +1 -0
  157. package/dist/magnetz-torrent/request-gate.d.ts +9 -0
  158. package/dist/magnetz-torrent/request-gate.js +48 -0
  159. package/dist/magnetz-torrent/request-gate.js.map +1 -0
  160. package/dist/package-version.d.ts +2 -0
  161. package/dist/package-version.js +5 -0
  162. package/dist/package-version.js.map +1 -0
  163. package/dist/rutube-streaming/client.d.ts +18 -0
  164. package/dist/rutube-streaming/client.js +106 -0
  165. package/dist/rutube-streaming/client.js.map +1 -0
  166. package/dist/rutube-streaming/config.d.ts +26 -0
  167. package/dist/rutube-streaming/config.js +57 -0
  168. package/dist/rutube-streaming/config.js.map +1 -0
  169. package/dist/rutube-streaming/index.d.ts +4 -0
  170. package/dist/rutube-streaming/index.js +38 -0
  171. package/dist/rutube-streaming/index.js.map +1 -0
  172. package/dist/rutube-streaming/mapping.d.ts +4 -0
  173. package/dist/rutube-streaming/mapping.js +32 -0
  174. package/dist/rutube-streaming/mapping.js.map +1 -0
  175. package/dist/rutube-streaming/matching.d.ts +2 -0
  176. package/dist/rutube-streaming/matching.js +37 -0
  177. package/dist/rutube-streaming/matching.js.map +1 -0
  178. package/dist/shared/abort.d.ts +1 -0
  179. package/dist/shared/abort.js +22 -0
  180. package/dist/shared/abort.js.map +1 -1
  181. package/dist/shared/http.d.ts +8 -2
  182. package/dist/shared/http.js +37 -67
  183. package/dist/shared/http.js.map +1 -1
  184. package/dist/shared/index.d.ts +1 -0
  185. package/dist/shared/index.js +1 -0
  186. package/dist/shared/index.js.map +1 -1
  187. package/dist/shared/mapping.js +3 -1
  188. package/dist/shared/mapping.js.map +1 -1
  189. package/dist/shared/output-url.d.ts +2 -0
  190. package/dist/shared/output-url.js +50 -0
  191. package/dist/shared/output-url.js.map +1 -0
  192. package/dist/shared/rate-limit.js +2 -18
  193. package/dist/shared/rate-limit.js.map +1 -1
  194. package/dist/shared/response-body.d.ts +6 -0
  195. package/dist/shared/response-body.js +109 -0
  196. package/dist/shared/response-body.js.map +1 -0
  197. package/dist/shared/safe-fetch.d.ts +16 -0
  198. package/dist/shared/safe-fetch.js +300 -0
  199. package/dist/shared/safe-fetch.js.map +1 -0
  200. package/dist/shared/torrent-release-matching.d.ts +7 -0
  201. package/dist/shared/torrent-release-matching.js +143 -0
  202. package/dist/shared/torrent-release-matching.js.map +1 -0
  203. package/dist/shared/torrent-release-metadata.d.ts +4 -0
  204. package/dist/shared/torrent-release-metadata.js +95 -0
  205. package/dist/shared/torrent-release-metadata.js.map +1 -0
  206. package/dist/shikimori/index.js +4 -7
  207. package/dist/shikimori/index.js.map +1 -1
  208. package/dist/tvmaze/index.d.ts +11 -0
  209. package/dist/tvmaze/index.js +274 -0
  210. package/dist/tvmaze/index.js.map +1 -0
  211. package/dist/veoveo-streaming/client.d.ts +28 -0
  212. package/dist/veoveo-streaming/client.js +212 -0
  213. package/dist/veoveo-streaming/client.js.map +1 -0
  214. package/dist/veoveo-streaming/config.d.ts +32 -0
  215. package/dist/veoveo-streaming/config.js +67 -0
  216. package/dist/veoveo-streaming/config.js.map +1 -0
  217. package/dist/veoveo-streaming/index.d.ts +4 -0
  218. package/dist/veoveo-streaming/index.js +46 -0
  219. package/dist/veoveo-streaming/index.js.map +1 -0
  220. package/dist/veoveo-streaming/mapping.d.ts +3 -0
  221. package/dist/veoveo-streaming/mapping.js +121 -0
  222. package/dist/veoveo-streaming/mapping.js.map +1 -0
  223. package/dist/videohub-streaming/client.d.ts +33 -0
  224. package/dist/videohub-streaming/client.js +208 -0
  225. package/dist/videohub-streaming/client.js.map +1 -0
  226. package/dist/videohub-streaming/config.d.ts +34 -0
  227. package/dist/videohub-streaming/config.js +73 -0
  228. package/dist/videohub-streaming/config.js.map +1 -0
  229. package/dist/videohub-streaming/index.d.ts +4 -0
  230. package/dist/videohub-streaming/index.js +53 -0
  231. package/dist/videohub-streaming/index.js.map +1 -0
  232. package/dist/videohub-streaming/mapping.d.ts +3 -0
  233. package/dist/videohub-streaming/mapping.js +84 -0
  234. package/dist/videohub-streaming/mapping.js.map +1 -0
  235. package/dist/wikidata/cache.d.ts +11 -0
  236. package/dist/wikidata/cache.js +30 -0
  237. package/dist/wikidata/cache.js.map +1 -0
  238. package/dist/wikidata/candidates.d.ts +12 -0
  239. package/dist/wikidata/candidates.js +67 -0
  240. package/dist/wikidata/candidates.js.map +1 -0
  241. package/dist/wikidata/client.d.ts +40 -0
  242. package/dist/wikidata/client.js +224 -0
  243. package/dist/wikidata/client.js.map +1 -0
  244. package/dist/wikidata/index.d.ts +3 -0
  245. package/dist/wikidata/index.js +30 -104
  246. package/dist/wikidata/index.js.map +1 -1
  247. package/dist/yts-torrent/client.d.ts +25 -0
  248. package/dist/yts-torrent/client.js +163 -0
  249. package/dist/yts-torrent/client.js.map +1 -0
  250. package/dist/yts-torrent/config.d.ts +22 -0
  251. package/dist/yts-torrent/config.js +57 -0
  252. package/dist/yts-torrent/config.js.map +1 -0
  253. package/dist/yts-torrent/index.d.ts +4 -0
  254. package/dist/yts-torrent/index.js +42 -0
  255. package/dist/yts-torrent/index.js.map +1 -0
  256. package/dist/yts-torrent/mapping.d.ts +4 -0
  257. package/dist/yts-torrent/mapping.js +107 -0
  258. package/dist/yts-torrent/mapping.js.map +1 -0
  259. package/package.json +23 -5
@@ -0,0 +1,35 @@
1
+ import { rethrowIfProviderAborted } from "../shared/abort.js";
2
+ import { canResolveStrictTorrentQuery } from "../shared/torrent-release-matching.js";
3
+ import { searchMagnetzTorrents } from "./client.js";
4
+ import { createMagnetzTorrentCapabilities, createMagnetzTorrentConfig, } from "./config.js";
5
+ import { mapMagnetzTorrentResponse } from "./mapping.js";
6
+ import { selectMagnetzTorrentReleases } from "./matching.js";
7
+ export function magnetzTorrentProvider(options = {}) {
8
+ const config = createMagnetzTorrentConfig(options);
9
+ return {
10
+ name: config.name,
11
+ version: options.version,
12
+ kind: "torrent",
13
+ catalog: {
14
+ displayName: "Magnetz",
15
+ scope: "international",
16
+ },
17
+ capabilities: createMagnetzTorrentCapabilities(),
18
+ async discoverTorrents(query, context) {
19
+ if (query.providers && !query.providers.includes(config.name))
20
+ return null;
21
+ if (!canResolveStrictTorrentQuery(query))
22
+ return null;
23
+ try {
24
+ const releases = await searchMagnetzTorrents(config, query, context);
25
+ const matches = selectMagnetzTorrentReleases(releases, query);
26
+ return mapMagnetzTorrentResponse(config.name, config.baseUrl, matches, query);
27
+ }
28
+ catch (error) {
29
+ rethrowIfProviderAborted(context, error);
30
+ throw error;
31
+ }
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/magnetz-torrent/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,4BAA4B,EAAE,MAAM,uCAAuC,CAAC;AACrF,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,gCAAgC,EAChC,0BAA0B,GAE3B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAI7D,MAAM,UAAU,sBAAsB,CACpC,UAAyC,EAAE;IAE3C,MAAM,MAAM,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC;IAEnD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,SAAS;QACf,OAAO,EAAE;YACP,WAAW,EAAE,SAAS;YACtB,KAAK,EAAE,eAAe;SACvB;QACD,YAAY,EAAE,gCAAgC,EAAE;QAChD,KAAK,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO;YACnC,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC3E,IAAI,CAAC,4BAA4B,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YAEtD,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;gBACrE,MAAM,OAAO,GAAG,4BAA4B,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;gBAC9D,OAAO,yBAAyB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;YAChF,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"}
@@ -0,0 +1,3 @@
1
+ import type { TorrentDiscoveryQuery, TorrentDiscoveryResponse } from "@media-engine/core";
2
+ import type { MagnetzTorrentRelease } from "./client.js";
3
+ export declare function mapMagnetzTorrentResponse(provider: string, providerUrl: string, releases: MagnetzTorrentRelease[], query: TorrentDiscoveryQuery): TorrentDiscoveryResponse | null;
@@ -0,0 +1,52 @@
1
+ import { createCanonicalMagnetUri, createTorrentEpisodeRef, mapTorrentReleaseTitle, } from "../shared/torrent-release-metadata.js";
2
+ export function mapMagnetzTorrentResponse(provider, providerUrl, releases, query) {
3
+ const checkedAt = new Date().toISOString();
4
+ const candidates = deduplicateReleases(releases).map((release) => mapCandidate(provider, release, query, checkedAt));
5
+ if (candidates.length === 0)
6
+ return null;
7
+ return {
8
+ query,
9
+ item: {
10
+ type: query.type,
11
+ title: query.title,
12
+ year: query.year,
13
+ },
14
+ candidates,
15
+ sourceProviders: [{ provider, url: providerUrl }],
16
+ checkedAt,
17
+ };
18
+ }
19
+ function mapCandidate(provider, release, query, checkedAt) {
20
+ const episode = createTorrentEpisodeRef(query);
21
+ return {
22
+ id: `${provider}:${release.infoHash.toLowerCase()}`,
23
+ provider,
24
+ title: release.title,
25
+ infoHash: release.infoHash,
26
+ sizeBytes: release.sizeBytes,
27
+ publishedAt: release.createdAt,
28
+ ...(episode ? { episode } : {}),
29
+ release: mapTorrentReleaseTitle(release.title),
30
+ peers: {
31
+ seeders: release.seeders,
32
+ leechers: release.leechers,
33
+ checkedAt,
34
+ },
35
+ handoff: {
36
+ kind: "magnet",
37
+ uri: createCanonicalMagnetUri(release.infoHash, release.title),
38
+ },
39
+ availability: release.seeders > 0 ? "available" : "unseeded",
40
+ sourceUrl: release.sourceUrl,
41
+ };
42
+ }
43
+ function deduplicateReleases(releases) {
44
+ const seen = new Set();
45
+ return releases.filter((release) => {
46
+ if (seen.has(release.infoHash))
47
+ return false;
48
+ seen.add(release.infoHash);
49
+ return true;
50
+ });
51
+ }
52
+ //# sourceMappingURL=mapping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapping.js","sourceRoot":"","sources":["../../src/magnetz-torrent/mapping.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,uCAAuC,CAAC;AAG/C,MAAM,UAAU,yBAAyB,CACvC,QAAgB,EAChB,WAAmB,EACnB,QAAiC,EACjC,KAA4B;IAE5B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,UAAU,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAC/D,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAClD,CAAC;IAEF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzC,OAAO;QACL,KAAK;QACL,IAAI,EAAE;YACJ,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,IAAI,EAAE,KAAK,CAAC,IAAI;SACjB;QACD,UAAU;QACV,eAAe,EAAE,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC;QACjD,SAAS;KACV,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CACnB,QAAgB,EAChB,OAA8B,EAC9B,KAA4B,EAC5B,SAAiB;IAEjB,MAAM,OAAO,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;IAE/C,OAAO;QACL,EAAE,EAAE,GAAG,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE;QACnD,QAAQ;QACR,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,WAAW,EAAE,OAAO,CAAC,SAAS;QAC9B,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,KAAK,CAAC;QAC9C,KAAK,EAAE;YACL,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,SAAS;SACV;QACD,OAAO,EAAE;YACP,IAAI,EAAE,QAAQ;YACd,GAAG,EAAE,wBAAwB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC;SAC/D;QACD,YAAY,EAAE,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU;QAC5D,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAAC,QAAiC;IAC5D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE;QACjC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7C,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { TorrentDiscoveryQuery } from "@media-engine/core";
2
+ import type { MagnetzTorrentRelease } from "./client.js";
3
+ export declare function selectMagnetzTorrentReleases(releases: MagnetzTorrentRelease[], query: TorrentDiscoveryQuery): MagnetzTorrentRelease[];
@@ -0,0 +1,5 @@
1
+ import { selectStrictTorrentReleases } from "../shared/torrent-release-matching.js";
2
+ export function selectMagnetzTorrentReleases(releases, query) {
3
+ return selectStrictTorrentReleases(releases, query);
4
+ }
5
+ //# sourceMappingURL=matching.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matching.js","sourceRoot":"","sources":["../../src/magnetz-torrent/matching.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAC;AAGpF,MAAM,UAAU,4BAA4B,CAC1C,QAAiC,EACjC,KAA4B;IAE5B,OAAO,2BAA2B,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface MagnetzRequestGateOptions {
2
+ intervalMs: number;
3
+ now?: () => number;
4
+ }
5
+ export declare class MagnetzRequestGate {
6
+ #private;
7
+ constructor(options: MagnetzRequestGateOptions);
8
+ wait(signal?: AbortSignal): Promise<void>;
9
+ }
@@ -0,0 +1,48 @@
1
+ import { waitForAbortableDelay } from "../shared/abort.js";
2
+ const DEFAULT_NOW = Date.now;
3
+ // Serializes starts from one provider instance and leaves a small gap between them. The live API
4
+ // advertises a 30-request header but has also returned short burst 429 responses below that count.
5
+ export class MagnetzRequestGate {
6
+ #intervalMs;
7
+ #now;
8
+ #tail = Promise.resolve();
9
+ #nextStartAt = 0;
10
+ constructor(options) {
11
+ this.#intervalMs = options.intervalMs;
12
+ this.#now = options.now ?? DEFAULT_NOW;
13
+ }
14
+ async wait(signal) {
15
+ const previous = this.#tail;
16
+ let release = () => { };
17
+ const turn = new Promise((resolve) => {
18
+ release = resolve;
19
+ });
20
+ this.#tail = previous.then(() => turn);
21
+ try {
22
+ await waitForPromise(previous, signal);
23
+ await waitForAbortableDelay(Math.max(0, this.#nextStartAt - this.#now()), signal);
24
+ this.#nextStartAt = this.#now() + this.#intervalMs;
25
+ }
26
+ finally {
27
+ release();
28
+ }
29
+ }
30
+ }
31
+ function waitForPromise(promise, signal) {
32
+ if (!signal)
33
+ return promise;
34
+ if (signal.aborted)
35
+ return Promise.reject(signal.reason);
36
+ return new Promise((resolve, reject) => {
37
+ const onAbort = () => reject(signal.reason);
38
+ signal.addEventListener("abort", onAbort, { once: true });
39
+ void promise.then(() => {
40
+ signal.removeEventListener("abort", onAbort);
41
+ resolve();
42
+ }, (error) => {
43
+ signal.removeEventListener("abort", onAbort);
44
+ reject(error);
45
+ });
46
+ });
47
+ }
48
+ //# sourceMappingURL=request-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-gate.js","sourceRoot":"","sources":["../../src/magnetz-torrent/request-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC;AAO7B,iGAAiG;AACjG,mGAAmG;AACnG,MAAM,OAAO,kBAAkB;IACpB,WAAW,CAAS;IACpB,IAAI,CAAe;IAC5B,KAAK,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IACzC,YAAY,GAAG,CAAC,CAAC;IAEjB,YAAY,OAAkC;QAC5C,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAoB;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;QAC5B,IAAI,OAAO,GAAe,GAAG,EAAE,GAAE,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACzC,OAAO,GAAG,OAAO,CAAC;QACpB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAEvC,IAAI,CAAC;YACH,MAAM,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACvC,MAAM,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;YAClF,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;QACrD,CAAC;gBAAS,CAAC;YACT,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AAED,SAAS,cAAc,CAAC,OAAsB,EAAE,MAA+B;IAC7E,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC;IAC5B,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAEzD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,KAAK,OAAO,CAAC,IAAI,CACf,GAAG,EAAE;YACH,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,OAAO,EAAE,CAAC;QACZ,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;YACR,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const MEDIA_ENGINE_PROVIDERS_VERSION = "1.1.0";
2
+ export declare const MEDIA_ENGINE_DEFAULT_USER_AGENT = "MediaEngine/1.1.0 (https://github.com/Yaneart/media-engine)";
@@ -0,0 +1,5 @@
1
+ // Keep this runtime value aligned with packages/providers/package.json.
2
+ // scripts/check-release-consistency.mjs enforces the manifest/runtime contract.
3
+ export const MEDIA_ENGINE_PROVIDERS_VERSION = "1.1.0";
4
+ export const MEDIA_ENGINE_DEFAULT_USER_AGENT = `MediaEngine/${MEDIA_ENGINE_PROVIDERS_VERSION} (https://github.com/Yaneart/media-engine)`;
5
+ //# sourceMappingURL=package-version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-version.js","sourceRoot":"","sources":["../src/package-version.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,gFAAgF;AAChF,MAAM,CAAC,MAAM,8BAA8B,GAAG,OAAO,CAAC;AAEtD,MAAM,CAAC,MAAM,+BAA+B,GAAG,eAAe,8BAA8B,4CAA4C,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type ProviderContext } from "@media-engine/core";
2
+ import type { RutubeStreamingConfig } from "./config.js";
3
+ export interface RutubeMovieCandidate {
4
+ id: string;
5
+ title: string;
6
+ durationSeconds: number;
7
+ categoryId: number;
8
+ hidden: boolean;
9
+ deleted: boolean;
10
+ adult: boolean;
11
+ locked: boolean;
12
+ audio: boolean;
13
+ paid: boolean;
14
+ livestream: boolean;
15
+ }
16
+ export declare function searchRutubeMovies(config: RutubeStreamingConfig, title: string, year: number, context: ProviderContext): Promise<RutubeMovieCandidate[]>;
17
+ export declare function createRutubeSearchUrl(config: Pick<RutubeStreamingConfig, "baseUrl" | "searchResultLimit">, title: string, year: number): URL;
18
+ export declare function parseRutubeSearchResponse(provider: string, value: unknown, limit: number): RutubeMovieCandidate[];
@@ -0,0 +1,106 @@
1
+ import { ProviderError } from "@media-engine/core";
2
+ import { fetchJson } from "../shared/index.js";
3
+ const MAX_TITLE_LENGTH = 500;
4
+ const MAX_DURATION_SECONDS = 24 * 60 * 60;
5
+ const RUTUBE_VIDEO_ID = /^[a-f0-9]{32}$/iu;
6
+ export async function searchRutubeMovies(config, title, year, context) {
7
+ const url = createRutubeSearchUrl(config, title, year);
8
+ const payload = await fetchJson({
9
+ provider: config.name,
10
+ url,
11
+ context,
12
+ fetch: config.fetch,
13
+ rateLimitGate: config.rateLimitGate,
14
+ maxResponseBytes: config.maxResponseBytes,
15
+ init: {
16
+ headers: {
17
+ Accept: "application/json",
18
+ "User-Agent": config.userAgent,
19
+ },
20
+ },
21
+ });
22
+ return parseRutubeSearchResponse(config.name, payload, config.searchResultLimit);
23
+ }
24
+ export function createRutubeSearchUrl(config, title, year) {
25
+ const url = new URL("/api/search/video/", `${config.baseUrl}/`);
26
+ url.searchParams.set("content_type", "video");
27
+ url.searchParams.set("duration", "movie");
28
+ url.searchParams.set("limit", String(config.searchResultLimit));
29
+ url.searchParams.set("query", `${title.trim()} ${year}`);
30
+ return url;
31
+ }
32
+ export function parseRutubeSearchResponse(provider, value, limit) {
33
+ if (!isRecord(value) || !Array.isArray(value.results))
34
+ throw invalidResponse(provider);
35
+ const candidates = value.results.slice(0, limit).flatMap((entry) => {
36
+ const candidate = parseCandidate(entry);
37
+ return candidate ? [candidate] : [];
38
+ });
39
+ if (value.results.length > 0 && candidates.length === 0)
40
+ throw invalidResponse(provider);
41
+ return candidates;
42
+ }
43
+ function parseCandidate(value) {
44
+ if (!isRecord(value) || !isRecord(value.category))
45
+ return undefined;
46
+ const id = readVideoId(value.id);
47
+ const title = readString(value.title, MAX_TITLE_LENGTH);
48
+ const durationSeconds = readInteger(value.duration, 0, MAX_DURATION_SECONDS);
49
+ const categoryId = readInteger(value.category.id, 0, 10_000);
50
+ const flags = [
51
+ value.is_hidden,
52
+ value.is_deleted,
53
+ value.is_adult,
54
+ value.is_locked,
55
+ value.is_audio,
56
+ value.is_paid,
57
+ value.is_livestream,
58
+ ];
59
+ if (!id ||
60
+ !title ||
61
+ durationSeconds === undefined ||
62
+ categoryId === undefined ||
63
+ flags.some((flag) => typeof flag !== "boolean")) {
64
+ return undefined;
65
+ }
66
+ const [hidden, deleted, adult, locked, audio, paid, livestream] = flags;
67
+ return {
68
+ id,
69
+ title,
70
+ durationSeconds,
71
+ categoryId,
72
+ hidden: hidden,
73
+ deleted: deleted,
74
+ adult: adult,
75
+ locked: locked,
76
+ audio: audio,
77
+ paid: paid,
78
+ livestream: livestream,
79
+ };
80
+ }
81
+ function readVideoId(value) {
82
+ if (typeof value !== "string")
83
+ return undefined;
84
+ const id = value.trim().toLowerCase();
85
+ return RUTUBE_VIDEO_ID.test(id) ? id : undefined;
86
+ }
87
+ function readString(value, maxLength) {
88
+ return typeof value === "string" ? value.trim().slice(0, maxLength) || undefined : undefined;
89
+ }
90
+ function readInteger(value, min, max) {
91
+ return Number.isSafeInteger(value) && Number(value) >= min && Number(value) <= max
92
+ ? Number(value)
93
+ : undefined;
94
+ }
95
+ function isRecord(value) {
96
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
97
+ }
98
+ function invalidResponse(provider) {
99
+ return new ProviderError({
100
+ provider,
101
+ code: "PROVIDER_INVALID_RESPONSE",
102
+ message: `Provider "${provider}" returned an invalid Rutube search response.`,
103
+ retryable: false,
104
+ });
105
+ }
106
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/rutube-streaming/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAwB,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG/C,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAC1C,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAgB3C,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAA6B,EAC7B,KAAa,EACb,IAAY,EACZ,OAAwB;IAExB,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,MAAM,SAAS,CAAU;QACvC,QAAQ,EAAE,MAAM,CAAC,IAAI;QACrB,GAAG;QACH,OAAO;QACP,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;QACzC,IAAI,EAAE;YACJ,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,YAAY,EAAE,MAAM,CAAC,SAAS;aAC/B;SACF;KACF,CAAC,CAAC;IAEH,OAAO,yBAAyB,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,iBAAiB,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,MAAoE,EACpE,KAAa,EACb,IAAY;IAEZ,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,oBAAoB,EAAE,GAAG,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC;IAChE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IAC9C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC1C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAChE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACzD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,QAAgB,EAChB,KAAc,EACd,KAAa;IAEb,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;QAAE,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;IAEvF,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACjE,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACxC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;IACzF,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IAEpE,MAAM,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;IACxD,MAAM,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,EAAE,oBAAoB,CAAC,CAAC;IAC7E,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG;QACZ,KAAK,CAAC,SAAS;QACf,KAAK,CAAC,UAAU;QAChB,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,SAAS;QACf,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,OAAO;QACb,KAAK,CAAC,aAAa;KACpB,CAAC;IAEF,IACE,CAAC,EAAE;QACH,CAAC,KAAK;QACN,eAAe,KAAK,SAAS;QAC7B,UAAU,KAAK,SAAS;QACxB,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,SAAS,CAAC,EAC/C,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,GAAG,KAAkB,CAAC;IACrF,OAAO;QACL,EAAE;QACF,KAAK;QACL,eAAe;QACf,UAAU;QACV,MAAM,EAAE,MAAO;QACf,OAAO,EAAE,OAAQ;QACjB,KAAK,EAAE,KAAM;QACb,MAAM,EAAE,MAAO;QACf,KAAK,EAAE,KAAM;QACb,IAAI,EAAE,IAAK;QACX,UAAU,EAAE,UAAW;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACtC,OAAO,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACnD,CAAC;AAED,SAAS,UAAU,CAAC,KAAc,EAAE,SAAiB;IACnD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/F,CAAC;AAED,SAAS,WAAW,CAAC,KAAc,EAAE,GAAW,EAAE,GAAW;IAC3D,OAAO,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG;QAChF,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QACf,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,eAAe,CAAC,QAAgB;IACvC,OAAO,IAAI,aAAa,CAAC;QACvB,QAAQ;QACR,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,aAAa,QAAQ,+CAA+C;QAC7E,SAAS,EAAE,KAAK;KACjB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { StreamingProviderCapabilities } from "@media-engine/core";
2
+ import { ProviderRateLimitGate, type ProviderFetch } from "../shared/index.js";
3
+ export interface RutubeStreamingProviderOptions {
4
+ name?: string;
5
+ version?: string;
6
+ baseUrl?: string;
7
+ fetch?: ProviderFetch;
8
+ maxResponseBytes?: number;
9
+ searchResultLimit?: number;
10
+ minDurationSeconds?: number;
11
+ userAgent?: string;
12
+ now?: () => number;
13
+ }
14
+ export interface RutubeStreamingConfig {
15
+ name: string;
16
+ baseUrl: string;
17
+ fetch: ProviderFetch;
18
+ rateLimitGate: ProviderRateLimitGate;
19
+ maxResponseBytes: number;
20
+ searchResultLimit: number;
21
+ minDurationSeconds: number;
22
+ userAgent: string;
23
+ now: () => number;
24
+ }
25
+ export declare function createRutubeConfig(options: RutubeStreamingProviderOptions): RutubeStreamingConfig;
26
+ export declare function createRutubeCapabilities(): StreamingProviderCapabilities;
@@ -0,0 +1,57 @@
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 = "rutube-streaming";
6
+ const DEFAULT_BASE_URL = "https://rutube.ru";
7
+ const DEFAULT_MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
8
+ const DEFAULT_SEARCH_RESULT_LIMIT = 20;
9
+ const DEFAULT_MIN_DURATION_SECONDS = 3_000;
10
+ export function createRutubeConfig(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: 2 }),
16
+ rateLimitGate: new ProviderRateLimitGate(),
17
+ maxResponseBytes: resolveBoundedIntegerOption(options.maxResponseBytes, DEFAULT_MAX_RESPONSE_BYTES, "Rutube streaming maxResponseBytes", 1_024, 8 * 1024 * 1024),
18
+ searchResultLimit: resolveBoundedIntegerOption(options.searchResultLimit, DEFAULT_SEARCH_RESULT_LIMIT, "Rutube streaming searchResultLimit", 1, 50),
19
+ minDurationSeconds: resolveBoundedIntegerOption(options.minDurationSeconds, DEFAULT_MIN_DURATION_SECONDS, "Rutube streaming minDurationSeconds", 60, 12 * 60 * 60),
20
+ userAgent: options.userAgent?.trim() || MEDIA_ENGINE_DEFAULT_USER_AGENT,
21
+ now: options.now ?? Date.now,
22
+ };
23
+ }
24
+ export function createRutubeCapabilities() {
25
+ return {
26
+ mediaTypes: ["movie"],
27
+ lookup: {
28
+ byTitle: true,
29
+ byExternalIds: [],
30
+ byEpisode: false,
31
+ },
32
+ features: ["embed"],
33
+ };
34
+ }
35
+ function normalizeProviderName(value) {
36
+ const name = value.trim();
37
+ if (!name)
38
+ throw new TypeError("Rutube streaming provider name is required.");
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("Rutube streaming baseUrl must be a valid HTTPS URL.", { cause: error });
48
+ }
49
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "") {
50
+ throw new TypeError("Rutube streaming baseUrl must be a credential-free HTTPS URL.");
51
+ }
52
+ url.pathname = "/";
53
+ url.search = "";
54
+ url.hash = "";
55
+ return url.href.replace(/\/$/u, "");
56
+ }
57
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/rutube-streaming/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { StreamingProvider } from "@media-engine/core";
2
+ import { type RutubeStreamingProviderOptions } from "./config.js";
3
+ export type { RutubeStreamingProviderOptions } from "./config.js";
4
+ export declare function rutubeStreamingProvider(options?: RutubeStreamingProviderOptions): StreamingProvider;
@@ -0,0 +1,38 @@
1
+ import { rethrowIfProviderAborted } from "../shared/abort.js";
2
+ import { searchRutubeMovies } from "./client.js";
3
+ import { createRutubeCapabilities, createRutubeConfig, } from "./config.js";
4
+ import { mapRutubeAvailability } from "./mapping.js";
5
+ import { selectRutubeMovie } from "./matching.js";
6
+ export function rutubeStreamingProvider(options = {}) {
7
+ const config = createRutubeConfig(options);
8
+ return {
9
+ name: config.name,
10
+ version: options.version,
11
+ kind: "streaming",
12
+ capabilities: createRutubeCapabilities(),
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 searchRutubeMovies(config, query.title, query.year, context);
20
+ const candidate = selectRutubeMovie(candidates, query.title, query.year, config.minDurationSeconds);
21
+ return candidate ? mapRutubeAvailability(config, candidate, query) : null;
22
+ }
23
+ catch (error) {
24
+ rethrowIfProviderAborted(context, error);
25
+ throw error;
26
+ }
27
+ },
28
+ };
29
+ }
30
+ function canResolveQuery(query) {
31
+ return (query.type === "movie" &&
32
+ Boolean(query.title?.trim()) &&
33
+ Number.isInteger(query.year) &&
34
+ query.seasonNumber === undefined &&
35
+ query.episodeNumber === undefined &&
36
+ query.absoluteEpisodeNumber === undefined);
37
+ }
38
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rutube-streaming/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EACL,wBAAwB,EACxB,kBAAkB,GAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAIlD,MAAM,UAAU,uBAAuB,CACrC,UAA0C,EAAE;IAE5C,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAE3C,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,WAAW;QACjB,YAAY,EAAE,wBAAwB,EAAE;QACxC,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,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,KAAM,EAAE,KAAK,CAAC,IAAK,EAAE,OAAO,CAAC,CAAC;gBACxF,MAAM,SAAS,GAAG,iBAAiB,CACjC,UAAU,EACV,KAAK,CAAC,KAAM,EACZ,KAAK,CAAC,IAAK,EACX,MAAM,CAAC,kBAAkB,CAC1B,CAAC;gBACF,OAAO,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5E,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;QACjC,KAAK,CAAC,qBAAqB,KAAK,SAAS,CAC1C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { MediaAvailability } from "@media-engine/core";
2
+ import type { RutubeMovieCandidate } from "./client.js";
3
+ import type { RutubeStreamingConfig } from "./config.js";
4
+ export declare function mapRutubeAvailability(config: Pick<RutubeStreamingConfig, "name" | "baseUrl" | "now">, candidate: RutubeMovieCandidate, query: MediaAvailability["query"]): MediaAvailability | null;
@@ -0,0 +1,32 @@
1
+ import { normalizeProviderOutputUrl } from "../shared/index.js";
2
+ export function mapRutubeAvailability(config, candidate, query) {
3
+ const embedUrl = normalizeProviderOutputUrl(new URL(`/play/embed/${candidate.id}`, `${config.baseUrl}/`).href);
4
+ const sourceUrl = normalizeProviderOutputUrl(new URL(`/video/${candidate.id}/`, `${config.baseUrl}/`).href);
5
+ if (!embedUrl || !sourceUrl)
6
+ return null;
7
+ return {
8
+ query,
9
+ item: {
10
+ type: "movie",
11
+ title: query.title,
12
+ year: query.year,
13
+ },
14
+ options: [
15
+ {
16
+ id: `${config.name}:${candidate.id}`,
17
+ provider: config.name,
18
+ player: {
19
+ kind: "embed",
20
+ label: "Rutube",
21
+ providerPlayerId: candidate.id,
22
+ },
23
+ access: { url: embedUrl },
24
+ availability: "available",
25
+ sourceUrl,
26
+ },
27
+ ],
28
+ sourceProviders: [{ provider: config.name, url: sourceUrl }],
29
+ checkedAt: new Date(config.now()).toISOString(),
30
+ };
31
+ }
32
+ //# sourceMappingURL=mapping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapping.js","sourceRoot":"","sources":["../../src/rutube-streaming/mapping.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAIhE,MAAM,UAAU,qBAAqB,CACnC,MAA+D,EAC/D,SAA+B,EAC/B,KAAiC;IAEjC,MAAM,QAAQ,GAAG,0BAA0B,CACzC,IAAI,GAAG,CAAC,eAAe,SAAS,CAAC,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,IAAI,CAClE,CAAC;IACF,MAAM,SAAS,GAAG,0BAA0B,CAC1C,IAAI,GAAG,CAAC,UAAU,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,IAAI,CAC9D,CAAC;IACF,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAEzC,OAAO;QACL,KAAK;QACL,IAAI,EAAE;YACJ,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,IAAI,EAAE,KAAK,CAAC,IAAI;SACjB;QACD,OAAO,EAAE;YACP;gBACE,EAAE,EAAE,GAAG,MAAM,CAAC,IAAI,IAAI,SAAS,CAAC,EAAE,EAAE;gBACpC,QAAQ,EAAE,MAAM,CAAC,IAAI;gBACrB,MAAM,EAAE;oBACN,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE,QAAQ;oBACf,gBAAgB,EAAE,SAAS,CAAC,EAAE;iBAC/B;gBACD,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE;gBACzB,YAAY,EAAE,WAAW;gBACzB,SAAS;aACV;SACF;QACD,eAAe,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;QAC5D,SAAS,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE;KAChD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { RutubeMovieCandidate } from "./client.js";
2
+ export declare function selectRutubeMovie(candidates: RutubeMovieCandidate[], title: string, year: number, minDurationSeconds: number): RutubeMovieCandidate | undefined;
@@ -0,0 +1,37 @@
1
+ import { normalizeProviderSearchText } from "../shared/mapping.js";
2
+ const MOVIE_CATEGORY_ID = 4;
3
+ const GENERIC_MOVIE_WORDS = new Set(["film", "movie", "фильм", "кино"]);
4
+ export function selectRutubeMovie(candidates, title, year, minDurationSeconds) {
5
+ const normalizedTitle = normalizeProviderSearchText(title);
6
+ if (!normalizedTitle)
7
+ return undefined;
8
+ return candidates.find((candidate) => isPublicMovie(candidate, minDurationSeconds) &&
9
+ hasExactYear(candidate.title, year) &&
10
+ normalizeCandidateTitle(candidate.title, year) === normalizedTitle);
11
+ }
12
+ function isPublicMovie(candidate, minDurationSeconds) {
13
+ return (candidate.categoryId === MOVIE_CATEGORY_ID &&
14
+ candidate.durationSeconds >= minDurationSeconds &&
15
+ !candidate.hidden &&
16
+ !candidate.deleted &&
17
+ !candidate.adult &&
18
+ !candidate.locked &&
19
+ !candidate.audio &&
20
+ !candidate.paid &&
21
+ !candidate.livestream);
22
+ }
23
+ function hasExactYear(value, year) {
24
+ return new RegExp(`(?:^|\\D)${year}(?:\\D|$)`, "u").test(value);
25
+ }
26
+ function normalizeCandidateTitle(value, year) {
27
+ const words = normalizeProviderSearchText(value)
28
+ .split(" ")
29
+ .filter(Boolean)
30
+ .filter((word) => word !== String(year));
31
+ while (words.length > 0 && GENERIC_MOVIE_WORDS.has(words[0]))
32
+ words.shift();
33
+ while (words.length > 0 && GENERIC_MOVIE_WORDS.has(words.at(-1)))
34
+ words.pop();
35
+ return words.join(" ");
36
+ }
37
+ //# sourceMappingURL=matching.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matching.js","sourceRoot":"","sources":["../../src/rutube-streaming/matching.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AAEnE,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAExE,MAAM,UAAU,iBAAiB,CAC/B,UAAkC,EAClC,KAAa,EACb,IAAY,EACZ,kBAA0B;IAE1B,MAAM,eAAe,GAAG,2BAA2B,CAAC,KAAK,CAAC,CAAC;IAC3D,IAAI,CAAC,eAAe;QAAE,OAAO,SAAS,CAAC;IAEvC,OAAO,UAAU,CAAC,IAAI,CACpB,CAAC,SAAS,EAAE,EAAE,CACZ,aAAa,CAAC,SAAS,EAAE,kBAAkB,CAAC;QAC5C,YAAY,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC;QACnC,uBAAuB,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,eAAe,CACrE,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,SAA+B,EAAE,kBAA0B;IAChF,OAAO,CACL,SAAS,CAAC,UAAU,KAAK,iBAAiB;QAC1C,SAAS,CAAC,eAAe,IAAI,kBAAkB;QAC/C,CAAC,SAAS,CAAC,MAAM;QACjB,CAAC,SAAS,CAAC,OAAO;QAClB,CAAC,SAAS,CAAC,KAAK;QAChB,CAAC,SAAS,CAAC,MAAM;QACjB,CAAC,SAAS,CAAC,KAAK;QAChB,CAAC,SAAS,CAAC,IAAI;QACf,CAAC,SAAS,CAAC,UAAU,CACtB,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAa,EAAE,IAAY;IAC/C,OAAO,IAAI,MAAM,CAAC,YAAY,IAAI,WAAW,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAa,EAAE,IAAY;IAC1D,MAAM,KAAK,GAAG,2BAA2B,CAAC,KAAK,CAAC;SAC7C,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,OAAO,CAAC;SACf,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAE3C,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;QAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IAC7E,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC;QAAE,KAAK,CAAC,GAAG,EAAE,CAAC;IAC/E,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC"}
@@ -1,2 +1,3 @@
1
1
  import type { ProviderContext } from "@media-engine/core";
2
2
  export declare function rethrowIfProviderAborted(context: Pick<ProviderContext, "signal">, error: unknown): void;
3
+ export declare function waitForAbortableDelay(milliseconds: number, signal: AbortSignal | undefined): Promise<void>;
@@ -5,4 +5,26 @@ export function rethrowIfProviderAborted(context, error) {
5
5
  throw context.signal.reason ?? error;
6
6
  }
7
7
  }
8
+ // Waits without losing caller cancellation and removes the listener on normal completion.
9
+ // Ждёт с поддержкой отмены caller и удаляет listener после обычного завершения.
10
+ export function waitForAbortableDelay(milliseconds, signal) {
11
+ if (milliseconds <= 0) {
12
+ return signal?.aborted ? Promise.reject(signal.reason) : Promise.resolve();
13
+ }
14
+ return new Promise((resolve, reject) => {
15
+ if (signal?.aborted) {
16
+ reject(signal.reason);
17
+ return;
18
+ }
19
+ const timeout = setTimeout(() => {
20
+ signal?.removeEventListener("abort", onAbort);
21
+ resolve();
22
+ }, milliseconds);
23
+ const onAbort = () => {
24
+ clearTimeout(timeout);
25
+ reject(signal?.reason);
26
+ };
27
+ signal?.addEventListener("abort", onAbort, { once: true });
28
+ });
29
+ }
8
30
  //# sourceMappingURL=abort.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/shared/abort.ts"],"names":[],"mappings":"AAEA,+FAA+F;AAC/F,oGAAoG;AACpG,MAAM,UAAU,wBAAwB,CACtC,OAAwC,EACxC,KAAc;IAEd,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;QAC5B,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,KAAK,CAAC;IACvC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/shared/abort.ts"],"names":[],"mappings":"AAEA,+FAA+F;AAC/F,oGAAoG;AACpG,MAAM,UAAU,wBAAwB,CACtC,OAAwC,EACxC,KAAc;IAEd,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;QAC5B,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,KAAK,CAAC;IACvC,CAAC;AACH,CAAC;AAED,0FAA0F;AAC1F,gFAAgF;AAChF,MAAM,UAAU,qBAAqB,CACnC,YAAoB,EACpB,MAA+B;IAE/B,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7E,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtB,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,YAAY,CAAC,CAAC;QACjB,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzB,CAAC,CAAC;QACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -2,7 +2,10 @@ import { ProviderError, type ProviderErrorCode } from "@media-engine/core";
2
2
  import type { ProviderContext } from "@media-engine/core";
3
3
  import type { ProviderRateLimitGate } from "./rate-limit.js";
4
4
  export type ProviderFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
5
- export declare function normalizePublicHttpUrl(value: string | null | undefined): string | undefined;
5
+ export interface ProviderHttpScheduler {
6
+ setTimeout(callback: () => void, delayMs: number): unknown;
7
+ clearTimeout(handle: unknown): void;
8
+ }
6
9
  export interface FetchJsonOptions {
7
10
  provider: string;
8
11
  url: string | URL;
@@ -13,10 +16,13 @@ export interface FetchJsonOptions {
13
16
  retryDelayMs?: number;
14
17
  maxRetryDelayMs?: number;
15
18
  retryJitterRatio?: number;
19
+ maxResponseBytes?: number;
16
20
  rateLimitGate?: ProviderRateLimitGate;
21
+ scheduler?: ProviderHttpScheduler;
17
22
  }
18
23
  export declare function fetchJson<T>(options: FetchJsonOptions): Promise<T>;
19
- export declare function parseJsonResponse<T>(provider: string, response: Response): Promise<T>;
24
+ export declare function parseJsonResponse<T>(provider: string, response: Response, maxResponseBytes?: number, signal?: AbortSignal): Promise<T>;
20
25
  export declare function mapProviderHttpError(provider: string, error: unknown): ProviderError;
21
26
  export declare function mapHttpStatusToProviderErrorCode(status: number): ProviderErrorCode;
22
27
  export declare function mapHttpResponseToProviderError(provider: string, response: Response): ProviderError;
28
+ export declare function getProviderHttpStatus(error: unknown): number | undefined;