@media-engine/providers 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -242
- package/README.ru.md +204 -0
- 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 +11 -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 +53 -35
- 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 +67 -106
- package/dist/flixhq-streaming/index.js.map +1 -1
- package/dist/imdb-dataset/index.d.ts +20 -5
- package/dist/imdb-dataset/index.js +83 -197
- 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 +15 -33
- package/dist/kinobd/index.js.map +1 -1
- package/dist/kinobd-streaming/availability.d.ts +3 -0
- package/dist/kinobd-streaming/availability.js +179 -0
- package/dist/kinobd-streaming/availability.js.map +1 -0
- package/dist/kinobd-streaming/candidates.d.ts +32 -0
- package/dist/kinobd-streaming/candidates.js +282 -0
- package/dist/kinobd-streaming/candidates.js.map +1 -0
- package/dist/kinobd-streaming/config.d.ts +64 -0
- package/dist/kinobd-streaming/config.js +129 -0
- package/dist/kinobd-streaming/config.js.map +1 -0
- package/dist/kinobd-streaming/index.d.ts +3 -30
- package/dist/kinobd-streaming/index.js +2 -904
- package/dist/kinobd-streaming/index.js.map +1 -1
- package/dist/kinobd-streaming/players.d.ts +24 -0
- package/dist/kinobd-streaming/players.js +270 -0
- package/dist/kinobd-streaming/players.js.map +1 -0
- 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 +11 -0
- package/dist/kinobd-streaming/validation.js +170 -0
- package/dist/kinobd-streaming/validation.js.map +1 -0
- 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 +3 -0
- package/dist/shared/abort.js +30 -0
- package/dist/shared/abort.js.map +1 -0
- package/dist/shared/http.d.ts +13 -2
- package/dist/shared/http.js +77 -75
- package/dist/shared/http.js.map +1 -1
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +2 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/mapping.d.ts +5 -0
- package/dist/shared/mapping.js +32 -0
- package/dist/shared/mapping.js.map +1 -0
- package/dist/shared/options.d.ts +1 -0
- package/dist/shared/options.js +10 -0
- package/dist/shared/options.js.map +1 -0
- 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.d.ts +11 -0
- package/dist/shared/rate-limit.js +52 -0
- package/dist/shared/rate-limit.js.map +1 -0
- 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/retry.d.ts +10 -0
- package/dist/shared/retry.js +31 -0
- package/dist/shared/retry.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 +19 -11
- 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 +34 -111
- 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 +23 -6
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/wikidata/client.ts"],"names":[],"mappings":"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"}
|
package/dist/wikidata/index.d.ts
CHANGED
|
@@ -8,5 +8,8 @@ export interface WikidataProviderOptions {
|
|
|
8
8
|
fetch?: ProviderFetch;
|
|
9
9
|
version?: string;
|
|
10
10
|
searchLimit?: number;
|
|
11
|
+
entityLimit?: number;
|
|
12
|
+
cacheTtlMs?: number;
|
|
13
|
+
cacheMaxEntries?: number;
|
|
11
14
|
}
|
|
12
15
|
export declare function wikidataProvider(options?: WikidataProviderOptions): MediaProvider;
|
package/dist/wikidata/index.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeProviderOutputUrl, ProviderRateLimitGate, } from "../shared/index.js";
|
|
2
|
+
import { createProviderImage } from "../shared/mapping.js";
|
|
3
|
+
import { resolveBoundedIntegerOption } from "../shared/options.js";
|
|
4
|
+
import { MEDIA_ENGINE_DEFAULT_USER_AGENT } from "../package-version.js";
|
|
5
|
+
import { WikidataCache } from "./cache.js";
|
|
6
|
+
import { isRelevantWikidataTitleMatch } from "./candidates.js";
|
|
7
|
+
import { getWikidataEntityByImdbId, normalizeWikidataLanguage, searchWikidataEntities, } from "./client.js";
|
|
2
8
|
const PROVIDER_NAME = "wikidata";
|
|
3
9
|
const DEFAULT_BASE_URL = "https://www.wikidata.org";
|
|
4
10
|
const DEFAULT_SPARQL_URL = "https://query.wikidata.org/sparql";
|
|
5
11
|
const DEFAULT_LANGUAGE = "en";
|
|
6
12
|
const DEFAULT_SEARCH_LIMIT = 8;
|
|
7
|
-
const
|
|
13
|
+
const DEFAULT_ENTITY_LIMIT = 3;
|
|
14
|
+
const DEFAULT_CACHE_TTL_MS = 6 * 60 * 60 * 1_000;
|
|
15
|
+
const DEFAULT_CACHE_MAX_ENTRIES = 256;
|
|
8
16
|
const MOVIE_INSTANCE_IDS = new Set(["Q11424", "Q506240"]);
|
|
9
17
|
const SERIES_INSTANCE_IDS = new Set(["Q5398426", "Q1259759", "Q15416"]);
|
|
10
18
|
// Creates a Wikidata metadata provider for basic movie and series lookup.
|
|
@@ -15,11 +23,14 @@ export function wikidataProvider(options = {}) {
|
|
|
15
23
|
name: PROVIDER_NAME,
|
|
16
24
|
version: options.version,
|
|
17
25
|
kind: "metadata",
|
|
26
|
+
searchPosterMatchesDetails: true,
|
|
18
27
|
capabilities: {
|
|
19
28
|
mediaTypes: ["movie", "series"],
|
|
29
|
+
searchEnrichment: false,
|
|
20
30
|
search: {
|
|
21
31
|
byTitle: true,
|
|
22
32
|
byExternalIds: ["imdb"],
|
|
33
|
+
titleDiscovery: "fallback",
|
|
23
34
|
},
|
|
24
35
|
details: {
|
|
25
36
|
byExternalIds: ["imdb"],
|
|
@@ -37,13 +48,18 @@ export function wikidataProvider(options = {}) {
|
|
|
37
48
|
// Builds provider config with conservative defaults for public Wikimedia APIs.
|
|
38
49
|
// Собирает конфигурацию с консервативными defaults для публичных Wikimedia API.
|
|
39
50
|
function createWikidataConfig(options) {
|
|
51
|
+
const cacheTtlMs = resolveBoundedIntegerOption(options.cacheTtlMs, DEFAULT_CACHE_TTL_MS, "Wikidata cacheTtlMs", 0, 7 * 24 * 60 * 60 * 1_000);
|
|
52
|
+
const cacheMaxEntries = resolveBoundedIntegerOption(options.cacheMaxEntries, DEFAULT_CACHE_MAX_ENTRIES, "Wikidata cacheMaxEntries", 2, 2_048);
|
|
40
53
|
return {
|
|
41
54
|
baseUrl: trimTrailingSlash(options.baseUrl ?? DEFAULT_BASE_URL),
|
|
42
55
|
sparqlUrl: options.sparqlUrl ?? DEFAULT_SPARQL_URL,
|
|
43
|
-
language:
|
|
44
|
-
userAgent: options.userAgent ??
|
|
56
|
+
language: normalizeWikidataLanguage(options.language ?? DEFAULT_LANGUAGE),
|
|
57
|
+
userAgent: options.userAgent ?? MEDIA_ENGINE_DEFAULT_USER_AGENT,
|
|
45
58
|
fetch: options.fetch,
|
|
46
|
-
|
|
59
|
+
rateLimitGate: new ProviderRateLimitGate(),
|
|
60
|
+
searchLimit: resolveBoundedIntegerOption(options.searchLimit, DEFAULT_SEARCH_LIMIT, "Wikidata searchLimit", 1, 50),
|
|
61
|
+
entityLimit: resolveBoundedIntegerOption(options.entityLimit, DEFAULT_ENTITY_LIMIT, "Wikidata entityLimit", 1, 10),
|
|
62
|
+
cache: new WikidataCache({ maxEntries: cacheMaxEntries, ttlMs: cacheTtlMs }),
|
|
47
63
|
};
|
|
48
64
|
}
|
|
49
65
|
// Runs title or IMDb ID search through Wikidata without requiring credentials.
|
|
@@ -59,8 +75,7 @@ async function searchWikidata(config, query, context) {
|
|
|
59
75
|
if (!query.title?.trim()) {
|
|
60
76
|
return [];
|
|
61
77
|
}
|
|
62
|
-
const
|
|
63
|
-
const entities = await getEntities(config, entityIds, context);
|
|
78
|
+
const entities = await searchWikidataEntities(config, query, context);
|
|
64
79
|
return entities
|
|
65
80
|
.map((entity) => mapEntityToItem(config, entity, query, context))
|
|
66
81
|
.filter((item) => item !== undefined)
|
|
@@ -86,58 +101,12 @@ async function getWikidataDetails(config, query, context) {
|
|
|
86
101
|
// Resolves one Wikidata entity through an exact IMDb ID SPARQL lookup.
|
|
87
102
|
// Находит одну Wikidata entity через точный SPARQL lookup по IMDb ID.
|
|
88
103
|
async function getDetailsByImdbId(config, imdbId, type, context) {
|
|
89
|
-
const
|
|
90
|
-
if (!entityId) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
const [entity] = await getEntities(config, [entityId], context);
|
|
104
|
+
const entity = await getWikidataEntityByImdbId(config, imdbId, context);
|
|
94
105
|
const item = entity
|
|
95
106
|
? mapEntityToItem(config, entity, { ids: { imdb: imdbId }, type }, context)
|
|
96
107
|
: undefined;
|
|
97
108
|
return item ? itemToDetails(item) : null;
|
|
98
109
|
}
|
|
99
|
-
// Searches Wikidata item IDs using the public Action API search endpoint.
|
|
100
|
-
// Ищет Wikidata item IDs через публичный search endpoint Action API.
|
|
101
|
-
async function searchEntityIds(config, query, context) {
|
|
102
|
-
const url = new URL(`${config.baseUrl}/w/api.php`);
|
|
103
|
-
const language = normalizeLanguage(query.language ?? context.language ?? config.language);
|
|
104
|
-
url.searchParams.set("action", "wbsearchentities");
|
|
105
|
-
url.searchParams.set("format", "json");
|
|
106
|
-
url.searchParams.set("type", "item");
|
|
107
|
-
url.searchParams.set("language", language);
|
|
108
|
-
url.searchParams.set("uselang", language);
|
|
109
|
-
url.searchParams.set("search", query.title ?? "");
|
|
110
|
-
url.searchParams.set("limit", String(config.searchLimit));
|
|
111
|
-
const response = await requestJson(config, url, context);
|
|
112
|
-
return (response.search ?? [])
|
|
113
|
-
.map((result) => result.id)
|
|
114
|
-
.filter((id) => Boolean(id));
|
|
115
|
-
}
|
|
116
|
-
// Loads full Wikidata entity JSON for candidate IDs.
|
|
117
|
-
// Загружает полный JSON Wikidata entity для candidate IDs.
|
|
118
|
-
async function getEntities(config, entityIds, context) {
|
|
119
|
-
if (!entityIds.length) {
|
|
120
|
-
return [];
|
|
121
|
-
}
|
|
122
|
-
const url = new URL(`${config.baseUrl}/w/api.php`);
|
|
123
|
-
url.searchParams.set("action", "wbgetentities");
|
|
124
|
-
url.searchParams.set("format", "json");
|
|
125
|
-
url.searchParams.set("ids", entityIds.join("|"));
|
|
126
|
-
url.searchParams.set("props", "labels|descriptions|claims");
|
|
127
|
-
const response = await requestJson(config, url, context);
|
|
128
|
-
return Object.values(response.entities ?? {}).filter(isKnownEntity);
|
|
129
|
-
}
|
|
130
|
-
// Finds one Wikidata item ID by exact IMDb title ID.
|
|
131
|
-
// Находит один Wikidata item ID по точному IMDb title ID.
|
|
132
|
-
async function findEntityIdByImdbId(config, imdbId, context) {
|
|
133
|
-
const url = new URL(config.sparqlUrl);
|
|
134
|
-
const query = `SELECT ?item WHERE { ?item wdt:P345 "${escapeSparqlString(imdbId)}". } LIMIT 1`;
|
|
135
|
-
url.searchParams.set("format", "json");
|
|
136
|
-
url.searchParams.set("query", query);
|
|
137
|
-
const response = await requestJson(config, url, context);
|
|
138
|
-
const itemUrl = response.results?.bindings?.[0]?.item?.value;
|
|
139
|
-
return itemUrl?.split("/").at(-1);
|
|
140
|
-
}
|
|
141
110
|
// Maps a Wikidata entity into the normalized compact media model.
|
|
142
111
|
// Преобразует Wikidata entity в нормализованную compact media model.
|
|
143
112
|
function mapEntityToItem(config, entity, query, context) {
|
|
@@ -146,7 +115,7 @@ function mapEntityToItem(config, entity, query, context) {
|
|
|
146
115
|
return undefined;
|
|
147
116
|
}
|
|
148
117
|
const title = getLabel(entity, context.language ?? config.language);
|
|
149
|
-
if (!title || (query.title && !
|
|
118
|
+
if (!title || (query.title && !isRelevantWikidataTitleMatch(title, query.title))) {
|
|
150
119
|
return undefined;
|
|
151
120
|
}
|
|
152
121
|
const releaseDate = getTimeClaim(entity, "P577");
|
|
@@ -219,7 +188,7 @@ function createProviderSource(ids) {
|
|
|
219
188
|
return {
|
|
220
189
|
provider: PROVIDER_NAME,
|
|
221
190
|
ids,
|
|
222
|
-
url: imdbId ? `https://www.imdb.com/title/${imdbId}/` : undefined,
|
|
191
|
+
url: normalizeProviderOutputUrl(imdbId ? `https://www.imdb.com/title/${imdbId}/` : undefined),
|
|
223
192
|
};
|
|
224
193
|
}
|
|
225
194
|
// Creates normalized external IDs from Wikidata claims.
|
|
@@ -245,12 +214,13 @@ function getMediaType(entity) {
|
|
|
245
214
|
// Reads localized entity label with English fallback.
|
|
246
215
|
// Читает локализованный label entity с fallback на English.
|
|
247
216
|
function getLabel(entity, language) {
|
|
248
|
-
return entity.labels?.[
|
|
217
|
+
return entity.labels?.[normalizeWikidataLanguage(language)]?.value ?? entity.labels?.en?.value;
|
|
249
218
|
}
|
|
250
219
|
// Reads localized entity description with English fallback.
|
|
251
220
|
// Читает локализованное description entity с fallback на English.
|
|
252
221
|
function getDescription(entity, language) {
|
|
253
|
-
return (entity.descriptions?.[
|
|
222
|
+
return (entity.descriptions?.[normalizeWikidataLanguage(language)]?.value ??
|
|
223
|
+
entity.descriptions?.en?.value);
|
|
254
224
|
}
|
|
255
225
|
// Reads one string-valued claim.
|
|
256
226
|
// Читает один claim со строковым значением.
|
|
@@ -290,49 +260,12 @@ function getClaimValues(entity, property) {
|
|
|
290
260
|
// Создает Wikimedia image URL из Commons filename claim.
|
|
291
261
|
function getImage(entity) {
|
|
292
262
|
const filename = getStringClaim(entity, "P18");
|
|
293
|
-
|
|
294
|
-
?
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
: undefined;
|
|
300
|
-
}
|
|
301
|
-
// Sends provider JSON requests with Wikimedia-friendly headers.
|
|
302
|
-
// Отправляет JSON-запросы провайдера с Wikimedia-friendly headers.
|
|
303
|
-
function requestJson(config, url, context) {
|
|
304
|
-
return fetchJson({
|
|
305
|
-
provider: PROVIDER_NAME,
|
|
306
|
-
url,
|
|
307
|
-
context,
|
|
308
|
-
fetch: config.fetch,
|
|
309
|
-
init: {
|
|
310
|
-
headers: {
|
|
311
|
-
accept: "application/json",
|
|
312
|
-
"user-agent": config.userAgent,
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
// Checks that entity data has a stable ID.
|
|
318
|
-
// Проверяет, что entity data содержит стабильный ID.
|
|
319
|
-
function isKnownEntity(entity) {
|
|
320
|
-
return Boolean(entity.id);
|
|
321
|
-
}
|
|
322
|
-
// Keeps title matching conservative so generic search noise is dropped.
|
|
323
|
-
// Держит matching названия консервативным, чтобы отсечь шум generic search.
|
|
324
|
-
function isRelevantTitleMatch(title, queryTitle) {
|
|
325
|
-
const normalizedTitle = normalizeSearchText(title);
|
|
326
|
-
const normalizedQuery = normalizeSearchText(queryTitle);
|
|
327
|
-
return normalizedTitle.includes(normalizedQuery) || normalizedQuery.includes(normalizedTitle);
|
|
328
|
-
}
|
|
329
|
-
// Normalizes text for loose local comparisons.
|
|
330
|
-
// Нормализует текст для мягких локальных сравнений.
|
|
331
|
-
function normalizeSearchText(value) {
|
|
332
|
-
return value
|
|
333
|
-
.toLocaleLowerCase()
|
|
334
|
-
.replaceAll(/[^\p{L}\p{N}]+/gu, " ")
|
|
335
|
-
.trim();
|
|
263
|
+
const url = filename?.startsWith("http://") || filename?.startsWith("https://")
|
|
264
|
+
? filename
|
|
265
|
+
: filename
|
|
266
|
+
? `https://commons.wikimedia.org/wiki/Special:FilePath/${encodeURIComponent(filename.replaceAll(" ", "_"))}`
|
|
267
|
+
: undefined;
|
|
268
|
+
return createProviderImage(url, "poster", PROVIDER_NAME);
|
|
336
269
|
}
|
|
337
270
|
// Extracts a four-digit year from an ISO-like date.
|
|
338
271
|
// Достает четырехзначный год из ISO-like date.
|
|
@@ -340,16 +273,6 @@ function getYear(date) {
|
|
|
340
273
|
const year = date?.slice(0, 4);
|
|
341
274
|
return year && /^\d{4}$/.test(year) ? Number(year) : undefined;
|
|
342
275
|
}
|
|
343
|
-
// Normalizes BCP-style language tags to Wikidata language codes.
|
|
344
|
-
// Нормализует BCP-style language tags в language codes Wikidata.
|
|
345
|
-
function normalizeLanguage(language) {
|
|
346
|
-
return language.split("-")[0]?.trim().toLocaleLowerCase() || DEFAULT_LANGUAGE;
|
|
347
|
-
}
|
|
348
|
-
// Escapes a literal string for the narrow SPARQL query used here.
|
|
349
|
-
// Экранирует literal string для узкого SPARQL-запроса здесь.
|
|
350
|
-
function escapeSparqlString(value) {
|
|
351
|
-
return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
352
|
-
}
|
|
353
276
|
function trimTrailingSlash(value) {
|
|
354
277
|
return value.replace(/\/+$/, "");
|
|
355
278
|
}
|