@zivue/zuuid 0.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.
- package/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +368 -0
- package/dist/client.d.ts +29 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +39 -0
- package/dist/entity.d.ts +106 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/entity.js +101 -0
- package/dist/hash.d.ts +3 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +9 -0
- package/dist/identity.d.ts +14 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +39 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/providers/tmdb/client.d.ts +30 -0
- package/dist/providers/tmdb/client.d.ts.map +1 -0
- package/dist/providers/tmdb/client.js +92 -0
- package/dist/providers/tmdb/constants.d.ts +5 -0
- package/dist/providers/tmdb/constants.d.ts.map +1 -0
- package/dist/providers/tmdb/constants.js +4 -0
- package/dist/providers/tmdb/index.d.ts +7 -0
- package/dist/providers/tmdb/index.d.ts.map +1 -0
- package/dist/providers/tmdb/index.js +6 -0
- package/dist/providers/tmdb/movie.d.ts +164 -0
- package/dist/providers/tmdb/movie.d.ts.map +1 -0
- package/dist/providers/tmdb/movie.js +537 -0
- package/dist/providers/tmdb/person.d.ts +84 -0
- package/dist/providers/tmdb/person.d.ts.map +1 -0
- package/dist/providers/tmdb/person.js +345 -0
- package/dist/providers/tmdb/tv.d.ts +181 -0
- package/dist/providers/tmdb/tv.d.ts.map +1 -0
- package/dist/providers/tmdb/tv.js +522 -0
- package/dist/providers/tmdb/types.d.ts +36 -0
- package/dist/providers/tmdb/types.d.ts.map +1 -0
- package/dist/providers/tmdb/types.js +1 -0
- package/dist/source.d.ts +35 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +53 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/uuid.d.ts +3 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +32 -0
- package/package.json +72 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { createZuuidData } from "../../entity.js";
|
|
2
|
+
import { providerZuuid } from "../../identity.js";
|
|
3
|
+
import { attachSourceMetadata, createSourceRecord } from "../../source.js";
|
|
4
|
+
import { TMDB_BACKDROP_BASE_URL, TMDB_POSTER_BASE_URL, TMDB_PROVIDER } from "./constants.js";
|
|
5
|
+
export const TMDB_MOVIE_CATEGORY = "movie";
|
|
6
|
+
export async function fetchTmdbMovieSourceRecord(provider, input) {
|
|
7
|
+
const id = String(input.id).trim();
|
|
8
|
+
if (!id) {
|
|
9
|
+
throw new Error("TMDB movie id must not be empty");
|
|
10
|
+
}
|
|
11
|
+
if (!/^\d+$/.test(id)) {
|
|
12
|
+
throw new Error(`TMDB movie id must be numeric: ${id}`);
|
|
13
|
+
}
|
|
14
|
+
const payload = await provider.getJson(`/movie/${id}`, {
|
|
15
|
+
language: provider.language,
|
|
16
|
+
append_to_response: "alternative_titles,credits,external_ids,images,keywords,recommendations,similar,translations,release_dates"
|
|
17
|
+
});
|
|
18
|
+
if (!payload) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return createSourceRecord({
|
|
22
|
+
source: { provider: TMDB_PROVIDER, category: TMDB_MOVIE_CATEGORY, externalId: id },
|
|
23
|
+
payload: payload
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export async function searchTmdbMovieSourceRecords(provider, input) {
|
|
27
|
+
const payload = await fetchTmdbMovieSearchResults(provider, input);
|
|
28
|
+
return {
|
|
29
|
+
results: await sourceRecordsFromSearchResults(TMDB_MOVIE_CATEGORY, payload.results),
|
|
30
|
+
pagination: paginationFromTmdbSearchResponse(payload)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export async function searchTmdbMovies(provider, input, options = {}) {
|
|
34
|
+
const payload = await fetchTmdbMovieSearchResults(provider, input);
|
|
35
|
+
const searchResults = [];
|
|
36
|
+
for (const result of payload.results ?? []) {
|
|
37
|
+
if (!result.id) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const externalId = String(result.id);
|
|
41
|
+
const title = stringField(result.title) ?? stringField(result.original_title);
|
|
42
|
+
if (!title) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const zuuid = await providerZuuid({ provider: TMDB_PROVIDER, category: TMDB_MOVIE_CATEGORY, externalId });
|
|
46
|
+
searchResults.push({
|
|
47
|
+
id: zuuid,
|
|
48
|
+
zuuid,
|
|
49
|
+
category: TMDB_MOVIE_CATEGORY,
|
|
50
|
+
title,
|
|
51
|
+
date: stringField(result.release_date) ?? null,
|
|
52
|
+
cover: mediaUrl(result.poster_path ?? undefined, options.posterBaseUrl ?? TMDB_POSTER_BASE_URL) ?? null,
|
|
53
|
+
rating: typeof result.vote_average === "number" ? result.vote_average : null,
|
|
54
|
+
weight: typeof result.popularity === "number" ? result.popularity : null,
|
|
55
|
+
relationType: null,
|
|
56
|
+
attribute: null,
|
|
57
|
+
order: null,
|
|
58
|
+
source: { source: TMDB_PROVIDER, category: TMDB_MOVIE_CATEGORY, value: externalId }
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
results: searchResults,
|
|
63
|
+
pagination: paginationFromTmdbSearchResponse(payload)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function fetchTmdbMovieSearchResults(provider, input) {
|
|
67
|
+
const query = searchQuery(input.query, "TMDB movie search query");
|
|
68
|
+
const payload = await provider.getJson("/search/movie", {
|
|
69
|
+
...searchParams(provider, input),
|
|
70
|
+
query,
|
|
71
|
+
...(input.region ? { region: input.region } : {}),
|
|
72
|
+
...(input.year !== undefined ? { year: String(input.year) } : {}),
|
|
73
|
+
...(input.primaryReleaseYear !== undefined ? { primary_release_year: String(input.primaryReleaseYear) } : {})
|
|
74
|
+
});
|
|
75
|
+
return payload ?? {};
|
|
76
|
+
}
|
|
77
|
+
export async function transformTmdbMovie(source, options = {}) {
|
|
78
|
+
if (source.source.provider !== TMDB_PROVIDER || source.source.category !== TMDB_MOVIE_CATEGORY) {
|
|
79
|
+
throw new Error(`unsupported TMDB source: ${source.source.provider}:${source.source.category}`);
|
|
80
|
+
}
|
|
81
|
+
const payload = source.payload;
|
|
82
|
+
const tmdbId = tmdbMovieId(source, payload);
|
|
83
|
+
const title = stringField(payload.title) ?? stringField(payload.original_title);
|
|
84
|
+
if (!title) {
|
|
85
|
+
throw new Error("missing required TMDB movie field: title");
|
|
86
|
+
}
|
|
87
|
+
const zuuid = await providerZuuid({
|
|
88
|
+
provider: TMDB_PROVIDER,
|
|
89
|
+
category: TMDB_MOVIE_CATEGORY,
|
|
90
|
+
externalId: tmdbId
|
|
91
|
+
});
|
|
92
|
+
const data = createZuuidData({
|
|
93
|
+
zuuid,
|
|
94
|
+
category: TMDB_MOVIE_CATEGORY,
|
|
95
|
+
primaryTitle: title
|
|
96
|
+
});
|
|
97
|
+
const releaseDate = stringField(payload.release_date);
|
|
98
|
+
if (releaseDate) {
|
|
99
|
+
validateDate(releaseDate, "release_date");
|
|
100
|
+
data.primaryDate = releaseDate;
|
|
101
|
+
}
|
|
102
|
+
if (typeof payload.vote_average === "number") {
|
|
103
|
+
data.rating = payload.vote_average;
|
|
104
|
+
}
|
|
105
|
+
const overview = stringField(payload.overview);
|
|
106
|
+
if (overview) {
|
|
107
|
+
data.descriptions.push({
|
|
108
|
+
language: stringField(payload.original_language),
|
|
109
|
+
value: overview,
|
|
110
|
+
source: TMDB_PROVIDER
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const originalTitle = stringField(payload.original_title);
|
|
114
|
+
if (originalTitle && originalTitle !== title) {
|
|
115
|
+
data.aliases.push({
|
|
116
|
+
value: originalTitle,
|
|
117
|
+
language: stringField(payload.original_language),
|
|
118
|
+
aliasType: "original_title",
|
|
119
|
+
isPrimary: false
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
addExternalIds(data, payload);
|
|
123
|
+
addAlternativeTitles(data, payload, title);
|
|
124
|
+
addTranslations(data, payload, title);
|
|
125
|
+
addImage(data, "poster", payload.poster_path, options.posterBaseUrl ?? TMDB_POSTER_BASE_URL, true);
|
|
126
|
+
addImage(data, "backdrop", payload.backdrop_path, options.backdropBaseUrl ?? TMDB_BACKDROP_BASE_URL, false);
|
|
127
|
+
addImages(data, payload, options);
|
|
128
|
+
data.cover = data.media.find((media) => media.mediaCategory === "poster")?.url;
|
|
129
|
+
addGenreTags(data, payload);
|
|
130
|
+
addKeywordTags(data, payload);
|
|
131
|
+
await addCredits(data, payload);
|
|
132
|
+
await addCollectionsAndCompanies(data, payload, options);
|
|
133
|
+
await addRelatedMovies(data, "similar", payload.similar, options);
|
|
134
|
+
await addRelatedMovies(data, "related", payload.recommendations, options);
|
|
135
|
+
addDetail(data, "original_language", payload.original_language);
|
|
136
|
+
addDetail(data, "status", payload.status);
|
|
137
|
+
addDetail(data, "tagline", payload.tagline);
|
|
138
|
+
addNumberDetail(data, "runtime_minutes", payload.runtime);
|
|
139
|
+
addNumberDetail(data, "vote_count", payload.vote_count);
|
|
140
|
+
addNumberDetail(data, "popularity", payload.popularity);
|
|
141
|
+
addNumberDetail(data, "budget", payload.budget);
|
|
142
|
+
addNumberDetail(data, "revenue", payload.revenue);
|
|
143
|
+
addDetail(data, "homepage", payload.homepage);
|
|
144
|
+
addBooleanDetail(data, "adult", payload.adult);
|
|
145
|
+
addBooleanDetail(data, "video", payload.video);
|
|
146
|
+
addBooleanDetail(data, "softcore", payload.softcore);
|
|
147
|
+
addArrayDetail(data, "origin_country", payload.origin_country);
|
|
148
|
+
addMovieCertifications(data, payload);
|
|
149
|
+
addStructuredDetail(data, "production_countries", payload.production_countries);
|
|
150
|
+
addStructuredDetail(data, "release_dates", payload.release_dates?.results);
|
|
151
|
+
addStructuredDetail(data, "spoken_languages", payload.spoken_languages);
|
|
152
|
+
addStructuredDetail(data, "watch_providers", payload.watch_providers?.results);
|
|
153
|
+
return attachSourceMetadata(data, source);
|
|
154
|
+
}
|
|
155
|
+
function tmdbMovieId(source, payload) {
|
|
156
|
+
const sourceId = source.source.externalId.trim();
|
|
157
|
+
if (sourceId) {
|
|
158
|
+
return sourceId;
|
|
159
|
+
}
|
|
160
|
+
const payloadId = payload.id === undefined ? undefined : String(payload.id).trim();
|
|
161
|
+
if (payloadId) {
|
|
162
|
+
return payloadId;
|
|
163
|
+
}
|
|
164
|
+
throw new Error("missing required TMDB movie field: id");
|
|
165
|
+
}
|
|
166
|
+
function addImage(data, mediaCategory, path, baseUrl, isPrimary, image) {
|
|
167
|
+
const value = stringField(path);
|
|
168
|
+
if (!value) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const url = value.startsWith("http://") || value.startsWith("https://")
|
|
172
|
+
? value
|
|
173
|
+
: baseUrl
|
|
174
|
+
? `${baseUrl.replace(/\/$/, "")}/${value.replace(/^\//, "")}`
|
|
175
|
+
: value;
|
|
176
|
+
data.media.push({
|
|
177
|
+
url,
|
|
178
|
+
mediaType: "image",
|
|
179
|
+
mediaCategory,
|
|
180
|
+
width: image?.width,
|
|
181
|
+
height: image?.height,
|
|
182
|
+
isPrimary,
|
|
183
|
+
source: TMDB_PROVIDER
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function addImages(data, payload, options) {
|
|
187
|
+
for (const image of payload.images?.posters ?? []) {
|
|
188
|
+
addImage(data, "poster", image.file_path, options.posterBaseUrl ?? TMDB_POSTER_BASE_URL, false, image);
|
|
189
|
+
}
|
|
190
|
+
for (const image of payload.images?.backdrops ?? []) {
|
|
191
|
+
addImage(data, "backdrop", image.file_path, options.backdropBaseUrl ?? TMDB_BACKDROP_BASE_URL, false, image);
|
|
192
|
+
}
|
|
193
|
+
for (const image of payload.images?.logos ?? []) {
|
|
194
|
+
addImage(data, "logo", image.file_path, options.posterBaseUrl ?? TMDB_POSTER_BASE_URL, false, image);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function addExternalIds(data, payload) {
|
|
198
|
+
const ids = payload.external_ids ?? {};
|
|
199
|
+
addExternalId(data, "imdb", TMDB_MOVIE_CATEGORY, stringField(ids.imdb_id ?? undefined) ?? stringField(payload.imdb_id));
|
|
200
|
+
addExternalId(data, "wikidata", TMDB_MOVIE_CATEGORY, stringField(ids.wikidata_id ?? undefined));
|
|
201
|
+
addExternalId(data, "facebook", TMDB_MOVIE_CATEGORY, stringField(ids.facebook_id ?? undefined));
|
|
202
|
+
addExternalId(data, "instagram", TMDB_MOVIE_CATEGORY, stringField(ids.instagram_id ?? undefined));
|
|
203
|
+
addExternalId(data, "twitter", TMDB_MOVIE_CATEGORY, stringField(ids.twitter_id ?? undefined));
|
|
204
|
+
}
|
|
205
|
+
function addExternalId(data, source, category, value) {
|
|
206
|
+
if (!value || data.externalIds.some((id) => id.source === source && id.category === category && id.value === value)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
data.externalIds.push({ source, category, value });
|
|
210
|
+
}
|
|
211
|
+
function addAlternativeTitles(data, payload, primaryTitle) {
|
|
212
|
+
for (const item of payload.alternative_titles?.titles ?? []) {
|
|
213
|
+
const value = stringField(item.title);
|
|
214
|
+
if (!value || value === primaryTitle) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
data.aliases.push({
|
|
218
|
+
value,
|
|
219
|
+
region: stringField(item.iso_3166_1),
|
|
220
|
+
aliasType: stringField(item.type) ?? "alternative_title",
|
|
221
|
+
isPrimary: false,
|
|
222
|
+
source: TMDB_PROVIDER
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function addTranslations(data, payload, primaryTitle) {
|
|
227
|
+
for (const item of payload.translations?.translations ?? []) {
|
|
228
|
+
const title = stringField(item.data?.title);
|
|
229
|
+
const language = stringField(item.iso_639_1);
|
|
230
|
+
const region = stringField(item.iso_3166_1);
|
|
231
|
+
if (title && title !== primaryTitle) {
|
|
232
|
+
data.aliases.push({
|
|
233
|
+
value: title,
|
|
234
|
+
language,
|
|
235
|
+
region,
|
|
236
|
+
aliasType: "translation",
|
|
237
|
+
isPrimary: false,
|
|
238
|
+
source: TMDB_PROVIDER
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
const overview = stringField(item.data?.overview);
|
|
242
|
+
if (overview) {
|
|
243
|
+
data.descriptions.push({
|
|
244
|
+
language,
|
|
245
|
+
region,
|
|
246
|
+
value: overview,
|
|
247
|
+
source: TMDB_PROVIDER
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function addGenreTags(data, payload) {
|
|
253
|
+
for (const genre of payload.genres ?? []) {
|
|
254
|
+
addTag(data, genre.name);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function addKeywordTags(data, payload) {
|
|
258
|
+
for (const keyword of payload.keywords?.keywords ?? []) {
|
|
259
|
+
addTag(data, keyword.name);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function addTag(data, value) {
|
|
263
|
+
const tag = stringField(value)?.toLowerCase();
|
|
264
|
+
if (tag && !data.tags.includes(tag)) {
|
|
265
|
+
data.tags.push(tag);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function addCredits(data, payload) {
|
|
269
|
+
for (const cast of payload.credits?.cast ?? []) {
|
|
270
|
+
const id = cast.id === undefined ? undefined : String(cast.id);
|
|
271
|
+
const name = stringField(cast.name);
|
|
272
|
+
if (!id || !name) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const zuuid = await providerZuuid({ provider: TMDB_PROVIDER, category: "person", externalId: id });
|
|
276
|
+
data.relations.push({
|
|
277
|
+
id: zuuid,
|
|
278
|
+
zuuid,
|
|
279
|
+
relationType: "performed_by",
|
|
280
|
+
direction: "outgoing",
|
|
281
|
+
title: name,
|
|
282
|
+
category: "person",
|
|
283
|
+
date: null,
|
|
284
|
+
cover: mediaUrl(cast.profile_path ?? undefined, TMDB_POSTER_BASE_URL) ?? null,
|
|
285
|
+
rating: null,
|
|
286
|
+
weight: null,
|
|
287
|
+
source: TMDB_PROVIDER,
|
|
288
|
+
externalId: id,
|
|
289
|
+
attribute: stringField(cast.character) ?? null,
|
|
290
|
+
order: cast.order ?? null
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
for (const crew of payload.credits?.crew ?? []) {
|
|
294
|
+
const id = crew.id === undefined ? undefined : String(crew.id);
|
|
295
|
+
const name = stringField(crew.name);
|
|
296
|
+
const job = stringField(crew.job);
|
|
297
|
+
if (!id || !name || !job) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const zuuid = await providerZuuid({ provider: TMDB_PROVIDER, category: "person", externalId: id });
|
|
301
|
+
data.relations.push({
|
|
302
|
+
id: zuuid,
|
|
303
|
+
zuuid,
|
|
304
|
+
relationType: relationForCrewJob(job),
|
|
305
|
+
direction: "outgoing",
|
|
306
|
+
title: name,
|
|
307
|
+
category: "person",
|
|
308
|
+
date: null,
|
|
309
|
+
cover: mediaUrl(crew.profile_path ?? undefined, TMDB_POSTER_BASE_URL) ?? null,
|
|
310
|
+
rating: null,
|
|
311
|
+
weight: null,
|
|
312
|
+
source: TMDB_PROVIDER,
|
|
313
|
+
externalId: id,
|
|
314
|
+
attribute: job,
|
|
315
|
+
order: null
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function addCollectionsAndCompanies(data, payload, options) {
|
|
320
|
+
if (payload.belongs_to_collection?.id && payload.belongs_to_collection.name) {
|
|
321
|
+
const id = String(payload.belongs_to_collection.id);
|
|
322
|
+
const zuuid = await providerZuuid({ provider: TMDB_PROVIDER, category: "collection", externalId: id });
|
|
323
|
+
data.relations.push({
|
|
324
|
+
id: zuuid,
|
|
325
|
+
zuuid,
|
|
326
|
+
relationType: "part_of",
|
|
327
|
+
direction: "outgoing",
|
|
328
|
+
title: payload.belongs_to_collection.name,
|
|
329
|
+
category: "collection",
|
|
330
|
+
date: null,
|
|
331
|
+
cover: mediaUrl(payload.belongs_to_collection.poster_path, options.posterBaseUrl ?? TMDB_POSTER_BASE_URL) ?? null,
|
|
332
|
+
rating: null,
|
|
333
|
+
weight: null,
|
|
334
|
+
source: TMDB_PROVIDER,
|
|
335
|
+
externalId: id,
|
|
336
|
+
attribute: null,
|
|
337
|
+
order: null
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
for (const company of payload.production_companies ?? []) {
|
|
341
|
+
if (!company.id || !company.name) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const id = String(company.id);
|
|
345
|
+
const zuuid = await providerZuuid({ provider: TMDB_PROVIDER, category: "company", externalId: id });
|
|
346
|
+
data.relations.push({
|
|
347
|
+
id: zuuid,
|
|
348
|
+
zuuid,
|
|
349
|
+
relationType: "published_by",
|
|
350
|
+
direction: "outgoing",
|
|
351
|
+
title: company.name,
|
|
352
|
+
category: "company",
|
|
353
|
+
date: null,
|
|
354
|
+
cover: mediaUrl(company.logo_path ?? undefined, options.posterBaseUrl ?? TMDB_POSTER_BASE_URL) ?? null,
|
|
355
|
+
rating: null,
|
|
356
|
+
weight: null,
|
|
357
|
+
source: TMDB_PROVIDER,
|
|
358
|
+
externalId: id,
|
|
359
|
+
attribute: stringField(company.origin_country) ?? null,
|
|
360
|
+
order: null
|
|
361
|
+
});
|
|
362
|
+
addImage(data, "company_logo", company.logo_path ?? undefined, options.posterBaseUrl ?? TMDB_POSTER_BASE_URL, false);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async function addRelatedMovies(data, recommendationType, list, options) {
|
|
366
|
+
for (const item of list?.results ?? []) {
|
|
367
|
+
if (!item.id) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const id = String(item.id);
|
|
371
|
+
const title = stringField(item.title) ?? stringField(item.original_title);
|
|
372
|
+
if (!title) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const zuuid = await providerZuuid({ provider: TMDB_PROVIDER, category: TMDB_MOVIE_CATEGORY, externalId: id });
|
|
376
|
+
const rating = typeof item.vote_average === "number" ? item.vote_average : null;
|
|
377
|
+
data.recommendations.push({
|
|
378
|
+
id: zuuid,
|
|
379
|
+
zuuid,
|
|
380
|
+
recommendationType,
|
|
381
|
+
relationType: recommendationType,
|
|
382
|
+
weight: rating,
|
|
383
|
+
source: TMDB_PROVIDER,
|
|
384
|
+
title,
|
|
385
|
+
category: TMDB_MOVIE_CATEGORY,
|
|
386
|
+
date: stringField(item.release_date) ?? null,
|
|
387
|
+
cover: mediaUrl(item.poster_path ?? undefined, options.posterBaseUrl ?? TMDB_POSTER_BASE_URL) ?? null,
|
|
388
|
+
rating,
|
|
389
|
+
externalId: id,
|
|
390
|
+
attribute: null,
|
|
391
|
+
order: null,
|
|
392
|
+
reasons: [recommendationType]
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function relationForCrewJob(job) {
|
|
397
|
+
switch (job.toLowerCase()) {
|
|
398
|
+
case "director":
|
|
399
|
+
return "directed_by";
|
|
400
|
+
case "screenplay":
|
|
401
|
+
case "writer":
|
|
402
|
+
case "novel":
|
|
403
|
+
return "authored_by";
|
|
404
|
+
case "producer":
|
|
405
|
+
return "produced_by";
|
|
406
|
+
default:
|
|
407
|
+
return "related_to";
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function addDetail(data, key, value) {
|
|
411
|
+
const normalized = stringField(value);
|
|
412
|
+
if (!normalized) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
data.details.push({ key, value: normalized, source: TMDB_PROVIDER });
|
|
416
|
+
}
|
|
417
|
+
function addNumberDetail(data, key, value) {
|
|
418
|
+
if (typeof value !== "number") {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
data.details.push({ key, value, source: TMDB_PROVIDER });
|
|
422
|
+
}
|
|
423
|
+
function addBooleanDetail(data, key, value) {
|
|
424
|
+
if (typeof value !== "boolean") {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
data.details.push({ key, value, source: TMDB_PROVIDER });
|
|
428
|
+
}
|
|
429
|
+
function addArrayDetail(data, key, value) {
|
|
430
|
+
if (!value?.length) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
data.details.push({ key, value, source: TMDB_PROVIDER });
|
|
434
|
+
}
|
|
435
|
+
function addMovieCertifications(data, payload) {
|
|
436
|
+
const certifications = [];
|
|
437
|
+
for (const country of payload.release_dates?.results ?? []) {
|
|
438
|
+
const region = stringField(country.iso_3166_1);
|
|
439
|
+
if (!region) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
for (const release of country.release_dates ?? []) {
|
|
443
|
+
const certification = stringField(release.certification);
|
|
444
|
+
if (!certification) {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const certificationData = {
|
|
448
|
+
region,
|
|
449
|
+
certification,
|
|
450
|
+
descriptors: release.descriptors ?? []
|
|
451
|
+
};
|
|
452
|
+
const language = stringField(release.iso_639_1);
|
|
453
|
+
const note = stringField(release.note);
|
|
454
|
+
const releaseDate = stringField(release.release_date);
|
|
455
|
+
if (language) {
|
|
456
|
+
certificationData.language = language;
|
|
457
|
+
}
|
|
458
|
+
if (note) {
|
|
459
|
+
certificationData.note = note;
|
|
460
|
+
}
|
|
461
|
+
if (releaseDate) {
|
|
462
|
+
certificationData.releaseDate = releaseDate;
|
|
463
|
+
}
|
|
464
|
+
if (typeof release.type === "number") {
|
|
465
|
+
certificationData.type = release.type;
|
|
466
|
+
}
|
|
467
|
+
certifications.push(certificationData);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (!certifications.length) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
data.details.push({
|
|
474
|
+
key: "certifications",
|
|
475
|
+
value: certifications,
|
|
476
|
+
source: TMDB_PROVIDER
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
function addStructuredDetail(data, key, value) {
|
|
480
|
+
if (value === undefined || value === null || (Array.isArray(value) && value.length === 0)) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
data.details.push({ key, value, source: TMDB_PROVIDER });
|
|
484
|
+
}
|
|
485
|
+
function stringField(value) {
|
|
486
|
+
const normalized = value?.trim();
|
|
487
|
+
return normalized ? normalized : undefined;
|
|
488
|
+
}
|
|
489
|
+
function mediaUrl(path, baseUrl) {
|
|
490
|
+
const value = stringField(path);
|
|
491
|
+
if (!value) {
|
|
492
|
+
return undefined;
|
|
493
|
+
}
|
|
494
|
+
if (value.startsWith("http://") || value.startsWith("https://")) {
|
|
495
|
+
return value;
|
|
496
|
+
}
|
|
497
|
+
return baseUrl ? `${baseUrl.replace(/\/$/, "")}/${value.replace(/^\//, "")}` : value;
|
|
498
|
+
}
|
|
499
|
+
function validateDate(value, field) {
|
|
500
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(Date.parse(`${value}T00:00:00.000Z`))) {
|
|
501
|
+
throw new Error(`invalid TMDB movie field ${field}: ${value}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function searchQuery(value, field) {
|
|
505
|
+
const normalized = stringField(value);
|
|
506
|
+
if (!normalized) {
|
|
507
|
+
throw new Error(`${field} must not be empty`);
|
|
508
|
+
}
|
|
509
|
+
return normalized;
|
|
510
|
+
}
|
|
511
|
+
function searchParams(provider, input) {
|
|
512
|
+
return {
|
|
513
|
+
language: input.language ?? provider.language,
|
|
514
|
+
page: String(input.page ?? 1),
|
|
515
|
+
include_adult: String(input.includeAdult ?? false)
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
async function sourceRecordsFromSearchResults(category, results) {
|
|
519
|
+
const records = [];
|
|
520
|
+
for (const result of results ?? []) {
|
|
521
|
+
if (!result.id) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
records.push(await createSourceRecord({
|
|
525
|
+
source: { provider: TMDB_PROVIDER, category, externalId: String(result.id) },
|
|
526
|
+
payload: result
|
|
527
|
+
}));
|
|
528
|
+
}
|
|
529
|
+
return records;
|
|
530
|
+
}
|
|
531
|
+
function paginationFromTmdbSearchResponse(payload) {
|
|
532
|
+
return {
|
|
533
|
+
page: payload.page ?? 1,
|
|
534
|
+
totalPages: payload.total_pages ?? 0,
|
|
535
|
+
totalResults: payload.total_results ?? 0
|
|
536
|
+
};
|
|
537
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type SearchResponse, type ZuuidData, type ZuuidSearchResult } from "../../entity.js";
|
|
2
|
+
import { type SourceRecord } from "../../source.js";
|
|
3
|
+
import type { JsonValue } from "../../types.js";
|
|
4
|
+
import type { TmdbProvider } from "./client.js";
|
|
5
|
+
import type { TmdbSearchInput, TmdbTransformOptions } from "./types.js";
|
|
6
|
+
export declare const TMDB_PERSON_CATEGORY = "person";
|
|
7
|
+
export declare const ZUUID_PERSON_CATEGORY = "person";
|
|
8
|
+
export type FetchTmdbPersonInput = {
|
|
9
|
+
id: string | number;
|
|
10
|
+
};
|
|
11
|
+
export type TmdbPersonPayload = {
|
|
12
|
+
adult?: boolean;
|
|
13
|
+
also_known_as?: string[];
|
|
14
|
+
biography?: string;
|
|
15
|
+
birthday?: string | null;
|
|
16
|
+
deathday?: string | null;
|
|
17
|
+
gender?: number;
|
|
18
|
+
homepage?: string | null;
|
|
19
|
+
id?: number | string;
|
|
20
|
+
imdb_id?: string | null;
|
|
21
|
+
known_for_department?: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
place_of_birth?: string | null;
|
|
24
|
+
popularity?: number;
|
|
25
|
+
profile_path?: string | null;
|
|
26
|
+
external_ids?: {
|
|
27
|
+
imdb_id?: string | null;
|
|
28
|
+
wikidata_id?: string | null;
|
|
29
|
+
facebook_id?: string | null;
|
|
30
|
+
instagram_id?: string | null;
|
|
31
|
+
twitter_id?: string | null;
|
|
32
|
+
tiktok_id?: string | null;
|
|
33
|
+
youtube_id?: string | null;
|
|
34
|
+
};
|
|
35
|
+
combined_credits?: {
|
|
36
|
+
cast?: TmdbPersonCredit[];
|
|
37
|
+
crew?: TmdbPersonCredit[];
|
|
38
|
+
};
|
|
39
|
+
images?: {
|
|
40
|
+
profiles?: TmdbPersonImage[];
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export type TmdbPersonCredit = {
|
|
44
|
+
id?: number;
|
|
45
|
+
title?: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
original_title?: string;
|
|
48
|
+
original_name?: string;
|
|
49
|
+
media_type?: string;
|
|
50
|
+
character?: string;
|
|
51
|
+
job?: string;
|
|
52
|
+
department?: string;
|
|
53
|
+
poster_path?: string | null;
|
|
54
|
+
release_date?: string;
|
|
55
|
+
first_air_date?: string;
|
|
56
|
+
vote_average?: number;
|
|
57
|
+
credit_id?: string;
|
|
58
|
+
episode_count?: number;
|
|
59
|
+
order?: number;
|
|
60
|
+
};
|
|
61
|
+
export type TmdbPersonImage = {
|
|
62
|
+
file_path?: string;
|
|
63
|
+
width?: number;
|
|
64
|
+
height?: number;
|
|
65
|
+
aspect_ratio?: number;
|
|
66
|
+
vote_average?: number;
|
|
67
|
+
vote_count?: number;
|
|
68
|
+
};
|
|
69
|
+
export type TmdbPersonSearchResult = {
|
|
70
|
+
adult?: boolean;
|
|
71
|
+
gender?: number;
|
|
72
|
+
id?: number;
|
|
73
|
+
known_for?: JsonValue[];
|
|
74
|
+
known_for_department?: string;
|
|
75
|
+
name?: string;
|
|
76
|
+
original_name?: string;
|
|
77
|
+
popularity?: number;
|
|
78
|
+
profile_path?: string | null;
|
|
79
|
+
};
|
|
80
|
+
export declare function fetchTmdbPersonSourceRecord(provider: TmdbProvider, input: FetchTmdbPersonInput): Promise<SourceRecord | undefined>;
|
|
81
|
+
export declare function searchTmdbPersonSourceRecords(provider: TmdbProvider, input: TmdbSearchInput): Promise<SearchResponse<SourceRecord>>;
|
|
82
|
+
export declare function searchTmdbPeople(provider: TmdbProvider, input: TmdbSearchInput, options?: TmdbTransformOptions): Promise<SearchResponse<ZuuidSearchResult>>;
|
|
83
|
+
export declare function transformTmdbPerson(source: SourceRecord, options?: TmdbTransformOptions): Promise<ZuuidData>;
|
|
84
|
+
//# sourceMappingURL=person.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"person.d.ts","sourceRoot":"","sources":["../../../src/providers/tmdb/person.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE/G,OAAO,EAA4C,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC9F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAsB,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE5F,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAC7C,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE;QACb,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,CAAC;IACF,gBAAgB,CAAC,EAAE;QACjB,IAAI,CAAC,EAAE,gBAAgB,EAAE,CAAC;QAC1B,IAAI,CAAC,EAAE,gBAAgB,EAAE,CAAC;KAC3B,CAAC;IACF,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;KAC9B,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEF,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAsBnC;AAED,wBAAsB,6BAA6B,CACjD,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAMvC;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC,CAkC5C;AAeD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,SAAS,CAAC,CAsDpB"}
|