@media-engine/providers 0.1.1 → 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.
- package/README.md +132 -3
- package/README.ru.md +134 -3
- package/dist/aniliberty-streaming/client.d.ts +33 -0
- package/dist/aniliberty-streaming/client.js +188 -0
- package/dist/aniliberty-streaming/client.js.map +1 -0
- package/dist/aniliberty-streaming/config.d.ts +24 -0
- package/dist/aniliberty-streaming/config.js +61 -0
- package/dist/aniliberty-streaming/config.js.map +1 -0
- package/dist/aniliberty-streaming/index.d.ts +4 -0
- package/dist/aniliberty-streaming/index.js +45 -0
- package/dist/aniliberty-streaming/index.js.map +1 -0
- package/dist/aniliberty-streaming/mapping.d.ts +3 -0
- package/dist/aniliberty-streaming/mapping.js +87 -0
- package/dist/aniliberty-streaming/mapping.js.map +1 -0
- package/dist/aniliberty-streaming/matching.d.ts +4 -0
- package/dist/aniliberty-streaming/matching.js +44 -0
- package/dist/aniliberty-streaming/matching.js.map +1 -0
- package/dist/anilist/graphql.d.ts +2 -0
- package/dist/anilist/graphql.js +135 -0
- package/dist/anilist/graphql.js.map +1 -0
- package/dist/anilist/index.js +8 -6
- package/dist/anilist/index.js.map +1 -1
- package/dist/bitsearch-torrent/client.d.ts +19 -0
- package/dist/bitsearch-torrent/client.js +181 -0
- package/dist/bitsearch-torrent/client.js.map +1 -0
- package/dist/bitsearch-torrent/config.d.ts +26 -0
- package/dist/bitsearch-torrent/config.js +74 -0
- package/dist/bitsearch-torrent/config.js.map +1 -0
- package/dist/bitsearch-torrent/index.d.ts +4 -0
- package/dist/bitsearch-torrent/index.js +35 -0
- package/dist/bitsearch-torrent/index.js.map +1 -0
- package/dist/bitsearch-torrent/mapping.d.ts +3 -0
- package/dist/bitsearch-torrent/mapping.js +53 -0
- package/dist/bitsearch-torrent/mapping.js.map +1 -0
- package/dist/bitsearch-torrent/matching.d.ts +3 -0
- package/dist/bitsearch-torrent/matching.js +13 -0
- package/dist/bitsearch-torrent/matching.js.map +1 -0
- package/dist/bitsearch-torrent/quota.d.ts +9 -0
- package/dist/bitsearch-torrent/quota.js +53 -0
- package/dist/bitsearch-torrent/quota.js.map +1 -0
- package/dist/cinemeta/index.js +37 -12
- package/dist/cinemeta/index.js.map +1 -1
- package/dist/ddbb-streaming/client.d.ts +24 -0
- package/dist/ddbb-streaming/client.js +98 -0
- package/dist/ddbb-streaming/client.js.map +1 -0
- package/dist/ddbb-streaming/config.d.ts +31 -0
- package/dist/ddbb-streaming/config.js +67 -0
- package/dist/ddbb-streaming/config.js.map +1 -0
- package/dist/ddbb-streaming/index.d.ts +4 -0
- package/dist/ddbb-streaming/index.js +53 -0
- package/dist/ddbb-streaming/index.js.map +1 -0
- package/dist/ddbb-streaming/mapping.d.ts +8 -0
- package/dist/ddbb-streaming/mapping.js +164 -0
- package/dist/ddbb-streaming/mapping.js.map +1 -0
- package/dist/ddbb-streaming/validation.d.ts +3 -0
- package/dist/ddbb-streaming/validation.js +84 -0
- package/dist/ddbb-streaming/validation.js.map +1 -0
- package/dist/experimental-streaming/index.js +55 -0
- package/dist/experimental-streaming/index.js.map +1 -1
- package/dist/flixhq-streaming/client.d.ts +12 -0
- package/dist/flixhq-streaming/client.js +103 -0
- package/dist/flixhq-streaming/client.js.map +1 -0
- package/dist/flixhq-streaming/index.d.ts +2 -2
- package/dist/flixhq-streaming/index.js +58 -104
- package/dist/flixhq-streaming/index.js.map +1 -1
- package/dist/imdb-dataset/index.d.ts +20 -5
- package/dist/imdb-dataset/index.js +82 -184
- package/dist/imdb-dataset/index.js.map +1 -1
- package/dist/imdb-dataset/memory-storage.d.ts +7 -0
- package/dist/imdb-dataset/memory-storage.js +168 -0
- package/dist/imdb-dataset/memory-storage.js.map +1 -0
- package/dist/imdb-dataset/sqlite-import.d.ts +24 -0
- package/dist/imdb-dataset/sqlite-import.js +298 -0
- package/dist/imdb-dataset/sqlite-import.js.map +1 -0
- package/dist/imdb-dataset/sqlite-runtime.d.ts +3 -0
- package/dist/imdb-dataset/sqlite-runtime.js +11 -0
- package/dist/imdb-dataset/sqlite-runtime.js.map +1 -0
- package/dist/imdb-dataset/sqlite-schema.d.ts +6 -0
- package/dist/imdb-dataset/sqlite-schema.js +88 -0
- package/dist/imdb-dataset/sqlite-schema.js.map +1 -0
- package/dist/imdb-dataset/sqlite-storage.d.ts +12 -0
- package/dist/imdb-dataset/sqlite-storage.js +208 -0
- package/dist/imdb-dataset/sqlite-storage.js.map +1 -0
- package/dist/imdb-dataset/storage.d.ts +33 -0
- package/dist/imdb-dataset/storage.js +2 -0
- package/dist/imdb-dataset/storage.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/jacred-torrent/client.d.ts +24 -0
- package/dist/jacred-torrent/client.js +254 -0
- package/dist/jacred-torrent/client.js.map +1 -0
- package/dist/jacred-torrent/config.d.ts +24 -0
- package/dist/jacred-torrent/config.js +70 -0
- package/dist/jacred-torrent/config.js.map +1 -0
- package/dist/jacred-torrent/index.d.ts +4 -0
- package/dist/jacred-torrent/index.js +45 -0
- package/dist/jacred-torrent/index.js.map +1 -0
- package/dist/jacred-torrent/mapping.d.ts +3 -0
- package/dist/jacred-torrent/mapping.js +129 -0
- package/dist/jacred-torrent/mapping.js.map +1 -0
- package/dist/jacred-torrent/matching.d.ts +3 -0
- package/dist/jacred-torrent/matching.js +19 -0
- package/dist/jacred-torrent/matching.js.map +1 -0
- package/dist/kinobd/index.js +5 -6
- package/dist/kinobd/index.js.map +1 -1
- package/dist/kinobd-streaming/availability.js +52 -29
- package/dist/kinobd-streaming/availability.js.map +1 -1
- package/dist/kinobd-streaming/candidates.d.ts +3 -2
- package/dist/kinobd-streaming/candidates.js +89 -37
- package/dist/kinobd-streaming/candidates.js.map +1 -1
- package/dist/kinobd-streaming/config.d.ts +15 -0
- package/dist/kinobd-streaming/config.js +21 -17
- package/dist/kinobd-streaming/config.js.map +1 -1
- package/dist/kinobd-streaming/index.d.ts +1 -1
- package/dist/kinobd-streaming/index.js.map +1 -1
- package/dist/kinobd-streaming/players.d.ts +2 -1
- package/dist/kinobd-streaming/players.js +7 -7
- package/dist/kinobd-streaming/players.js.map +1 -1
- package/dist/kinobd-streaming/request-budget.d.ts +15 -0
- package/dist/kinobd-streaming/request-budget.js +61 -0
- package/dist/kinobd-streaming/request-budget.js.map +1 -0
- package/dist/kinobd-streaming/validation.d.ts +5 -3
- package/dist/kinobd-streaming/validation.js +78 -49
- package/dist/kinobd-streaming/validation.js.map +1 -1
- package/dist/magnetz-torrent/client.d.ts +16 -0
- package/dist/magnetz-torrent/client.js +235 -0
- package/dist/magnetz-torrent/client.js.map +1 -0
- package/dist/magnetz-torrent/config.d.ts +27 -0
- package/dist/magnetz-torrent/config.js +73 -0
- package/dist/magnetz-torrent/config.js.map +1 -0
- package/dist/magnetz-torrent/index.d.ts +4 -0
- package/dist/magnetz-torrent/index.js +35 -0
- package/dist/magnetz-torrent/index.js.map +1 -0
- package/dist/magnetz-torrent/mapping.d.ts +3 -0
- package/dist/magnetz-torrent/mapping.js +52 -0
- package/dist/magnetz-torrent/mapping.js.map +1 -0
- package/dist/magnetz-torrent/matching.d.ts +3 -0
- package/dist/magnetz-torrent/matching.js +5 -0
- package/dist/magnetz-torrent/matching.js.map +1 -0
- package/dist/magnetz-torrent/request-gate.d.ts +9 -0
- package/dist/magnetz-torrent/request-gate.js +48 -0
- package/dist/magnetz-torrent/request-gate.js.map +1 -0
- package/dist/package-version.d.ts +2 -0
- package/dist/package-version.js +5 -0
- package/dist/package-version.js.map +1 -0
- package/dist/shared/abort.d.ts +1 -0
- package/dist/shared/abort.js +22 -0
- package/dist/shared/abort.js.map +1 -1
- package/dist/shared/http.d.ts +8 -2
- package/dist/shared/http.js +37 -67
- package/dist/shared/http.js.map +1 -1
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/mapping.js +3 -1
- package/dist/shared/mapping.js.map +1 -1
- package/dist/shared/output-url.d.ts +2 -0
- package/dist/shared/output-url.js +50 -0
- package/dist/shared/output-url.js.map +1 -0
- package/dist/shared/rate-limit.js +2 -18
- package/dist/shared/rate-limit.js.map +1 -1
- package/dist/shared/response-body.d.ts +6 -0
- package/dist/shared/response-body.js +109 -0
- package/dist/shared/response-body.js.map +1 -0
- package/dist/shared/safe-fetch.d.ts +14 -0
- package/dist/shared/safe-fetch.js +274 -0
- package/dist/shared/safe-fetch.js.map +1 -0
- package/dist/shared/torrent-release-matching.d.ts +7 -0
- package/dist/shared/torrent-release-matching.js +143 -0
- package/dist/shared/torrent-release-matching.js.map +1 -0
- package/dist/shared/torrent-release-metadata.d.ts +4 -0
- package/dist/shared/torrent-release-metadata.js +95 -0
- package/dist/shared/torrent-release-metadata.js.map +1 -0
- package/dist/shikimori/index.js +4 -7
- package/dist/shikimori/index.js.map +1 -1
- package/dist/tvmaze/index.d.ts +11 -0
- package/dist/tvmaze/index.js +274 -0
- package/dist/tvmaze/index.js.map +1 -0
- package/dist/wikidata/cache.d.ts +11 -0
- package/dist/wikidata/cache.js +30 -0
- package/dist/wikidata/cache.js.map +1 -0
- package/dist/wikidata/candidates.d.ts +12 -0
- package/dist/wikidata/candidates.js +67 -0
- package/dist/wikidata/candidates.js.map +1 -0
- package/dist/wikidata/client.d.ts +40 -0
- package/dist/wikidata/client.js +224 -0
- package/dist/wikidata/client.js.map +1 -0
- package/dist/wikidata/index.d.ts +3 -0
- package/dist/wikidata/index.js +30 -104
- package/dist/wikidata/index.js.map +1 -1
- package/dist/yts-torrent/client.d.ts +25 -0
- package/dist/yts-torrent/client.js +163 -0
- package/dist/yts-torrent/client.js.map +1 -0
- package/dist/yts-torrent/config.d.ts +22 -0
- package/dist/yts-torrent/config.js +57 -0
- package/dist/yts-torrent/config.js.map +1 -0
- package/dist/yts-torrent/index.d.ts +4 -0
- package/dist/yts-torrent/index.js +42 -0
- package/dist/yts-torrent/index.js.map +1 -0
- package/dist/yts-torrent/mapping.d.ts +4 -0
- package/dist/yts-torrent/mapping.js +107 -0
- package/dist/yts-torrent/mapping.js.map +1 -0
- package/package.json +18 -5
package/README.md
CHANGED
|
@@ -16,14 +16,16 @@ npm install @media-engine/core @media-engine/providers
|
|
|
16
16
|
import { MediaEngine } from "@media-engine/core";
|
|
17
17
|
import {
|
|
18
18
|
aniListProvider,
|
|
19
|
+
ddbbStreamingProvider,
|
|
19
20
|
flixHqStreamingProvider,
|
|
20
21
|
kinobdProvider,
|
|
21
22
|
kinobdStreamingProvider,
|
|
22
23
|
shikimoriProvider,
|
|
24
|
+
tvMazeProvider,
|
|
23
25
|
} from "@media-engine/providers";
|
|
24
26
|
|
|
25
27
|
const media = new MediaEngine({
|
|
26
|
-
providers: [kinobdProvider(), shikimoriProvider(), aniListProvider()],
|
|
28
|
+
providers: [kinobdProvider(), shikimoriProvider(), aniListProvider(), tvMazeProvider()],
|
|
27
29
|
});
|
|
28
30
|
|
|
29
31
|
const result = await media.search({ title: "One Piece" });
|
|
@@ -37,15 +39,34 @@ Add only the providers that make sense for your application. Media Engine will c
|
|
|
37
39
|
- `cinemetaProvider()` — IMDb-linked movie and series data;
|
|
38
40
|
- `shikimoriProvider()` — anime search and details;
|
|
39
41
|
- `aniListProvider()` — international anime titles, popularity, and artwork;
|
|
40
|
-
- `
|
|
41
|
-
- `
|
|
42
|
+
- `tvMazeProvider()` — fallback IMDb-backed series identities and localized aliases;
|
|
43
|
+
- `wikidataProvider()` — fallback structured identity and metadata enrichment;
|
|
44
|
+
- `imdbDatasetProvider()` — local IMDb data supplied as small in-memory TSV fixtures or through an application-owned indexed storage adapter.
|
|
42
45
|
|
|
43
46
|
None of these built-in providers needs your API key. TMDB IDs may appear in results, but this package does not call the TMDB API.
|
|
44
47
|
|
|
48
|
+
TVmaze data is licensed under CC BY-SA. The provider keeps a TVmaze show URL in source attribution; applications using TVmaze data should preserve and render that link. See the [TVmaze API licensing terms](https://www.tvmaze.com/api#licensing).
|
|
49
|
+
|
|
50
|
+
Wikidata fallback discovery loads at most three title-relevant entities through a selected-property query and caches entity/IMDb mappings for six hours in a 256-entry process-local LRU by default. `entityLimit` is bounded to 1–10, `cacheTtlMs` to 0–7 days, and `cacheMaxEntries` to 2–2048; a zero TTL disables this provider-local cache.
|
|
51
|
+
|
|
52
|
+
The backward-compatible IMDb TSV adapter parses the complete input into memory and is intended for small datasets and fixtures. Full-dataset integrations can inject the exported synchronous/asynchronous `ImdbDatasetStorage` contract, including a direct ID lookup and bounded normalized title search, without adding a database dependency for other package users.
|
|
53
|
+
|
|
54
|
+
An included persisted adapter can stream plain or gzip IMDb TSV files into a versioned, atomically replaced SQLite/FTS index. `buildImdbDatasetSqliteIndex()` creates it and `openImdbDatasetSqliteStorage()` opens it read-only for `imdbDatasetProvider({ storage })`. This optional path lazily uses built-in `node:sqlite` and requires Node.js 22.13 or newer; importing the package or using the small in-memory adapter keeps the Node.js 20 baseline.
|
|
55
|
+
|
|
56
|
+
Expected upstream failures are reported as typed `ProviderError` values, and shared HTTP errors expose their originating status through `getProviderHttpStatus`. An untyped Cinemeta IMDb lookup returns `null` only after both movie and series candidates confirm absence; a temporary branch outage remains retryable unless the other branch returned usable details. AniList similarly distinguishes GraphQL rate limits and server outages from validation errors or malformed payloads, allowing Media Engine to avoid caching incomplete metadata as a healthy response.
|
|
57
|
+
|
|
58
|
+
Shared `fetchJson` calls stream at most 4 MiB by default before parsing and accept a positive `maxResponseBytes` override for provider-specific limits. A declared or chunked oversized body is cancelled and reported as the non-retryable `PROVIDER_RESPONSE_TOO_LARGE`; malformed JSON within the limit remains `PROVIDER_INVALID_RESPONSE`.
|
|
59
|
+
|
|
60
|
+
Low-level adapters may supply a `ProviderHttpScheduler` through `FetchJsonOptions.scheduler` when
|
|
61
|
+
they need deterministic control of retry and total-timeout timers, especially in tests. Normal
|
|
62
|
+
provider calls omit it and use the platform timers.
|
|
63
|
+
|
|
45
64
|
## Player sources
|
|
46
65
|
|
|
47
66
|
- `kinobdStreamingProvider()` — movie, series, and anime player options;
|
|
48
67
|
- `flixHqStreamingProvider()` — international movie and selected series-episode options;
|
|
68
|
+
- `ddbbStreamingProvider()` — opt-in Kinopoisk/IMDb lookup through an independent DDBB player route;
|
|
69
|
+
- `aniLibertyStreamingProvider()` — opt-in exact title/year anime lookup with direct HLS episodes;
|
|
49
70
|
- `experimentalStreamingProvider()` — data configured by your own application, useful in tests and UI work.
|
|
50
71
|
|
|
51
72
|
```ts
|
|
@@ -66,6 +87,114 @@ const result = await media.getAvailability({
|
|
|
66
87
|
|
|
67
88
|
These are third-party player targets, not videos hosted by Media Engine. Availability depends on the upstream source and the user's environment.
|
|
68
89
|
|
|
90
|
+
The repository API enables `ddbbStreamingProvider()` after its repeated reliability/diversity
|
|
91
|
+
checkpoint; direct package consumers still choose their own provider list explicitly. It accepts
|
|
92
|
+
only Kinopoisk or IMDb IDs, returns generic movie/series/anime embeds, and does not claim exact
|
|
93
|
+
season/episode mapping. Its diversity-first mapping keeps one main option per returned player before
|
|
94
|
+
adding unique translation URLs. Missing nullable players produce no result; confirmed 404/410 or
|
|
95
|
+
stable deletion markers are removed, while transient validation failures remain `unknown`.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const media = new MediaEngine({
|
|
99
|
+
streamingProviders: [
|
|
100
|
+
kinobdStreamingProvider(),
|
|
101
|
+
flixHqStreamingProvider(),
|
|
102
|
+
ddbbStreamingProvider(), // explicit opt-in
|
|
103
|
+
aniLibertyStreamingProvider(), // explicit opt-in
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The repository API also enables `aniLibertyStreamingProvider()` after that checkpoint. Because
|
|
109
|
+
AniLiberty does not publish MAL, AniList, or Shikimori IDs for releases, the adapter requires both
|
|
110
|
+
title and year, accepts only one exact normalized match, and revalidates the loaded release before
|
|
111
|
+
returning streams. It supports generic episode maps and exact `absoluteEpisodeNumber` queries, but
|
|
112
|
+
does not guess season/episode mappings. Each safe first-party 480p/720p/1080p URL is returned as
|
|
113
|
+
direct HLS; release geo and copyright blocks are preserved as normalized availability states.
|
|
114
|
+
|
|
115
|
+
Live player validation removes an option only after HTTP 404/410 or a stable deletion marker. Rate limits, server errors, network failures, and validation timeouts keep the discovered option with `availability: "unknown"`, allowing the engine to expose the degradation and retry it instead of caching a transiently reduced result.
|
|
116
|
+
|
|
117
|
+
KinoBD bounds one availability lookup to 24 child HTTP attempts by default and validates at most eight discovered players through three workers. Public tuning remains bounded (`childRequestLimit` up to 64, `playerValidationLimit` up to 16, and `playerValidationConcurrency` up to 4). Nested iframe validation starts only when the fixed provider deadline can still grant a full validation window. `onPlayerAudit` receives additive `metrics` for discovered and validated players, limit/budget skips, transient unknown results, confirmed removals, and consumed child requests.
|
|
118
|
+
|
|
119
|
+
FlixHQ site navigation cannot leave its configured origin, including through redirects. External player and subtitle checks resolve every A/AAAA answer, reject private, local, reserved, multicast, or mixed public/private destinations, validate every bounded redirect hop, and pin the connection to the approved address. A custom provider `fetch` is an explicit trusted transport injection intended for controlled tests or self-hosted environments; it is responsible for equivalent network policy.
|
|
120
|
+
|
|
121
|
+
DDBB caps its JSON response, output option count, live validation count, validation concurrency,
|
|
122
|
+
validation body size, and per-player timeout. Its default transport applies the same hardened DNS,
|
|
123
|
+
redirect, and connection-pinning policy to the DDBB endpoint and returned players. A custom `fetch`
|
|
124
|
+
is the same explicit trusted test/self-hosted boundary used by the other streaming providers. Options
|
|
125
|
+
beyond the bounded validation count remain visible with `availability: "unknown"`; they are never
|
|
126
|
+
presented as successfully checked. Alloha is represented by one main option because voiceovers can
|
|
127
|
+
be switched inside that player; if no main iframe exists, at most one safe translation URL becomes
|
|
128
|
+
the entry point. Other player families keep their distinct translation options.
|
|
129
|
+
|
|
130
|
+
AniLiberty bounds search candidates, release episodes, JSON bytes, retries, and total provider time
|
|
131
|
+
through the shared engine/provider controls. Its default transport uses the hardened DNS, redirect,
|
|
132
|
+
and connection-pinning policy for API calls. Direct HLS targets still pass the shared browser-facing
|
|
133
|
+
output URL policy; playback network policy remains the consuming application's responsibility.
|
|
134
|
+
|
|
135
|
+
Before built-in providers expose artwork, player, or subtitle URLs, one output policy accepts only HTTP(S) targets without credentials, raw control characters, or literal local/private/reserved addresses. Valid paths and CDN query parameters, including expiring signatures, are preserved. This browser-facing check does not replace DNS validation or an application-owned media proxy.
|
|
136
|
+
|
|
137
|
+
## Torrent discovery sources
|
|
138
|
+
|
|
139
|
+
`ytsTorrentProvider()` is an opt-in no-key movie source. It resolves an exact IMDb identity or one
|
|
140
|
+
exact title/year match and returns normalized 720p/1080p/2160p-style magnet candidates with size,
|
|
141
|
+
release metadata, and best-effort peer counts.
|
|
142
|
+
|
|
143
|
+
`jacRedTorrentProvider()` is an opt-in no-key Russian/multilingual source for exact title/year
|
|
144
|
+
movie, series, and anime lookup. It can filter a requested season, but intentionally returns no
|
|
145
|
+
result for exact ordinary or absolute episode queries. Returned title, year, category, season,
|
|
146
|
+
info hash, source URL, release metadata, and peer fields are bounded and revalidated. Each result
|
|
147
|
+
also retains the bounded JacRed tracker ID as `catalogSource`; known values receive labels such as
|
|
148
|
+
BitRu, RuTracker, RuTor, Kinozal, NNM-Club, and Knaben without implying a guaranteed audio
|
|
149
|
+
language. Its
|
|
150
|
+
`baseUrl` and `searchPath` are configurable because the live first-party route and published route
|
|
151
|
+
currently differ.
|
|
152
|
+
|
|
153
|
+
`bitsearchTorrentProvider()` is an opt-in no-key broad international source. It requires title and
|
|
154
|
+
year, pins movie/TV/anime categories, and revalidates the exact release title, explicit year, media
|
|
155
|
+
type, requested season, and requested ordinary or absolute episode before returning a candidate.
|
|
156
|
+
It consumes only the documented bounded search response, remembers an exhausted anonymous quota
|
|
157
|
+
from `X-RateLimit-*`, and emits one canonical magnet per validated info hash. The public anonymous
|
|
158
|
+
tier is currently limited to 200 requests/day per IP, so applications should keep engine caching
|
|
159
|
+
enabled and avoid speculative calls.
|
|
160
|
+
|
|
161
|
+
`magnetzTorrentProvider()` is an opt-in no-key international meta-search source. It performs one
|
|
162
|
+
bounded first-page search request, then revalidates exact title, year, season, and ordinary or
|
|
163
|
+
absolute episode markers locally. It never fans out into per-result detail calls. The adapter emits
|
|
164
|
+
one canonical magnet per validated info hash, preserves reported peer state and source attribution,
|
|
165
|
+
and spaces request starts by one second because the live service has returned short burst 429s even
|
|
166
|
+
while its rate-limit header still reported capacity.
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const media = new MediaEngine({
|
|
170
|
+
torrentProviders: [
|
|
171
|
+
ytsTorrentProvider(),
|
|
172
|
+
jacRedTorrentProvider(),
|
|
173
|
+
bitsearchTorrentProvider(),
|
|
174
|
+
magnetzTorrentProvider(),
|
|
175
|
+
],
|
|
176
|
+
providerTimeouts: {
|
|
177
|
+
"yts-torrent": 15_000,
|
|
178
|
+
"jacred-torrent": 20_000,
|
|
179
|
+
"bitsearch-torrent": 15_000,
|
|
180
|
+
"magnetz-torrent": 15_000,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const torrents = await media.discoverTorrents({
|
|
185
|
+
type: "movie",
|
|
186
|
+
title: "Inception",
|
|
187
|
+
year: 2010,
|
|
188
|
+
ids: { imdb: "tt1375666" },
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The provider does not download torrent metadata, contact trackers, join a swarm, or play video.
|
|
193
|
+
The completed multi-source checkpoint kept repository API defaults empty: Bitsearch has a small
|
|
194
|
+
anonymous daily quota, Magnetz exposes burst-sensitive limits, and JacRed still has an occasional
|
|
195
|
+
long timeout tail. Applications can opt into any subset explicitly and monitor it with
|
|
196
|
+
`pnpm smoke:torrents`; package consumers remain in control of their own request budget.
|
|
197
|
+
|
|
69
198
|
Provider options, limitations, and safety rules are summarized in the [provider guide](https://github.com/Yaneart/media-engine/blob/main/docs/providers.md).
|
|
70
199
|
|
|
71
200
|
## License
|
package/README.ru.md
CHANGED
|
@@ -16,14 +16,16 @@ npm install @media-engine/core @media-engine/providers
|
|
|
16
16
|
import { MediaEngine } from "@media-engine/core";
|
|
17
17
|
import {
|
|
18
18
|
aniListProvider,
|
|
19
|
+
ddbbStreamingProvider,
|
|
19
20
|
flixHqStreamingProvider,
|
|
20
21
|
kinobdProvider,
|
|
21
22
|
kinobdStreamingProvider,
|
|
22
23
|
shikimoriProvider,
|
|
24
|
+
tvMazeProvider,
|
|
23
25
|
} from "@media-engine/providers";
|
|
24
26
|
|
|
25
27
|
const media = new MediaEngine({
|
|
26
|
-
providers: [kinobdProvider(), shikimoriProvider(), aniListProvider()],
|
|
28
|
+
providers: [kinobdProvider(), shikimoriProvider(), aniListProvider(), tvMazeProvider()],
|
|
27
29
|
});
|
|
28
30
|
|
|
29
31
|
const result = await media.search({ title: "Ван-Пис" });
|
|
@@ -37,15 +39,35 @@ const result = await media.search({ title: "Ван-Пис" });
|
|
|
37
39
|
- `cinemetaProvider()` — данные фильмов и сериалов, связанные с IMDb;
|
|
38
40
|
- `shikimoriProvider()` — поиск и детали аниме;
|
|
39
41
|
- `aniListProvider()` — международные названия аниме, популярность и изображения;
|
|
40
|
-
- `
|
|
41
|
-
- `
|
|
42
|
+
- `tvMazeProvider()` — резервные IMDb identity сериалов и локализованные aliases;
|
|
43
|
+
- `wikidataProvider()` — резервная проверка identity и обогащение открытыми структурированными данными;
|
|
44
|
+
- `imdbDatasetProvider()` — локальные данные IMDb в виде небольших in-memory TSV fixtures или через индексированный storage adapter приложения.
|
|
42
45
|
|
|
43
46
|
Для встроенных провайдеров не нужен ваш API-ключ. TMDB ID может встретиться в результате, но сам пакет не обращается к TMDB API.
|
|
44
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
|
+
|
|
45
65
|
## Провайдеры плееров
|
|
46
66
|
|
|
47
67
|
- `kinobdStreamingProvider()` — варианты плееров для фильмов, сериалов и аниме;
|
|
48
68
|
- `flixHqStreamingProvider()` — международные варианты для фильмов и выбранных эпизодов сериалов;
|
|
69
|
+
- `ddbbStreamingProvider()` — opt-in поиск по Kinopoisk/IMDb через независимый маршрут DDBB;
|
|
70
|
+
- `aniLibertyStreamingProvider()` — opt-in точный поиск аниме по названию/году с прямыми HLS-сериями;
|
|
49
71
|
- `experimentalStreamingProvider()` — данные, настроенные вашим приложением для тестов и разработки интерфейса.
|
|
50
72
|
|
|
51
73
|
```ts
|
|
@@ -66,6 +88,115 @@ const result = await media.getAvailability({
|
|
|
66
88
|
|
|
67
89
|
Это ссылки на сторонние плееры, а не видео, размещённые Media Engine. Доступность зависит от внешнего источника и окружения пользователя.
|
|
68
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
|
+
|
|
69
200
|
Настройки, ограничения и правила безопасности кратко описаны в [документации провайдеров](https://github.com/Yaneart/media-engine/blob/main/docs/providers.md).
|
|
70
201
|
|
|
71
202
|
## Лицензия
|
|
@@ -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;
|