@media-engine/core 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -255
- package/README.ru.md +80 -0
- package/dist/cache/memory.d.ts +5 -0
- package/dist/cache/memory.js +43 -3
- package/dist/cache/memory.js.map +1 -1
- package/dist/cache/types.d.ts +2 -0
- package/dist/details/types.d.ts +6 -0
- package/dist/engine/availability.d.ts +6 -0
- package/dist/engine/availability.js +137 -0
- package/dist/engine/availability.js.map +1 -0
- package/dist/engine/circuit-breaker.d.ts +37 -0
- package/dist/engine/circuit-breaker.js +174 -0
- package/dist/engine/circuit-breaker.js.map +1 -0
- package/dist/engine/concurrency-limiter.d.ts +13 -0
- package/dist/engine/concurrency-limiter.js +109 -0
- package/dist/engine/concurrency-limiter.js.map +1 -0
- package/dist/engine/engine.d.ts +15 -5
- package/dist/engine/engine.js +510 -848
- package/dist/engine/engine.js.map +1 -1
- package/dist/engine/in-flight.d.ts +15 -0
- package/dist/engine/in-flight.js +80 -0
- package/dist/engine/in-flight.js.map +1 -0
- package/dist/engine/operation.d.ts +7 -0
- package/dist/engine/operation.js +49 -0
- package/dist/engine/operation.js.map +1 -0
- package/dist/engine/provider-calls.d.ts +52 -0
- package/dist/engine/provider-calls.js +262 -0
- package/dist/engine/provider-calls.js.map +1 -0
- package/dist/engine/query.d.ts +26 -0
- package/dist/engine/query.js +430 -0
- package/dist/engine/query.js.map +1 -0
- package/dist/engine/response-meta.d.ts +15 -0
- package/dist/engine/response-meta.js +33 -0
- package/dist/engine/response-meta.js.map +1 -0
- package/dist/engine/runtime.d.ts +5 -0
- package/dist/engine/runtime.js +48 -0
- package/dist/engine/runtime.js.map +1 -0
- package/dist/engine/search-discovery.d.ts +3 -0
- package/dist/engine/search-discovery.js +35 -0
- package/dist/engine/search-discovery.js.map +1 -0
- package/dist/engine/search-enrichment-shared.d.ts +23 -0
- package/dist/engine/search-enrichment-shared.js +68 -0
- package/dist/engine/search-enrichment-shared.js.map +1 -0
- package/dist/engine/search-enrichment.d.ts +42 -0
- package/dist/engine/search-enrichment.js +150 -0
- package/dist/engine/search-enrichment.js.map +1 -0
- package/dist/engine/search-identity-snapshot.d.ts +15 -0
- package/dist/engine/search-identity-snapshot.js +101 -0
- package/dist/engine/search-identity-snapshot.js.map +1 -0
- package/dist/engine/search-outcomes.d.ts +32 -0
- package/dist/engine/search-outcomes.js +118 -0
- package/dist/engine/search-outcomes.js.map +1 -0
- package/dist/engine/search-poster-enrichment.d.ts +31 -0
- package/dist/engine/search-poster-enrichment.js +93 -0
- package/dist/engine/search-poster-enrichment.js.map +1 -0
- package/dist/engine/search-result-freeze.d.ts +13 -0
- package/dist/engine/search-result-freeze.js +155 -0
- package/dist/engine/search-result-freeze.js.map +1 -0
- package/dist/engine/stale-fallback.d.ts +11 -0
- package/dist/engine/stale-fallback.js +60 -0
- package/dist/engine/stale-fallback.js.map +1 -0
- package/dist/engine/timeout-budget.d.ts +7 -0
- package/dist/engine/timeout-budget.js +31 -0
- package/dist/engine/timeout-budget.js.map +1 -0
- package/dist/engine/torrents.d.ts +22 -0
- package/dist/engine/torrents.js +188 -0
- package/dist/engine/torrents.js.map +1 -0
- package/dist/engine/types.d.ts +35 -0
- package/dist/errors/types.d.ts +1 -1
- package/dist/errors/types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/media/types.d.ts +1 -1
- package/dist/merge/details-identity.d.ts +5 -0
- package/dist/merge/details-identity.js +63 -0
- package/dist/merge/details-identity.js.map +1 -0
- package/dist/merge/diversity.d.ts +15 -0
- package/dist/merge/diversity.js +52 -0
- package/dist/merge/diversity.js.map +1 -0
- package/dist/merge/fields.d.ts +25 -0
- package/dist/merge/fields.js +364 -0
- package/dist/merge/fields.js.map +1 -0
- package/dist/merge/grouping.d.ts +3 -0
- package/dist/merge/grouping.js +143 -0
- package/dist/merge/grouping.js.map +1 -0
- package/dist/merge/identity.d.ts +5 -0
- package/dist/merge/identity.js +21 -0
- package/dist/merge/identity.js.map +1 -0
- package/dist/merge/internal.d.ts +22 -0
- package/dist/merge/internal.js +15 -0
- package/dist/merge/internal.js.map +1 -0
- package/dist/merge/media-type.d.ts +5 -0
- package/dist/merge/media-type.js +15 -0
- package/dist/merge/media-type.js.map +1 -0
- package/dist/merge/priority.d.ts +9 -0
- package/dist/merge/priority.js +63 -0
- package/dist/merge/priority.js.map +1 -0
- package/dist/merge/scoring.d.ts +15 -0
- package/dist/merge/scoring.js +364 -0
- package/dist/merge/scoring.js.map +1 -0
- package/dist/merge/strategy.js +66 -955
- package/dist/merge/strategy.js.map +1 -1
- package/dist/merge/title.d.ts +4 -0
- package/dist/merge/title.js +22 -0
- package/dist/merge/title.js.map +1 -0
- package/dist/providers/registry.d.ts +4 -2
- package/dist/providers/registry.js +11 -2
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/types.d.ts +4 -0
- package/dist/response/types.d.ts +21 -0
- package/dist/search/types.d.ts +34 -0
- package/dist/testing/providers.js +2 -0
- package/dist/testing/providers.js.map +1 -1
- package/dist/torrent/index.d.ts +1 -0
- package/dist/torrent/index.js +2 -0
- package/dist/torrent/index.js.map +1 -0
- package/dist/torrent/types.d.ts +132 -0
- package/dist/torrent/types.js +2 -0
- package/dist/torrent/types.js.map +1 -0
- package/package.json +13 -4
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DetailsQuery } from "../details/index.js";
|
|
2
|
+
import type { ExternalIds } from "../media/index.js";
|
|
3
|
+
import type { ProviderSearchQuery, ProviderSearchResult } from "../providers/index.js";
|
|
4
|
+
import type { SearchQuery } from "../search/index.js";
|
|
5
|
+
import type { StreamQuery } from "../streaming/index.js";
|
|
6
|
+
import type { TorrentDiscoveryQuery } from "../torrent/index.js";
|
|
7
|
+
export declare const EXTERNAL_ID_SHORTCUTS: readonly ["imdb", "tmdb", "kinopoisk", "shikimori", "myAnimeList", "aniList"];
|
|
8
|
+
export declare function normalizeSearchQuery(query: SearchQuery): SearchQuery;
|
|
9
|
+
export declare function inferTitleLanguage(title: string | undefined): string | undefined;
|
|
10
|
+
export declare function normalizeDetailsQuery(query: DetailsQuery): DetailsQuery;
|
|
11
|
+
export declare function normalizeStreamQuery(query: StreamQuery): StreamQuery;
|
|
12
|
+
export declare function normalizeTorrentQuery(query: TorrentDiscoveryQuery): TorrentDiscoveryQuery;
|
|
13
|
+
export declare function validateSearchQuery(query: SearchQuery): void;
|
|
14
|
+
export declare function validateDetailsQuery(query: DetailsQuery): void;
|
|
15
|
+
export declare function validateStreamQuery(query: StreamQuery): void;
|
|
16
|
+
export declare function validateTorrentQuery(query: TorrentDiscoveryQuery): void;
|
|
17
|
+
export declare function createProviderSearchQuery(query: SearchQuery): ProviderSearchQuery;
|
|
18
|
+
export declare function createSearchFallbackQuery(query: SearchQuery): SearchQuery | undefined;
|
|
19
|
+
export declare function appendUniqueSearchResults(target: ProviderSearchResult[], candidates: ProviderSearchResult[]): void;
|
|
20
|
+
export declare function createSearchCacheKey(query: SearchQuery): string;
|
|
21
|
+
export declare function createSearchIdentitySnapshotCacheKey(query: SearchQuery): string;
|
|
22
|
+
export declare function createDetailsCacheKey(query: DetailsQuery): string;
|
|
23
|
+
export declare function createAvailabilityCacheKey(query: StreamQuery): string;
|
|
24
|
+
export declare function createTorrentDiscoveryCacheKey(query: TorrentDiscoveryQuery): string;
|
|
25
|
+
export declare function hasExternalIds(ids: ExternalIds | undefined): boolean;
|
|
26
|
+
export declare function sortObject(value: unknown): unknown;
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { MediaEngineError } from "../errors/index.js";
|
|
2
|
+
// Top-level public external ID shortcuts supported by engine queries.
|
|
3
|
+
// Верхнеуровневые публичные сокращения внешних ID, поддерживаемые query движка.
|
|
4
|
+
export const EXTERNAL_ID_SHORTCUTS = [
|
|
5
|
+
"imdb",
|
|
6
|
+
"tmdb",
|
|
7
|
+
"kinopoisk",
|
|
8
|
+
"shikimori",
|
|
9
|
+
"myAnimeList",
|
|
10
|
+
"aniList",
|
|
11
|
+
];
|
|
12
|
+
const EXTERNAL_ID_SOURCES = [...EXTERNAL_ID_SHORTCUTS, "worldArt"];
|
|
13
|
+
const NUMERIC_EXTERNAL_ID_SOURCES = new Set([
|
|
14
|
+
"tmdb",
|
|
15
|
+
"kinopoisk",
|
|
16
|
+
"shikimori",
|
|
17
|
+
"myAnimeList",
|
|
18
|
+
"aniList",
|
|
19
|
+
"worldArt",
|
|
20
|
+
]);
|
|
21
|
+
const MEDIA_TYPES = new Set(["movie", "series", "anime"]);
|
|
22
|
+
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/u;
|
|
23
|
+
const IMDB_TITLE_ID = /^tt\d{7,12}$/u;
|
|
24
|
+
const NUMERIC_EXTERNAL_ID = /^\d+$/u;
|
|
25
|
+
const SEARCH_FALLBACK_MIN_TOKENS = 3;
|
|
26
|
+
const SEARCH_FALLBACK_MIN_LAST_TOKEN_LENGTH = 4;
|
|
27
|
+
const SEARCH_JOINED_FALLBACK_MIN_LENGTH = 6;
|
|
28
|
+
const SEARCH_JOINED_FALLBACK_MAX_LENGTH = 8;
|
|
29
|
+
const SEARCH_JOINED_FALLBACK_MIN_PART_LENGTH = 3;
|
|
30
|
+
const MAX_SEARCH_LIMIT = 100;
|
|
31
|
+
const MAX_TORRENT_LIMIT = 100;
|
|
32
|
+
const MAX_TORRENT_ALTERNATIVE_TITLES = 20;
|
|
33
|
+
const MAX_PROVIDER_SEARCH_LIMIT = 100;
|
|
34
|
+
const MAX_TITLE_LENGTH = 300;
|
|
35
|
+
const MAX_LANGUAGE_LENGTH = 35;
|
|
36
|
+
const MAX_EXTERNAL_ID_LENGTH = 128;
|
|
37
|
+
const MAX_DEPRECATED_DETAILS_ID_LENGTH = 128;
|
|
38
|
+
const MAX_PROVIDER_FILTER_LENGTH = 100;
|
|
39
|
+
const MAX_PROVIDER_FILTERS = 100;
|
|
40
|
+
// Normalizes top-level external ID shortcuts into the ids object.
|
|
41
|
+
// Нормализует верхнеуровневые сокращения внешних ID в объект ids.
|
|
42
|
+
export function normalizeSearchQuery(query) {
|
|
43
|
+
const title = normalizeOptionalString(query.title);
|
|
44
|
+
const ids = normalizeExternalIds(query.ids, query);
|
|
45
|
+
const language = normalizeLanguage(query.language);
|
|
46
|
+
return {
|
|
47
|
+
...(title ? { title } : {}),
|
|
48
|
+
...(query.type !== undefined ? { type: query.type } : {}),
|
|
49
|
+
...(query.year !== undefined ? { year: query.year } : {}),
|
|
50
|
+
...(ids ? { ids } : {}),
|
|
51
|
+
...(query.limit !== undefined ? { limit: query.limit } : {}),
|
|
52
|
+
...(language ? { language } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Infers a provider lookup language only when the caller did not specify one.
|
|
56
|
+
// Определяет язык provider lookup только если caller не передал его явно.
|
|
57
|
+
export function inferTitleLanguage(title) {
|
|
58
|
+
if (!title)
|
|
59
|
+
return undefined;
|
|
60
|
+
if (/[а-яё]/iu.test(title))
|
|
61
|
+
return "ru";
|
|
62
|
+
if (/[\u3040-\u30ff\u3400-\u9fff]/u.test(title))
|
|
63
|
+
return "ja";
|
|
64
|
+
return /[a-z]/iu.test(title) ? "en" : undefined;
|
|
65
|
+
}
|
|
66
|
+
// Normalizes top-level external ID shortcuts into a details ids object.
|
|
67
|
+
// Нормализует верхнеуровневые сокращения внешних ID в объект ids для details.
|
|
68
|
+
export function normalizeDetailsQuery(query) {
|
|
69
|
+
const ids = normalizeExternalIds(query.ids, query);
|
|
70
|
+
const id = normalizeOptionalString(query.id);
|
|
71
|
+
const language = normalizeLanguage(query.language);
|
|
72
|
+
return {
|
|
73
|
+
...(!ids && id ? { id } : {}),
|
|
74
|
+
...(ids ? { ids } : {}),
|
|
75
|
+
...(query.type !== undefined ? { type: query.type } : {}),
|
|
76
|
+
...(language ? { language } : {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// Normalizes top-level external ID shortcuts into a streaming ids object.
|
|
80
|
+
// Нормализует верхнеуровневые сокращения внешних ID в объект ids для streaming.
|
|
81
|
+
export function normalizeStreamQuery(query) {
|
|
82
|
+
const title = normalizeOptionalString(query.title);
|
|
83
|
+
const ids = normalizeExternalIds(query.ids, query);
|
|
84
|
+
const providers = normalizeProviderFilters(query.providers);
|
|
85
|
+
const language = normalizeLanguage(query.language);
|
|
86
|
+
return {
|
|
87
|
+
type: query.type,
|
|
88
|
+
...(ids ? { ids } : {}),
|
|
89
|
+
...(title ? { title } : {}),
|
|
90
|
+
...(query.year !== undefined ? { year: query.year } : {}),
|
|
91
|
+
...(query.seasonNumber !== undefined ? { seasonNumber: query.seasonNumber } : {}),
|
|
92
|
+
...(query.episodeNumber !== undefined ? { episodeNumber: query.episodeNumber } : {}),
|
|
93
|
+
...(query.absoluteEpisodeNumber !== undefined
|
|
94
|
+
? { absoluteEpisodeNumber: query.absoluteEpisodeNumber }
|
|
95
|
+
: {}),
|
|
96
|
+
...(providers ? { providers } : {}),
|
|
97
|
+
...(language ? { language } : {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// Normalizes top-level external ID shortcuts into a torrent discovery ids object.
|
|
101
|
+
// Нормализует верхнеуровневые сокращения внешних ID в torrent discovery ids object.
|
|
102
|
+
export function normalizeTorrentQuery(query) {
|
|
103
|
+
const title = normalizeOptionalString(query.title);
|
|
104
|
+
const alternativeTitles = normalizeAlternativeTitles(query.alternativeTitles, title);
|
|
105
|
+
const ids = normalizeExternalIds(query.ids, query);
|
|
106
|
+
const providers = normalizeProviderFilters(query.providers);
|
|
107
|
+
const language = normalizeLanguage(query.language);
|
|
108
|
+
return {
|
|
109
|
+
type: query.type,
|
|
110
|
+
...(ids ? { ids } : {}),
|
|
111
|
+
...(title ? { title } : {}),
|
|
112
|
+
...(alternativeTitles ? { alternativeTitles } : {}),
|
|
113
|
+
...(query.year !== undefined ? { year: query.year } : {}),
|
|
114
|
+
...(query.seasonNumber !== undefined ? { seasonNumber: query.seasonNumber } : {}),
|
|
115
|
+
...(query.episodeNumber !== undefined ? { episodeNumber: query.episodeNumber } : {}),
|
|
116
|
+
...(query.absoluteEpisodeNumber !== undefined
|
|
117
|
+
? { absoluteEpisodeNumber: query.absoluteEpisodeNumber }
|
|
118
|
+
: {}),
|
|
119
|
+
...(providers ? { providers } : {}),
|
|
120
|
+
...(language ? { language } : {}),
|
|
121
|
+
...(query.limit !== undefined ? { limit: query.limit } : {}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// Validates that a search query has at least one supported lookup input.
|
|
125
|
+
// Проверяет, что search query содержит хотя бы один поддерживаемый вход для поиска.
|
|
126
|
+
export function validateSearchQuery(query) {
|
|
127
|
+
validateCommonQueryFields(query);
|
|
128
|
+
if (query.limit !== undefined &&
|
|
129
|
+
(!Number.isInteger(query.limit) || query.limit < 0 || query.limit > MAX_SEARCH_LIMIT)) {
|
|
130
|
+
throw new MediaEngineError({
|
|
131
|
+
code: "INVALID_QUERY",
|
|
132
|
+
message: `Search query limit must be an integer between 0 and ${MAX_SEARCH_LIMIT}.`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (query.year !== undefined && (!Number.isInteger(query.year) || query.year < 0)) {
|
|
136
|
+
throwInvalidQuery("Search query year must be a non-negative integer.");
|
|
137
|
+
}
|
|
138
|
+
if (query.title || hasExternalIds(query.ids)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
throw new MediaEngineError({
|
|
142
|
+
code: "INVALID_QUERY",
|
|
143
|
+
message: "Search query must include title or external ids.",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// Validates that a details query has at least one supported lookup input.
|
|
147
|
+
// Проверяет, что details query содержит хотя бы один поддерживаемый вход для поиска.
|
|
148
|
+
export function validateDetailsQuery(query) {
|
|
149
|
+
validateCommonQueryFields(query);
|
|
150
|
+
if (query.id) {
|
|
151
|
+
validateBoundedString("Details query id", query.id, MAX_DEPRECATED_DETAILS_ID_LENGTH);
|
|
152
|
+
}
|
|
153
|
+
if (hasExternalIds(query.ids)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (query.id?.trim()) {
|
|
157
|
+
throw new MediaEngineError({
|
|
158
|
+
code: "INVALID_QUERY",
|
|
159
|
+
message: "Details query id is not a supported global lookup. Use ids or a named external ID shortcut.",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
throw new MediaEngineError({
|
|
163
|
+
code: "INVALID_QUERY",
|
|
164
|
+
message: "Details query must include external ids.",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// Validates that a streaming query can identify a media item or episode.
|
|
168
|
+
// Проверяет, что streaming query может определить медиа или эпизод.
|
|
169
|
+
export function validateStreamQuery(query) {
|
|
170
|
+
validateCommonQueryFields(query);
|
|
171
|
+
if (!query.type) {
|
|
172
|
+
throw new MediaEngineError({
|
|
173
|
+
code: "INVALID_QUERY",
|
|
174
|
+
message: "Stream query type is required.",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (query.providers && query.providers.length > MAX_PROVIDER_FILTERS) {
|
|
178
|
+
throwInvalidQuery(`Stream query providers must contain at most ${MAX_PROVIDER_FILTERS} unique names.`);
|
|
179
|
+
}
|
|
180
|
+
for (const provider of query.providers ?? []) {
|
|
181
|
+
validateBoundedString("Stream query provider", provider, MAX_PROVIDER_FILTER_LENGTH);
|
|
182
|
+
}
|
|
183
|
+
if ([query.year, query.seasonNumber, query.episodeNumber, query.absoluteEpisodeNumber].some((value) => value !== undefined && (!Number.isInteger(value) || value < 0))) {
|
|
184
|
+
throw new MediaEngineError({
|
|
185
|
+
code: "INVALID_QUERY",
|
|
186
|
+
message: "Stream query numeric fields must be non-negative integers.",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (query.title || hasExternalIds(query.ids)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
throw new MediaEngineError({
|
|
193
|
+
code: "INVALID_QUERY",
|
|
194
|
+
message: "Stream query must include title or external ids.",
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// Validates that a torrent discovery query can identify a media item or episode.
|
|
198
|
+
// Проверяет, что torrent discovery query может определить медиа или эпизод.
|
|
199
|
+
export function validateTorrentQuery(query) {
|
|
200
|
+
validateCommonQueryFields(query);
|
|
201
|
+
if (query.alternativeTitles && query.alternativeTitles.length > MAX_TORRENT_ALTERNATIVE_TITLES) {
|
|
202
|
+
throwInvalidQuery(`Torrent discovery query alternativeTitles must contain at most ${MAX_TORRENT_ALTERNATIVE_TITLES} unique titles.`);
|
|
203
|
+
}
|
|
204
|
+
for (const alternativeTitle of query.alternativeTitles ?? []) {
|
|
205
|
+
validateBoundedString("Torrent discovery query alternative title", alternativeTitle, MAX_TITLE_LENGTH);
|
|
206
|
+
}
|
|
207
|
+
if (!query.type) {
|
|
208
|
+
throwInvalidQuery("Torrent discovery query type is required.");
|
|
209
|
+
}
|
|
210
|
+
if (query.providers && query.providers.length > MAX_PROVIDER_FILTERS) {
|
|
211
|
+
throwInvalidQuery(`Torrent discovery query providers must contain at most ${MAX_PROVIDER_FILTERS} unique names.`);
|
|
212
|
+
}
|
|
213
|
+
for (const provider of query.providers ?? []) {
|
|
214
|
+
validateBoundedString("Torrent discovery query provider", provider, MAX_PROVIDER_FILTER_LENGTH);
|
|
215
|
+
}
|
|
216
|
+
if ([query.year, query.seasonNumber, query.episodeNumber, query.absoluteEpisodeNumber].some((value) => value !== undefined && (!Number.isInteger(value) || value < 0))) {
|
|
217
|
+
throwInvalidQuery("Torrent discovery query numeric fields must be non-negative integers.");
|
|
218
|
+
}
|
|
219
|
+
if (query.limit !== undefined &&
|
|
220
|
+
(!Number.isInteger(query.limit) || query.limit < 0 || query.limit > MAX_TORRENT_LIMIT)) {
|
|
221
|
+
throwInvalidQuery(`Torrent discovery query limit must be an integer between 0 and ${MAX_TORRENT_LIMIT}.`);
|
|
222
|
+
}
|
|
223
|
+
if (query.title || hasExternalIds(query.ids)) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
throwInvalidQuery("Torrent discovery query must include title or external ids.");
|
|
227
|
+
}
|
|
228
|
+
// Gives providers enough candidates so the engine can rank before applying the public limit.
|
|
229
|
+
// Дает провайдерам достаточно кандидатов, чтобы движок ранжировал до применения публичного limit.
|
|
230
|
+
export function createProviderSearchQuery(query) {
|
|
231
|
+
if (query.limit === undefined || query.limit === 0) {
|
|
232
|
+
return query;
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
...query,
|
|
236
|
+
limit: getProviderSearchLimit(query),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// Broadens an empty typo search or separates one likely joined compound title.
|
|
240
|
+
// Расширяет пустой поиск с опечаткой или разделяет вероятно слитное составное название.
|
|
241
|
+
export function createSearchFallbackQuery(query) {
|
|
242
|
+
if (!query.title || hasExternalIds(query.ids)) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
const title = query.title.trim();
|
|
246
|
+
const tokens = title.split(/\s+/);
|
|
247
|
+
const lastToken = tokens.at(-1);
|
|
248
|
+
if (tokens.length >= SEARCH_FALLBACK_MIN_TOKENS &&
|
|
249
|
+
lastToken &&
|
|
250
|
+
lastToken.length >= SEARCH_FALLBACK_MIN_LAST_TOKEN_LENGTH) {
|
|
251
|
+
return {
|
|
252
|
+
...query,
|
|
253
|
+
title: tokens.slice(0, -1).join(" "),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const characters = [...title];
|
|
257
|
+
if (tokens.length !== 1 ||
|
|
258
|
+
characters.length < SEARCH_JOINED_FALLBACK_MIN_LENGTH ||
|
|
259
|
+
characters.length > SEARCH_JOINED_FALLBACK_MAX_LENGTH ||
|
|
260
|
+
!/^\p{Script=Cyrillic}+$/u.test(title)) {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
const splitIndex = Math.floor(characters.length / 2);
|
|
264
|
+
if (splitIndex < SEARCH_JOINED_FALLBACK_MIN_PART_LENGTH ||
|
|
265
|
+
characters.length - splitIndex < SEARCH_JOINED_FALLBACK_MIN_PART_LENGTH) {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
...query,
|
|
270
|
+
title: `${characters.slice(0, splitIndex).join("")} ${characters.slice(splitIndex).join("")}`,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
// Adds fallback discoveries without duplicating the same provider item and its attribution.
|
|
274
|
+
// Добавляет fallback-результаты без дублирования item и атрибуции одного провайдера.
|
|
275
|
+
export function appendUniqueSearchResults(target, candidates) {
|
|
276
|
+
for (const candidate of candidates) {
|
|
277
|
+
const isDuplicate = target.some((existing) => existing.provider === candidate.provider &&
|
|
278
|
+
existing.item.type === candidate.item.type &&
|
|
279
|
+
existing.item.id === candidate.item.id);
|
|
280
|
+
if (!isDuplicate) {
|
|
281
|
+
target.push(candidate);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Creates a stable cache key for a normalized search query.
|
|
286
|
+
// Создает стабильный cache key для нормализованного search query.
|
|
287
|
+
export function createSearchCacheKey(query) {
|
|
288
|
+
return `search:${JSON.stringify(sortObject(query))}`;
|
|
289
|
+
}
|
|
290
|
+
// Shares confirmed identity ordering across equivalent searches with different public limits.
|
|
291
|
+
// Разделяет подтвержденный порядок identity между эквивалентными поисками с разными limit.
|
|
292
|
+
export function createSearchIdentitySnapshotCacheKey(query) {
|
|
293
|
+
const { limit: _limit, ...identityQuery } = query;
|
|
294
|
+
return `search-identity:${JSON.stringify(sortObject(identityQuery))}`;
|
|
295
|
+
}
|
|
296
|
+
// Creates a stable cache key for a normalized details query.
|
|
297
|
+
// Создает стабильный cache key для нормализованного details query.
|
|
298
|
+
export function createDetailsCacheKey(query) {
|
|
299
|
+
return `details:${JSON.stringify(sortObject(query))}`;
|
|
300
|
+
}
|
|
301
|
+
// Creates a stable cache key for a normalized streaming query.
|
|
302
|
+
// Создает стабильный cache key для нормализованного streaming query.
|
|
303
|
+
export function createAvailabilityCacheKey(query) {
|
|
304
|
+
return `availability:${JSON.stringify(sortObject(query))}`;
|
|
305
|
+
}
|
|
306
|
+
// Creates a stable cache key for a normalized torrent discovery query.
|
|
307
|
+
// Создает стабильный cache key для нормализованного torrent discovery query.
|
|
308
|
+
export function createTorrentDiscoveryCacheKey(query) {
|
|
309
|
+
return `torrents:${JSON.stringify(sortObject(query))}`;
|
|
310
|
+
}
|
|
311
|
+
// Checks whether an external ID object contains at least one ID.
|
|
312
|
+
// Проверяет, содержит ли объект внешних ID хотя бы один ID.
|
|
313
|
+
export function hasExternalIds(ids) {
|
|
314
|
+
return Boolean(ids && Object.values(ids).some((value) => Boolean(value)));
|
|
315
|
+
}
|
|
316
|
+
function normalizeExternalIds(nestedIds, shortcuts) {
|
|
317
|
+
const ids = {};
|
|
318
|
+
for (const source of EXTERNAL_ID_SOURCES) {
|
|
319
|
+
const nestedValue = normalizeOptionalString(nestedIds?.[source]);
|
|
320
|
+
const shortcutValue = EXTERNAL_ID_SHORTCUTS.includes(source)
|
|
321
|
+
? normalizeOptionalString(shortcuts[source])
|
|
322
|
+
: undefined;
|
|
323
|
+
const value = shortcutValue ?? nestedValue;
|
|
324
|
+
if (value) {
|
|
325
|
+
ids[source] = source === "imdb" ? value.toLowerCase() : value;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return hasExternalIds(ids) ? ids : undefined;
|
|
329
|
+
}
|
|
330
|
+
function normalizeProviderFilters(providers) {
|
|
331
|
+
if (!providers) {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
const normalized = [...new Set(providers.map(normalizeOptionalString).filter(isString))].sort();
|
|
335
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
336
|
+
}
|
|
337
|
+
function normalizeAlternativeTitles(values, primaryTitle) {
|
|
338
|
+
if (!values)
|
|
339
|
+
return undefined;
|
|
340
|
+
if (!Array.isArray(values) || values.length > MAX_TORRENT_ALTERNATIVE_TITLES) {
|
|
341
|
+
throwInvalidQuery(`Torrent discovery query alternativeTitles must contain at most ${MAX_TORRENT_ALTERNATIVE_TITLES} titles.`);
|
|
342
|
+
}
|
|
343
|
+
const primaryKey = primaryTitle?.toLocaleLowerCase();
|
|
344
|
+
const titles = new Map();
|
|
345
|
+
for (const value of values) {
|
|
346
|
+
if (typeof value !== "string") {
|
|
347
|
+
throwInvalidQuery("Torrent discovery query alternativeTitles must contain only strings.");
|
|
348
|
+
}
|
|
349
|
+
const normalized = normalizeOptionalString(value);
|
|
350
|
+
const key = normalized?.toLocaleLowerCase();
|
|
351
|
+
if (normalized && key !== primaryKey && !titles.has(key)) {
|
|
352
|
+
titles.set(key, normalized);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return titles.size > 0 ? [...titles.values()] : undefined;
|
|
356
|
+
}
|
|
357
|
+
function normalizeLanguage(language) {
|
|
358
|
+
return normalizeOptionalString(language)?.toLowerCase();
|
|
359
|
+
}
|
|
360
|
+
function normalizeOptionalString(value) {
|
|
361
|
+
const normalized = value?.trim();
|
|
362
|
+
return normalized ? normalized : undefined;
|
|
363
|
+
}
|
|
364
|
+
function validateCommonQueryFields(query) {
|
|
365
|
+
if (query.type !== undefined && !MEDIA_TYPES.has(query.type)) {
|
|
366
|
+
throwInvalidQuery("Query type must be movie, series, or anime.");
|
|
367
|
+
}
|
|
368
|
+
if (query.title) {
|
|
369
|
+
validateBoundedString("Query title", query.title, MAX_TITLE_LENGTH);
|
|
370
|
+
}
|
|
371
|
+
if (query.language) {
|
|
372
|
+
validateBoundedString("Query language", query.language, MAX_LANGUAGE_LENGTH);
|
|
373
|
+
}
|
|
374
|
+
for (const [source, value] of Object.entries(query.ids ?? {})) {
|
|
375
|
+
validateBoundedString(`External ID ${source}`, value, MAX_EXTERNAL_ID_LENGTH);
|
|
376
|
+
if (source === "imdb" && !IMDB_TITLE_ID.test(value)) {
|
|
377
|
+
throwInvalidQuery("External ID imdb must use the tt prefix followed by 7 to 12 digits.");
|
|
378
|
+
}
|
|
379
|
+
if (NUMERIC_EXTERNAL_ID_SOURCES.has(source) &&
|
|
380
|
+
!NUMERIC_EXTERNAL_ID.test(value)) {
|
|
381
|
+
throwInvalidQuery(`External ID ${source} must contain only digits.`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function validateBoundedString(field, value, maxLength) {
|
|
386
|
+
if (value.length > maxLength) {
|
|
387
|
+
throwInvalidQuery(`${field} must be at most ${maxLength} characters.`);
|
|
388
|
+
}
|
|
389
|
+
if (CONTROL_CHARACTERS.test(value)) {
|
|
390
|
+
throwInvalidQuery(`${field} must not contain control characters.`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function throwInvalidQuery(message) {
|
|
394
|
+
throw new MediaEngineError({ code: "INVALID_QUERY", message });
|
|
395
|
+
}
|
|
396
|
+
function isString(value) {
|
|
397
|
+
return value !== undefined;
|
|
398
|
+
}
|
|
399
|
+
// Sorts object keys recursively for deterministic JSON cache keys.
|
|
400
|
+
// Рекурсивно сортирует ключи объекта для детерминированных JSON cache keys.
|
|
401
|
+
export function sortObject(value) {
|
|
402
|
+
if (Array.isArray(value)) {
|
|
403
|
+
return value.map(sortObject);
|
|
404
|
+
}
|
|
405
|
+
if (value && typeof value === "object") {
|
|
406
|
+
return Object.fromEntries(Object.entries(value)
|
|
407
|
+
.filter(([, entryValue]) => entryValue !== undefined)
|
|
408
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
409
|
+
.map(([key, entryValue]) => [key, sortObject(entryValue)]));
|
|
410
|
+
}
|
|
411
|
+
return value;
|
|
412
|
+
}
|
|
413
|
+
// Expands broad short queries more because final ranking needs enough cross-provider candidates.
|
|
414
|
+
// Расширяет короткие широкие запросы сильнее, потому что финальному ranking нужны кандидаты разных провайдеров.
|
|
415
|
+
function getProviderSearchLimit(query) {
|
|
416
|
+
if (isBroadShortTitleSearch(query)) {
|
|
417
|
+
return Math.min(MAX_PROVIDER_SEARCH_LIMIT, Math.max(query.limit * 10, 50));
|
|
418
|
+
}
|
|
419
|
+
return Math.min(MAX_PROVIDER_SEARCH_LIMIT, Math.max(query.limit * 5, 10));
|
|
420
|
+
}
|
|
421
|
+
// Detects searches like "one" or "game" where popular canonical results may be deeper.
|
|
422
|
+
// Определяет поиски вроде "one" или "game", где популярные канонические результаты могут быть глубже.
|
|
423
|
+
function isBroadShortTitleSearch(query) {
|
|
424
|
+
if (query.type || hasExternalIds(query.ids)) {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
const normalizedTitle = query.title?.trim().replace(/\s+/g, " ") ?? "";
|
|
428
|
+
return (normalizedTitle.length > 0 && normalizedTitle.length <= 4 && !normalizedTitle.includes(" "));
|
|
429
|
+
}
|
|
430
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/engine/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EngineWarning, ProviderFailure, ProviderTimingMeta, ResponseMeta, SearchEnrichmentDebugMeta, SearchIdentitySnapshotDebugMeta } from "../response/index.js";
|
|
2
|
+
export interface ResponseMetaInput {
|
|
3
|
+
requested: string[];
|
|
4
|
+
successful: string[];
|
|
5
|
+
failed: ProviderFailure[];
|
|
6
|
+
warnings: EngineWarning[];
|
|
7
|
+
cached: boolean;
|
|
8
|
+
tookMs: number;
|
|
9
|
+
debug: boolean;
|
|
10
|
+
timings?: ProviderTimingMeta[];
|
|
11
|
+
enrichment?: SearchEnrichmentDebugMeta;
|
|
12
|
+
identitySnapshot?: SearchIdentitySnapshotDebugMeta;
|
|
13
|
+
}
|
|
14
|
+
export declare function createResponseMeta(input: ResponseMetaInput): ResponseMeta;
|
|
15
|
+
export declare function elapsedSince(startedAt: number): number;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Creates public response metadata for engine calls.
|
|
2
|
+
// Создает публичные метаданные ответа для вызовов engine.
|
|
3
|
+
export function createResponseMeta(input) {
|
|
4
|
+
return {
|
|
5
|
+
providers: {
|
|
6
|
+
requested: input.requested,
|
|
7
|
+
successful: input.successful,
|
|
8
|
+
failed: input.failed,
|
|
9
|
+
},
|
|
10
|
+
cached: input.cached,
|
|
11
|
+
tookMs: input.tookMs,
|
|
12
|
+
warnings: input.warnings.length > 0 ? input.warnings : undefined,
|
|
13
|
+
debug: input.debug
|
|
14
|
+
? {
|
|
15
|
+
providers: [
|
|
16
|
+
...new Set([
|
|
17
|
+
...input.requested,
|
|
18
|
+
...(input.timings ?? []).map((timing) => timing.provider),
|
|
19
|
+
]),
|
|
20
|
+
],
|
|
21
|
+
timings: input.timings ?? [],
|
|
22
|
+
enrichment: input.enrichment,
|
|
23
|
+
...(input.identitySnapshot ? { identitySnapshot: input.identitySnapshot } : {}),
|
|
24
|
+
}
|
|
25
|
+
: undefined,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// Returns elapsed milliseconds since a start timestamp.
|
|
29
|
+
// Возвращает количество миллисекунд, прошедших с начального timestamp.
|
|
30
|
+
export function elapsedSince(startedAt) {
|
|
31
|
+
return Date.now() - startedAt;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=response-meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-meta.js","sourceRoot":"","sources":["../../src/engine/response-meta.ts"],"names":[],"mappings":"AAwBA,qDAAqD;AACrD,0DAA0D;AAC1D,MAAM,UAAU,kBAAkB,CAAC,KAAwB;IACzD,OAAO;QACL,SAAS,EAAE;YACT,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,MAAM,EAAE,KAAK,CAAC,MAAM;SACrB;QACD,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QAChE,KAAK,EAAE,KAAK,CAAC,KAAK;YAChB,CAAC,CAAC;gBACE,SAAS,EAAE;oBACT,GAAG,IAAI,GAAG,CAAC;wBACT,GAAG,KAAK,CAAC,SAAS;wBAClB,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC;qBAC1D,CAAC;iBACH;gBACD,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE;gBAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAChF;YACH,CAAC,CAAC,SAAS;KACd,CAAC;AACJ,CAAC;AAED,wDAAwD;AACxD,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { StreamingProvider } from "../streaming/index.js";
|
|
2
|
+
import type { TorrentProvider } from "../torrent/index.js";
|
|
3
|
+
export declare function validateStreamingProviders(providers: StreamingProvider[]): StreamingProvider[];
|
|
4
|
+
export declare function validateTorrentProviders(providers: TorrentProvider[]): TorrentProvider[];
|
|
5
|
+
export declare function resolveProviderTimeoutMs(providerName: string, timeoutMs: number | undefined, providerTimeouts: Readonly<Record<string, number>>): number | undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Validates streaming providers and rejects duplicate public names.
|
|
2
|
+
// Проверяет streaming-провайдеры и отклоняет дубли публичных имен.
|
|
3
|
+
export function validateStreamingProviders(providers) {
|
|
4
|
+
const names = new Set();
|
|
5
|
+
for (const provider of providers) {
|
|
6
|
+
const name = provider.name.trim();
|
|
7
|
+
if (!name) {
|
|
8
|
+
throw new Error("Streaming provider name is required.");
|
|
9
|
+
}
|
|
10
|
+
if (name !== provider.name) {
|
|
11
|
+
throw new Error(`Streaming provider name "${provider.name}" must not include leading or trailing whitespace.`);
|
|
12
|
+
}
|
|
13
|
+
if (names.has(name)) {
|
|
14
|
+
throw new Error(`Streaming provider "${name}" is already registered.`);
|
|
15
|
+
}
|
|
16
|
+
names.add(name);
|
|
17
|
+
}
|
|
18
|
+
return [...providers];
|
|
19
|
+
}
|
|
20
|
+
// Validates torrent providers and rejects duplicate public names.
|
|
21
|
+
// Проверяет torrent-провайдеры и отклоняет дубли публичных имен.
|
|
22
|
+
export function validateTorrentProviders(providers) {
|
|
23
|
+
const names = new Set();
|
|
24
|
+
for (const provider of providers) {
|
|
25
|
+
const name = provider.name.trim();
|
|
26
|
+
if (!name) {
|
|
27
|
+
throw new Error("Torrent provider name is required.");
|
|
28
|
+
}
|
|
29
|
+
if (name !== provider.name) {
|
|
30
|
+
throw new Error(`Torrent provider name "${provider.name}" must not include leading or trailing whitespace.`);
|
|
31
|
+
}
|
|
32
|
+
if (names.has(name)) {
|
|
33
|
+
throw new Error(`Torrent provider "${name}" is already registered.`);
|
|
34
|
+
}
|
|
35
|
+
names.add(name);
|
|
36
|
+
}
|
|
37
|
+
return [...providers];
|
|
38
|
+
}
|
|
39
|
+
// Resolves a provider override without allowing it to exceed the global boundary.
|
|
40
|
+
// Выбирает override провайдера, не позволяя ему превысить глобальную границу.
|
|
41
|
+
export function resolveProviderTimeoutMs(providerName, timeoutMs, providerTimeouts) {
|
|
42
|
+
const providerTimeoutMs = providerTimeouts[providerName];
|
|
43
|
+
if (providerTimeoutMs === undefined) {
|
|
44
|
+
return timeoutMs;
|
|
45
|
+
}
|
|
46
|
+
return timeoutMs === undefined ? providerTimeoutMs : Math.min(timeoutMs, providerTimeoutMs);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/engine/runtime.ts"],"names":[],"mappings":"AAGA,oEAAoE;AACpE,mEAAmE;AACnE,MAAM,UAAU,0BAA0B,CAAC,SAA8B;IACvE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAEhC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAElC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,IAAI,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,4BAA4B,QAAQ,CAAC,IAAI,oDAAoD,CAC9F,CAAC;QACJ,CAAC;QAED,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,0BAA0B,CAAC,CAAC;QACzE,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;AACxB,CAAC;AAED,kEAAkE;AAClE,iEAAiE;AACjE,MAAM,UAAU,wBAAwB,CAAC,SAA4B;IACnE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAEhC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAElC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,CAAC;QAED,IAAI,IAAI,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,0BAA0B,QAAQ,CAAC,IAAI,oDAAoD,CAC5F,CAAC;QACJ,CAAC;QAED,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,0BAA0B,CAAC,CAAC;QACvE,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;AACxB,CAAC;AAED,kFAAkF;AAClF,8EAA8E;AAC9E,MAAM,UAAU,wBAAwB,CACtC,YAAoB,EACpB,SAA6B,EAC7B,gBAAkD;IAElD,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAC;IAEzD,IAAI,iBAAiB,KAAK,SAAS,EAAE,CAAC;QACpC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;AAC9F,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { MediaSearchResult, SearchQuery } from "../search/index.js";
|
|
2
|
+
export declare function needsFallbackTitleDiscovery(query: SearchQuery, relevantResults: MediaSearchResult[]): boolean;
|
|
3
|
+
export declare function needsPrimaryTitleBroadening(query: SearchQuery, relevantResults: MediaSearchResult[]): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { normalizeTitle } from "../merge/title.js";
|
|
2
|
+
// Decides whether slower fallback discovery can still resolve title identity.
|
|
3
|
+
// Решает, может ли более медленный fallback discovery уточнить identity по названию.
|
|
4
|
+
export function needsFallbackTitleDiscovery(query, relevantResults) {
|
|
5
|
+
if (!query.title?.trim() || hasExternalIds(query)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (relevantResults.length === 0) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
const normalizedQuery = normalizeTitle(query.title);
|
|
12
|
+
const isExactPrimaryMatch = (result) => [result.item.title, result.item.originalTitle]
|
|
13
|
+
.filter((title) => Boolean(title))
|
|
14
|
+
.some((title) => normalizeTitle(title) === normalizedQuery);
|
|
15
|
+
const exactMatches = relevantResults.filter((result) => isExactPrimaryMatch(result));
|
|
16
|
+
if (exactMatches.length === 0) {
|
|
17
|
+
return normalizedQuery.split(" ").filter(Boolean).length >= 2;
|
|
18
|
+
}
|
|
19
|
+
return isExactPrimaryMatch(relevantResults[0]) && exactMatches.length > 1;
|
|
20
|
+
}
|
|
21
|
+
// Broadens likely multi-word typos even when weak fuzzy candidates prevented an empty result.
|
|
22
|
+
// Расширяет вероятную multi-word опечатку, даже если weak fuzzy candidates дали непустой ответ.
|
|
23
|
+
export function needsPrimaryTitleBroadening(query, relevantResults) {
|
|
24
|
+
if (!query.title?.trim() || hasExternalIds(query)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const normalizedQuery = normalizeTitle(query.title);
|
|
28
|
+
return !relevantResults.some((result) => [result.item.title, result.item.originalTitle]
|
|
29
|
+
.filter((title) => Boolean(title))
|
|
30
|
+
.some((title) => normalizeTitle(title) === normalizedQuery));
|
|
31
|
+
}
|
|
32
|
+
function hasExternalIds(query) {
|
|
33
|
+
return Object.values(query.ids ?? {}).some(Boolean);
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=search-discovery.js.map
|