@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,73 @@
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 = "videohub-streaming";
6
+ const DEFAULT_BASE_URL = "https://plapi.cdnvideohub.com";
7
+ const DEFAULT_MAX_PLAYLIST_RESPONSE_BYTES = 2 * 1024 * 1024;
8
+ const DEFAULT_MAX_VIDEO_RESPONSE_BYTES = 256 * 1024;
9
+ const DEFAULT_PLAYLIST_ITEM_LIMIT = 1_000;
10
+ const DEFAULT_VIDEO_LOOKUP_LIMIT = 8;
11
+ const DEFAULT_VIDEO_LOOKUP_CONCURRENCY = 4;
12
+ const DEFAULT_VIDEO_LOOKUP_TIMEOUT_MS = 8_000;
13
+ const DEFAULT_LINK_TTL_MS = 5 * 60_000;
14
+ const DEFAULT_ADDRESS_ATTEMPT_TIMEOUT_MS = 3_500;
15
+ export function createVideoHubConfig(options) {
16
+ const name = normalizeProviderName(options.name ?? DEFAULT_PROVIDER_NAME);
17
+ return {
18
+ name,
19
+ baseUrl: normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL),
20
+ fetch: options.fetch ??
21
+ createHardenedProviderFetch({
22
+ provider: name,
23
+ maxRedirects: 2,
24
+ addressAttemptTimeoutMs: DEFAULT_ADDRESS_ATTEMPT_TIMEOUT_MS,
25
+ }),
26
+ rateLimitGate: new ProviderRateLimitGate(),
27
+ maxPlaylistResponseBytes: resolveBoundedIntegerOption(options.maxPlaylistResponseBytes, DEFAULT_MAX_PLAYLIST_RESPONSE_BYTES, "VideoHUB streaming maxPlaylistResponseBytes", 1_024, 16 * 1024 * 1024),
28
+ maxVideoResponseBytes: resolveBoundedIntegerOption(options.maxVideoResponseBytes, DEFAULT_MAX_VIDEO_RESPONSE_BYTES, "VideoHUB streaming maxVideoResponseBytes", 1_024, 4 * 1024 * 1024),
29
+ playlistItemLimit: resolveBoundedIntegerOption(options.playlistItemLimit, DEFAULT_PLAYLIST_ITEM_LIMIT, "VideoHUB streaming playlistItemLimit", 1, 5_000),
30
+ videoLookupLimit: resolveBoundedIntegerOption(options.videoLookupLimit, DEFAULT_VIDEO_LOOKUP_LIMIT, "VideoHUB streaming videoLookupLimit", 1, 32),
31
+ videoLookupConcurrency: resolveBoundedIntegerOption(options.videoLookupConcurrency, DEFAULT_VIDEO_LOOKUP_CONCURRENCY, "VideoHUB streaming videoLookupConcurrency", 1, 8),
32
+ videoLookupTimeoutMs: resolveBoundedIntegerOption(options.videoLookupTimeoutMs, DEFAULT_VIDEO_LOOKUP_TIMEOUT_MS, "VideoHUB streaming videoLookupTimeoutMs", 500, 15_000),
33
+ linkTtlMs: resolveBoundedIntegerOption(options.linkTtlMs, DEFAULT_LINK_TTL_MS, "VideoHUB streaming linkTtlMs", 30_000, 60 * 60_000),
34
+ userAgent: options.userAgent?.trim() || MEDIA_ENGINE_DEFAULT_USER_AGENT,
35
+ now: options.now ?? Date.now,
36
+ };
37
+ }
38
+ export function createVideoHubCapabilities() {
39
+ return {
40
+ mediaTypes: ["movie", "series"],
41
+ lookup: {
42
+ byTitle: false,
43
+ byExternalIds: ["kinopoisk"],
44
+ byEpisode: true,
45
+ },
46
+ features: ["mp4", "translations", "qualities", "episode_mapping", "headers"],
47
+ };
48
+ }
49
+ function normalizeProviderName(value) {
50
+ const name = value.trim();
51
+ if (!name)
52
+ throw new TypeError("VideoHUB streaming provider name is required.");
53
+ return name;
54
+ }
55
+ function normalizeBaseUrl(value) {
56
+ let url;
57
+ try {
58
+ url = new URL(value.trim());
59
+ }
60
+ catch (error) {
61
+ throw new TypeError("VideoHUB streaming baseUrl must be a valid HTTPS URL.", {
62
+ cause: error,
63
+ });
64
+ }
65
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "") {
66
+ throw new TypeError("VideoHUB streaming baseUrl must be a credential-free HTTPS URL.");
67
+ }
68
+ url.pathname = "/";
69
+ url.search = "";
70
+ url.hash = "";
71
+ return url.href.replace(/\/$/u, "");
72
+ }
73
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/videohub-streaming/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { StreamingProvider } from "@media-engine/core";
2
+ import { type VideoHubStreamingProviderOptions } from "./config.js";
3
+ export type { VideoHubStreamingProviderOptions } from "./config.js";
4
+ export declare function videoHubStreamingProvider(options?: VideoHubStreamingProviderOptions): StreamingProvider;
@@ -0,0 +1,53 @@
1
+ import { rethrowIfProviderAborted } from "../shared/abort.js";
2
+ import { loadVideoHubPlaylist, resolveVideoHubItems, resolveVideoHubKinopoiskId, } from "./client.js";
3
+ import { createVideoHubCapabilities, createVideoHubConfig, } from "./config.js";
4
+ import { mapVideoHubAvailability } from "./mapping.js";
5
+ export function videoHubStreamingProvider(options = {}) {
6
+ const config = createVideoHubConfig(options);
7
+ return {
8
+ name: config.name,
9
+ version: options.version,
10
+ kind: "streaming",
11
+ capabilities: createVideoHubCapabilities(),
12
+ availabilityDependsOnPlaybackUserAgent: true,
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
+ const kinopoiskId = resolveVideoHubKinopoiskId(query);
19
+ if (!kinopoiskId)
20
+ return null;
21
+ const playbackUserAgent = resolvePlaybackUserAgent(context, config.userAgent);
22
+ try {
23
+ const { playlist, sourceUrl } = await loadVideoHubPlaylist(config, kinopoiskId, context, playbackUserAgent);
24
+ if ((query.type === "series") !== playlist.isSerial)
25
+ return null;
26
+ const items = await resolveVideoHubItems(config, playlist, query, context, playbackUserAgent);
27
+ return mapVideoHubAvailability(config.name, kinopoiskId, playlist.title, items, query, sourceUrl, config.now(), config.linkTtlMs, playbackUserAgent);
28
+ }
29
+ catch (error) {
30
+ rethrowIfProviderAborted(context, error);
31
+ throw error;
32
+ }
33
+ },
34
+ };
35
+ }
36
+ function resolvePlaybackUserAgent(context, fallback) {
37
+ const value = context.playbackUserAgent?.trim();
38
+ return value && value.length <= 512 && !/[\u0000-\u001f\u007f]/u.test(value) ? value : fallback;
39
+ }
40
+ function canResolveQuery(query) {
41
+ if (query.type !== "movie" && query.type !== "series")
42
+ return false;
43
+ if (query.absoluteEpisodeNumber !== undefined)
44
+ return false;
45
+ if (query.type === "movie") {
46
+ return query.seasonNumber === undefined && query.episodeNumber === undefined;
47
+ }
48
+ return (Number.isInteger(query.seasonNumber) &&
49
+ (query.seasonNumber ?? 0) > 0 &&
50
+ Number.isInteger(query.episodeNumber) &&
51
+ (query.episodeNumber ?? 0) > 0);
52
+ }
53
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/videohub-streaming/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { MediaAvailability } from "@media-engine/core";
2
+ import type { ResolvedVideoHubItem } from "./client.js";
3
+ export declare function mapVideoHubAvailability(provider: string, kinopoiskId: string, playlistTitle: string | undefined, items: ResolvedVideoHubItem[], query: MediaAvailability["query"], sourceUrl: string, now: number, linkTtlMs: number, playbackUserAgent: string): MediaAvailability | null;
@@ -0,0 +1,84 @@
1
+ export function mapVideoHubAvailability(provider, kinopoiskId, playlistTitle, items, query, sourceUrl, now, linkTtlMs, playbackUserAgent) {
2
+ const expiresAt = new Date(now + linkTtlMs).toISOString();
3
+ const options = uniqueOptions(items.flatMap((item) => item.sources.map((source) => createOption(provider, kinopoiskId, item, source, query, expiresAt, playbackUserAgent))));
4
+ if (options.length === 0)
5
+ return null;
6
+ const episodes = query.type === "series"
7
+ ? [
8
+ {
9
+ seasonNumber: query.seasonNumber,
10
+ episodeNumber: query.episodeNumber,
11
+ options,
12
+ },
13
+ ]
14
+ : undefined;
15
+ const ids = { ...query.ids, kinopoisk: kinopoiskId };
16
+ return {
17
+ query,
18
+ item: {
19
+ type: query.type,
20
+ title: playlistTitle ?? query.title,
21
+ year: query.year,
22
+ ids,
23
+ },
24
+ ...(episodes ? { episodes } : {}),
25
+ options,
26
+ sourceProviders: [{ provider, url: sourceUrl, ids }],
27
+ checkedAt: new Date(now).toISOString(),
28
+ };
29
+ }
30
+ function createOption(provider, kinopoiskId, item, source, query, expiresAt, playbackUserAgent) {
31
+ const episode = query.type === "series"
32
+ ? { seasonNumber: item.seasonNumber, episodeNumber: item.episodeNumber }
33
+ : undefined;
34
+ const translation = createTranslation(item.voiceStudio, item.voiceType);
35
+ const episodeKey = episode ? `:${episode.seasonNumber}:${episode.episodeNumber}` : "";
36
+ return {
37
+ id: `${provider}:${kinopoiskId}${episodeKey}:${item.vkId}:${source.label}`,
38
+ provider,
39
+ player: {
40
+ kind: "mp4",
41
+ label: "VideoHUB",
42
+ providerPlayerId: `${item.vkId}${episodeKey}`,
43
+ },
44
+ translation,
45
+ quality: {
46
+ label: source.label,
47
+ ...(source.height !== undefined ? { height: source.height } : {}),
48
+ },
49
+ ...(episode ? { episode } : {}),
50
+ access: { url: source.url, headers: { "User-Agent": playbackUserAgent } },
51
+ availability: "available",
52
+ expiresAt,
53
+ sourceUrl: item.sourceUrl,
54
+ };
55
+ }
56
+ function createTranslation(voiceStudio, voiceType) {
57
+ const title = voiceStudio || voiceType || "VideoHUB";
58
+ const normalizedType = (voiceType ?? title).toLocaleLowerCase();
59
+ const type = normalizedType.includes("субтит")
60
+ ? "subtitles"
61
+ : normalizedType.includes("дубляж")
62
+ ? "dub"
63
+ : normalizedType.includes("голос")
64
+ ? "voiceover"
65
+ : normalizedType.includes("оригинал")
66
+ ? "original"
67
+ : "unknown";
68
+ return {
69
+ title,
70
+ type,
71
+ ...(voiceStudio ? { team: voiceStudio } : {}),
72
+ };
73
+ }
74
+ function uniqueOptions(options) {
75
+ const seen = new Set();
76
+ return options.filter((option) => {
77
+ const key = `${option.translation?.title ?? ""}\u0000${option.quality?.label ?? ""}\u0000${option.access.url}`;
78
+ if (seen.has(key))
79
+ return false;
80
+ seen.add(key);
81
+ return true;
82
+ });
83
+ }
84
+ //# sourceMappingURL=mapping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapping.js","sourceRoot":"","sources":["../../src/videohub-streaming/mapping.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export declare class WikidataCache<TEntity> {
2
+ #private;
3
+ constructor(options: {
4
+ maxEntries: number;
5
+ ttlMs: number;
6
+ });
7
+ getEntity(languageKey: string, entityId: string): TEntity | null | undefined;
8
+ setEntity(languageKey: string, entityId: string, entity: TEntity | null): void;
9
+ getImdbEntityId(imdbId: string): string | null | undefined;
10
+ setImdbEntityId(imdbId: string, entityId: string | null): void;
11
+ }
@@ -0,0 +1,30 @@
1
+ import { MemoryCache } from "@media-engine/core";
2
+ const ENTITY_KEY_PREFIX = "entity";
3
+ const IMDB_KEY_PREFIX = "imdb";
4
+ // Internal bounded cache for expensive Wikidata entity payloads and exact IMDb mappings.
5
+ // Внутренний bounded cache для тяжелых Wikidata entities и точных IMDb mappings.
6
+ export class WikidataCache {
7
+ #cache;
8
+ #ttlMs;
9
+ constructor(options) {
10
+ this.#cache = new MemoryCache({ maxEntries: options.maxEntries });
11
+ this.#ttlMs = options.ttlMs;
12
+ }
13
+ getEntity(languageKey, entityId) {
14
+ return this.#cache.get(`${ENTITY_KEY_PREFIX}:${languageKey}:${entityId.toUpperCase()}`);
15
+ }
16
+ setEntity(languageKey, entityId, entity) {
17
+ this.#cache.set(`${ENTITY_KEY_PREFIX}:${languageKey}:${entityId.toUpperCase()}`, entity, {
18
+ ttlMs: this.#ttlMs,
19
+ });
20
+ }
21
+ getImdbEntityId(imdbId) {
22
+ return this.#cache.get(`${IMDB_KEY_PREFIX}:${imdbId.toLowerCase()}`);
23
+ }
24
+ setImdbEntityId(imdbId, entityId) {
25
+ this.#cache.set(`${IMDB_KEY_PREFIX}:${imdbId.toLowerCase()}`, entityId, {
26
+ ttlMs: this.#ttlMs,
27
+ });
28
+ }
29
+ }
30
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/wikidata/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AACnC,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,yFAAyF;AACzF,iFAAiF;AACjF,MAAM,OAAO,aAAa;IACf,MAAM,CAAc;IACpB,MAAM,CAAS;IAExB,YAAY,OAA8C;QACxD,IAAI,CAAC,MAAM,GAAG,IAAI,WAAW,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAC9B,CAAC;IAED,SAAS,CAAC,WAAmB,EAAE,QAAgB;QAC7C,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CACpB,GAAG,iBAAiB,IAAI,WAAW,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,CAChE,CAAC;IACJ,CAAC;IAED,SAAS,CAAC,WAAmB,EAAE,QAAgB,EAAE,MAAsB;QACrE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,iBAAiB,IAAI,WAAW,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE;YACvF,KAAK,EAAE,IAAI,CAAC,MAAM;SACnB,CAAC,CAAC;IACL,CAAC;IAED,eAAe,CAAC,MAAc;QAC5B,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAgB,GAAG,eAAe,IAAI,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACtF,CAAC;IAED,eAAe,CAAC,MAAc,EAAE,QAAuB;QACrD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,eAAe,IAAI,MAAM,CAAC,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE;YACtE,KAAK,EAAE,IAAI,CAAC,MAAM;SACnB,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ import type { ProviderSearchQuery } from "@media-engine/core";
2
+ export interface WikidataSearchEntry {
3
+ id?: string;
4
+ label?: string;
5
+ description?: string;
6
+ match?: {
7
+ text?: string;
8
+ };
9
+ }
10
+ export declare function selectWikidataEntityIds(entries: WikidataSearchEntry[], query: ProviderSearchQuery, entityLimit: number): string[];
11
+ export declare function isRelevantWikidataTitleMatch(title: string, queryTitle: string): boolean;
12
+ export declare function normalizeWikidataEntityId(value: string | undefined): string | undefined;
@@ -0,0 +1,67 @@
1
+ import { normalizeProviderSearchText as normalizeSearchText } from "../shared/mapping.js";
2
+ // Keeps only title-relevant search summaries and drops obvious non-media hits before the
3
+ // selected-property entity request. Unknown summaries remain eligible to avoid locale-specific
4
+ // false negatives.
5
+ // Оставляет релевантные title summaries и отсекает явный не-media шум до entity-запроса.
6
+ export function selectWikidataEntityIds(entries, query, entityLimit) {
7
+ const title = query.title?.trim();
8
+ const classified = entries
9
+ .filter((entry) => {
10
+ if (!normalizeWikidataEntityId(entry.id)) {
11
+ return false;
12
+ }
13
+ return title
14
+ ? [entry.match?.text, entry.label].some((candidate) => candidate && isRelevantWikidataTitleMatch(candidate, title))
15
+ : true;
16
+ })
17
+ .map((entry) => ({ entry, relevance: classifySummary(entry.description, query.type) }))
18
+ .filter(({ relevance }) => relevance !== "irrelevant");
19
+ return [
20
+ ...classified.filter(({ relevance }) => relevance === "preferred"),
21
+ ...classified.filter(({ relevance }) => relevance === "unknown"),
22
+ ]
23
+ .slice(0, entityLimit)
24
+ .map(({ entry }) => normalizeWikidataEntityId(entry.id))
25
+ .filter((id) => id !== undefined);
26
+ }
27
+ const CHILD_OR_COLLECTION_SUMMARY_PATTERN = /\b(?:episode|season|film series|media franchise)\b|эпизод|сезон|медиафраншиз|кинофраншиз/iu;
28
+ const NON_MEDIA_SUMMARY_PATTERN = /\b(?:video|computer|board) game\b|\b(?:studio |live |soundtrack )?album\b|\b(?:song|single|novel|book|surname|family name|given name|city|town|village|municipality|landform|fictional character|company|organization|university|school|asteroid|taxon|band|mud|virtual world)\b|видеоигр|компьютерн\S* игр|настольн\S* игр|альбом|песн|роман|книг|фамили|город|насел[её]нн|франшиз|персонаж|саундтрек|компани|организац|университет|школ/iu;
29
+ const MOVIE_SUMMARY_PATTERN = /\b(?:film|movie|motion picture|telefilm)\b|фильм|кинофильм|мультфильм/iu;
30
+ const SERIES_SUMMARY_PATTERN = /\b(?:television|tv|web|anime) series\b|\bminiseries\b|телесериал|мини-сериал|аниме-сериал/iu;
31
+ function classifySummary(description, type) {
32
+ if (!description) {
33
+ return "unknown";
34
+ }
35
+ if (CHILD_OR_COLLECTION_SUMMARY_PATTERN.test(description)) {
36
+ return "irrelevant";
37
+ }
38
+ const describesMovie = MOVIE_SUMMARY_PATTERN.test(description);
39
+ const describesSeries = SERIES_SUMMARY_PATTERN.test(description);
40
+ if (type === "movie" && describesMovie) {
41
+ return "preferred";
42
+ }
43
+ if (type === "series" && describesSeries) {
44
+ return "preferred";
45
+ }
46
+ if (!type && (describesMovie || describesSeries)) {
47
+ return "preferred";
48
+ }
49
+ if (type === "movie" && describesSeries) {
50
+ return "irrelevant";
51
+ }
52
+ return (type === "series" && describesMovie) || NON_MEDIA_SUMMARY_PATTERN.test(description)
53
+ ? "irrelevant"
54
+ : "unknown";
55
+ }
56
+ export function isRelevantWikidataTitleMatch(title, queryTitle) {
57
+ const normalizedTitle = normalizeSearchText(title);
58
+ const normalizedQuery = normalizeSearchText(queryTitle);
59
+ return normalizedTitle.includes(normalizedQuery) || normalizedQuery.includes(normalizedTitle);
60
+ }
61
+ // Accepts only canonical Wikidata item IDs before using upstream values in queries/cache keys.
62
+ // Принимает только canonical Wikidata item IDs до использования upstream values в запросах.
63
+ export function normalizeWikidataEntityId(value) {
64
+ const normalized = value?.trim().toUpperCase();
65
+ return normalized && /^Q[1-9]\d*$/.test(normalized) ? normalized : undefined;
66
+ }
67
+ //# sourceMappingURL=candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.js","sourceRoot":"","sources":["../../src/wikidata/candidates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { type ProviderContext, type ProviderSearchQuery } from "@media-engine/core";
2
+ import { ProviderRateLimitGate, type ProviderFetch } from "../shared/index.js";
3
+ import { WikidataCache } from "./cache.js";
4
+ export interface WikidataClientConfig {
5
+ baseUrl: string;
6
+ sparqlUrl: string;
7
+ language: string;
8
+ userAgent: string;
9
+ fetch?: ProviderFetch;
10
+ rateLimitGate: ProviderRateLimitGate;
11
+ searchLimit: number;
12
+ entityLimit: number;
13
+ cache: WikidataCache<WikidataEntity>;
14
+ }
15
+ export interface WikidataEntity {
16
+ id: string;
17
+ labels?: Record<string, WikidataTextValue>;
18
+ descriptions?: Record<string, WikidataTextValue>;
19
+ claims?: Record<string, WikidataClaim[]>;
20
+ }
21
+ interface WikidataTextValue {
22
+ value?: string;
23
+ }
24
+ interface WikidataClaim {
25
+ mainsnak?: {
26
+ datavalue?: {
27
+ value?: WikidataClaimValue;
28
+ };
29
+ };
30
+ }
31
+ export type WikidataClaimValue = string | {
32
+ id?: string;
33
+ time?: string;
34
+ amount?: string;
35
+ text?: string;
36
+ };
37
+ export declare function searchWikidataEntities(config: WikidataClientConfig, query: ProviderSearchQuery, context: ProviderContext): Promise<WikidataEntity[]>;
38
+ export declare function getWikidataEntityByImdbId(config: WikidataClientConfig, imdbId: string, context: ProviderContext): Promise<WikidataEntity | undefined>;
39
+ export declare function normalizeWikidataLanguage(language: string): string;
40
+ export {};
@@ -0,0 +1,224 @@
1
+ import { ProviderError } from "@media-engine/core";
2
+ import { fetchJson } from "../shared/index.js";
3
+ import { normalizeWikidataEntityId, selectWikidataEntityIds, } from "./candidates.js";
4
+ const PROVIDER_NAME = "wikidata";
5
+ const DEFAULT_LANGUAGE = "en";
6
+ const MAX_RESPONSE_BYTES = 256 * 1_024;
7
+ export async function searchWikidataEntities(config, query, context) {
8
+ const entityIds = await searchEntityIds(config, query, context);
9
+ return getEntities(config, entityIds, context);
10
+ }
11
+ export async function getWikidataEntityByImdbId(config, imdbId, context) {
12
+ const cachedEntityId = config.cache.getImdbEntityId(imdbId);
13
+ if (cachedEntityId === null) {
14
+ return undefined;
15
+ }
16
+ if (cachedEntityId !== undefined) {
17
+ return (await getEntities(config, [cachedEntityId], context))[0];
18
+ }
19
+ const languages = getRequestedLanguages(context.language ?? config.language);
20
+ const response = await requestSparql(config, createSelectedPropertyQuery(`?item wdt:P345 "${escapeSparqlString(imdbId)}".`, languages, "LIMIT 1"), context);
21
+ const entity = parseSparqlEntities(response, languages)[0];
22
+ config.cache.setImdbEntityId(imdbId, entity?.id ?? null);
23
+ if (entity) {
24
+ config.cache.setEntity(createLanguageKey(languages), entity.id, entity);
25
+ }
26
+ return entity;
27
+ }
28
+ async function searchEntityIds(config, query, context) {
29
+ const url = new URL(`${config.baseUrl}/w/api.php`);
30
+ const language = normalizeWikidataLanguage(query.language ?? context.language ?? config.language);
31
+ url.searchParams.set("action", "wbsearchentities");
32
+ url.searchParams.set("format", "json");
33
+ url.searchParams.set("type", "item");
34
+ url.searchParams.set("language", language);
35
+ url.searchParams.set("uselang", language);
36
+ url.searchParams.set("search", query.title ?? "");
37
+ url.searchParams.set("limit", String(config.searchLimit));
38
+ const response = await requestJson(config, url, context);
39
+ if (!Array.isArray(response.search)) {
40
+ throw createInvalidResponseError("search entries");
41
+ }
42
+ return selectWikidataEntityIds(response.search, query, config.entityLimit);
43
+ }
44
+ async function getEntities(config, entityIds, context) {
45
+ if (!entityIds.length) {
46
+ return [];
47
+ }
48
+ const languages = getRequestedLanguages(context.language ?? config.language);
49
+ const languageKey = createLanguageKey(languages);
50
+ const entitiesById = new Map();
51
+ const missingIds = [];
52
+ for (const entityId of entityIds) {
53
+ const cached = config.cache.getEntity(languageKey, entityId);
54
+ if (cached === undefined) {
55
+ missingIds.push(entityId);
56
+ }
57
+ else if (cached !== null) {
58
+ entitiesById.set(entityId, cached);
59
+ }
60
+ }
61
+ if (missingIds.length) {
62
+ const values = missingIds.map((entityId) => `wd:${entityId}`).join(" ");
63
+ const response = await requestSparql(config, createSelectedPropertyQuery(`VALUES ?item { ${values} }`, languages), context);
64
+ const fetchedEntities = parseSparqlEntities(response, languages);
65
+ const expectedIds = new Set(missingIds);
66
+ if (fetchedEntities.some((entity) => !expectedIds.has(entity.id))) {
67
+ throw createInvalidResponseError("requested entity IDs");
68
+ }
69
+ const fetchedById = new Map(fetchedEntities.map((entity) => [entity.id, entity]));
70
+ for (const entityId of missingIds) {
71
+ const entity = fetchedById.get(entityId) ?? null;
72
+ config.cache.setEntity(languageKey, entityId, entity);
73
+ if (entity) {
74
+ const imdbId = getEntityImdbId(entity);
75
+ entitiesById.set(entityId, entity);
76
+ if (imdbId) {
77
+ config.cache.setImdbEntityId(imdbId, entityId);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ return entityIds.flatMap((entityId) => {
83
+ const entity = entitiesById.get(entityId);
84
+ return entity ? [entity] : [];
85
+ });
86
+ }
87
+ function createSelectedPropertyQuery(selector, languages, suffix = "") {
88
+ const requestedLanguage = languages[0] ?? DEFAULT_LANGUAGE;
89
+ return `SELECT ?item
90
+ (GROUP_CONCAT(DISTINCT STR(?instanceValue); separator="|") AS ?instances)
91
+ (SAMPLE(?imdbValue) AS ?imdb)
92
+ (MIN(?releaseValue) AS ?releaseDate)
93
+ (SAMPLE(?imageValue) AS ?image)
94
+ (SAMPLE(?originalValue) AS ?originalTitle)
95
+ (SAMPLE(?requestedLabelValue) AS ?requestedLabel)
96
+ (SAMPLE(?englishLabelValue) AS ?englishLabel)
97
+ (SAMPLE(?requestedDescriptionValue) AS ?requestedDescription)
98
+ (SAMPLE(?englishDescriptionValue) AS ?englishDescription)
99
+ WHERE {
100
+ ${selector}
101
+ OPTIONAL { ?item wdt:P31 ?instanceValue. }
102
+ OPTIONAL { ?item wdt:P345 ?imdbValue. }
103
+ OPTIONAL { ?item wdt:P577 ?releaseValue. }
104
+ OPTIONAL { ?item wdt:P18 ?imageValue. }
105
+ OPTIONAL { ?item wdt:P1476 ?originalValue. }
106
+ OPTIONAL {
107
+ ?item rdfs:label ?requestedLabelValue.
108
+ FILTER(LANG(?requestedLabelValue) = "${requestedLanguage}")
109
+ }
110
+ OPTIONAL {
111
+ ?item rdfs:label ?englishLabelValue.
112
+ FILTER(LANG(?englishLabelValue) = "en")
113
+ }
114
+ OPTIONAL {
115
+ ?item schema:description ?requestedDescriptionValue.
116
+ FILTER(LANG(?requestedDescriptionValue) = "${requestedLanguage}")
117
+ }
118
+ OPTIONAL {
119
+ ?item schema:description ?englishDescriptionValue.
120
+ FILTER(LANG(?englishDescriptionValue) = "en")
121
+ }
122
+ }
123
+ GROUP BY ?item
124
+ ${suffix}`;
125
+ }
126
+ function mapSparqlBinding(binding, languages) {
127
+ const entityId = normalizeWikidataEntityId(binding?.item?.value?.split("/").at(-1));
128
+ if (!binding || !entityId) {
129
+ return undefined;
130
+ }
131
+ const requestedLanguage = languages[0] ?? DEFAULT_LANGUAGE;
132
+ const labels = createLocalizedValues(requestedLanguage, binding.requestedLabel?.value, binding.englishLabel?.value);
133
+ const descriptions = createLocalizedValues(requestedLanguage, binding.requestedDescription?.value, binding.englishDescription?.value);
134
+ const claims = {};
135
+ setClaims(claims, "P31", (binding.instances?.value ?? "")
136
+ .split("|")
137
+ .map((value) => normalizeWikidataEntityId(value.split("/").at(-1)))
138
+ .filter((value) => value !== undefined)
139
+ .map((id) => ({ id })));
140
+ setClaims(claims, "P345", compactValues(binding.imdb?.value));
141
+ setClaims(claims, "P577", compactValues(binding.releaseDate?.value).map((time) => ({ time })));
142
+ setClaims(claims, "P18", compactValues(binding.image?.value));
143
+ setClaims(claims, "P1476", compactValues(binding.originalTitle?.value).map((text) => ({ text })));
144
+ return { id: entityId, labels, descriptions, claims };
145
+ }
146
+ function parseSparqlEntities(response, languages) {
147
+ const bindings = response.results?.bindings;
148
+ if (!Array.isArray(bindings)) {
149
+ throw createInvalidResponseError("SPARQL bindings");
150
+ }
151
+ const entities = bindings.map((binding) => mapSparqlBinding(binding, languages));
152
+ if (entities.some((entity) => entity === undefined)) {
153
+ throw createInvalidResponseError("canonical entity IDs");
154
+ }
155
+ return entities;
156
+ }
157
+ function createLocalizedValues(requestedLanguage, requestedValue, englishValue) {
158
+ const values = {};
159
+ if (requestedValue) {
160
+ values[requestedLanguage] = { value: requestedValue };
161
+ }
162
+ if (englishValue) {
163
+ values.en = { value: englishValue };
164
+ }
165
+ return Object.keys(values).length ? values : undefined;
166
+ }
167
+ function setClaims(claims, property, values) {
168
+ if (values.length) {
169
+ claims[property] = values.map((value) => ({
170
+ mainsnak: { datavalue: { value } },
171
+ }));
172
+ }
173
+ }
174
+ function compactValues(value) {
175
+ return value ? [value] : [];
176
+ }
177
+ function getEntityImdbId(entity) {
178
+ const value = entity.claims?.P345?.[0]?.mainsnak?.datavalue?.value;
179
+ return typeof value === "string" ? value : undefined;
180
+ }
181
+ function requestSparql(config, query, context) {
182
+ const url = new URL(config.sparqlUrl);
183
+ url.searchParams.set("format", "json");
184
+ url.searchParams.set("query", query);
185
+ return requestJson(config, url, context);
186
+ }
187
+ function requestJson(config, url, context) {
188
+ return fetchJson({
189
+ provider: PROVIDER_NAME,
190
+ url,
191
+ context,
192
+ fetch: config.fetch,
193
+ maxResponseBytes: MAX_RESPONSE_BYTES,
194
+ rateLimitGate: config.rateLimitGate,
195
+ init: {
196
+ headers: {
197
+ accept: "application/json",
198
+ "user-agent": config.userAgent,
199
+ },
200
+ },
201
+ });
202
+ }
203
+ export function normalizeWikidataLanguage(language) {
204
+ const primary = language.split("-")[0]?.trim().toLocaleLowerCase();
205
+ return primary && /^[a-z]{2,12}$/.test(primary) ? primary : DEFAULT_LANGUAGE;
206
+ }
207
+ function getRequestedLanguages(language) {
208
+ return [...new Set([normalizeWikidataLanguage(language), DEFAULT_LANGUAGE])];
209
+ }
210
+ function createLanguageKey(languages) {
211
+ return languages.join("|");
212
+ }
213
+ function escapeSparqlString(value) {
214
+ return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
215
+ }
216
+ function createInvalidResponseError(field) {
217
+ return new ProviderError({
218
+ provider: PROVIDER_NAME,
219
+ code: "PROVIDER_INVALID_RESPONSE",
220
+ message: `Provider "${PROVIDER_NAME}" returned invalid ${field}.`,
221
+ retryable: false,
222
+ });
223
+ }
224
+ //# sourceMappingURL=client.js.map