tmdb-lumosovich 0.8.0 → 0.9.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 +50 -7
- package/dist/guest-session-types.d.ts +26 -0
- package/dist/guest-session-types.js +1 -0
- package/dist/guest-sessions.d.ts +3 -0
- package/dist/guest-sessions.js +37 -0
- package/dist/http.d.ts +1 -1
- package/dist/index.cjs +232 -36
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/list-types.d.ts +74 -0
- package/dist/list-types.js +1 -0
- package/dist/list-validation.d.ts +5 -0
- package/dist/list-validation.js +54 -0
- package/dist/lists.d.ts +3 -0
- package/dist/lists.js +80 -0
- package/dist/watch-provider-types.d.ts +22 -0
- package/dist/watch-provider-types.js +1 -0
- package/dist/watch-providers.d.ts +3 -0
- package/dist/watch-providers.js +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
A small, typed Node.js wrapper for the TMDB API, including IMDb ID lookup through TMDB.
|
|
8
8
|
|
|
9
|
-
**Lumosovich** is being built incrementally toward full TMDB v3 API coverage. It currently supports external-ID lookup, search, discovery, trending, movie and TV series catalogs and metadata, reviews, keywords, networks, collections, companies, configuration, people and credit resources, and TV seasons, episodes, and episode groups.
|
|
9
|
+
**Lumosovich** is being built incrementally toward full TMDB v3 API coverage. It currently supports external-ID lookup, search, discovery, trending, movie and TV series catalogs and metadata, reviews, keywords, networks, collections, companies, configuration, people and credit resources, lists, guest-session ratings, watch-provider catalogs, and TV seasons, episodes, and episode groups.
|
|
10
10
|
|
|
11
11
|
The name comes from **Lumos** — a spell for bringing light — with a little Russian-style `-ovich` treatment.
|
|
12
12
|
|
|
@@ -14,11 +14,11 @@ Light up the movie database.
|
|
|
14
14
|
|
|
15
15
|
## Current status
|
|
16
16
|
|
|
17
|
-
Install [tmdb-lumosovich on npm](https://www.npmjs.com/package/tmdb-lumosovich), with source and issues on [GitHub](https://github.com/namipsg/lumosovich). Version `0.
|
|
17
|
+
Install [tmdb-lumosovich on npm](https://www.npmjs.com/package/tmdb-lumosovich), with source and issues on [GitHub](https://github.com/namipsg/lumosovich). Version `0.9.0` covers **134 of 152 TMDB v3 operations**, including all TV, person, credit, list, guest-session rating, and watch-provider catalog operations, measured against the official specification pinned on September 27, 2026. This is an early `0.x` library; full API coverage is still in progress. See the [coverage baseline](https://github.com/namipsg/lumosovich/blob/main/spec/README.md) for how coverage is checked.
|
|
18
18
|
|
|
19
|
-
The package moved from `lumosovich` to `tmdb-lumosovich` in version `0.7.1`; the public `Lumosovich` class kept its name. Previously published `lumosovich` versions remain installable, but new projects should use `tmdb-lumosovich`. The
|
|
19
|
+
The package moved from `lumosovich` to `tmdb-lumosovich` in version `0.7.1`; the public `Lumosovich` class kept its name. Previously published `lumosovich` versions remain installable, but new projects should use `tmdb-lumosovich`. The live smoke test exercises all 119 public read routes without changing account data. A new, unrated guest session currently receives HTTP 404 from TMDB on its rated-content routes. List mutations, account-state methods, and ratings require a TMDB session and are verified by offline request contracts.
|
|
20
20
|
|
|
21
|
-
The release passes
|
|
21
|
+
The release passes 245 offline tests and TypeScript declaration checks. See the [changelog](https://github.com/namipsg/lumosovich/blob/main/CHANGELOG.md) for earlier releases.
|
|
22
22
|
|
|
23
23
|
## Installation
|
|
24
24
|
|
|
@@ -372,6 +372,47 @@ const changes = await tmdb.people.changes(personId);
|
|
|
372
372
|
|
|
373
373
|
The three credit collections return `cast` and `crew` arrays with TMDB's original fields; `combinedCredits()` includes both movie and TV roles. `taggedImages()` has paginated image records, including episode stills, while `images()` has portrait profiles. TMDB can report `page: 0` for tagged images even when page 1 is requested. `changeList()` and `changes()` accept optional `startDate`, `endDate`, and `page`; dates use `YYYY-MM-DD`. `popular()` and `taggedImages()` accept a positive page. Credit details, popular people, and the three credit collections support a `language` override. `latest()` can be incomplete while a new person record is being edited on TMDB.
|
|
374
374
|
|
|
375
|
+
## Lists, guest sessions, and watch providers
|
|
376
|
+
|
|
377
|
+
Public lists use numeric TMDB list IDs. Reading list details and checking whether a movie is present require no user session:
|
|
378
|
+
|
|
379
|
+
```js
|
|
380
|
+
const list = await tmdb.lists.get(1, { page: 1 });
|
|
381
|
+
const status = await tmdb.lists.itemStatus(1, 550);
|
|
382
|
+
console.log(list.name, status.item_present);
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Creating or changing a list requires an existing TMDB **user** session. Supply `sessionId` explicitly for each write; a guest session cannot manage lists.
|
|
386
|
+
|
|
387
|
+
```js
|
|
388
|
+
const sessionId = process.env.TMDB_SESSION_ID;
|
|
389
|
+
if (!sessionId) throw new Error('Set TMDB_SESSION_ID first');
|
|
390
|
+
const created = await tmdb.lists.create({
|
|
391
|
+
name: 'Movies to watch', description: 'My picks', language: 'en', sessionId,
|
|
392
|
+
});
|
|
393
|
+
await tmdb.lists.addMovie(created.list_id, 550, { sessionId });
|
|
394
|
+
await tmdb.lists.removeMovie(created.list_id, 550, { sessionId });
|
|
395
|
+
await tmdb.lists.clear(created.list_id, { sessionId, confirm: true });
|
|
396
|
+
await tmdb.lists.delete(created.list_id, { sessionId });
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
`clear()` requires an explicit boolean `confirm`; deleting a list and removing an item are separate operations. TMDB can return list IDs as strings in read responses, so inspect the returned `id` rather than assuming its runtime type. List writes are contract-tested offline; the live smoke test does not alter an account.
|
|
400
|
+
|
|
401
|
+
```js
|
|
402
|
+
const guestId = process.env.TMDB_GUEST_SESSION_ID;
|
|
403
|
+
if (guestId) {
|
|
404
|
+
const ratedMovies = await tmdb.guestSessions.ratedMovies(guestId);
|
|
405
|
+
const ratedShows = await tmdb.guestSessions.ratedTv(guestId);
|
|
406
|
+
const ratedEpisodes = await tmdb.guestSessions.ratedTvEpisodes(guestId);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const regions = await tmdb.watchProviders.regions();
|
|
410
|
+
const movieProviders = await tmdb.watchProviders.movies({ watchRegion: 'US' });
|
|
411
|
+
const tvProviders = await tmdb.watchProviders.tv({ watchRegion: 'US' });
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Guest rating lists accept `language`, positive `page`, and `sortBy: 'created_at.asc' | 'created_at.desc'`. A new unrated guest session may receive HTTP 404. Provider catalogs return a `results` array; movie and TV catalogs accept `watchRegion`, while the regions catalog accepts only `language`. Guest-session creation is part of the upcoming authentication group.
|
|
415
|
+
|
|
375
416
|
## Image URLs
|
|
376
417
|
|
|
377
418
|
TMDB detail responses contain image paths. Build a safe public image URL with:
|
|
@@ -459,12 +500,14 @@ TMDB_API_KEY=... npm run test:live
|
|
|
459
500
|
```
|
|
460
501
|
|
|
461
502
|
The credential is read from the environment, never from a tracked file. This checks
|
|
462
|
-
all
|
|
463
|
-
|
|
503
|
+
all 119 public read routes. The test creates a temporary guest session for the
|
|
504
|
+
three rated-content routes and accepts TMDB's HTTP 404 for an unrated session.
|
|
505
|
+
List and account mutations require a user session and are covered by offline
|
|
506
|
+
contracts; the live test does not change account data.
|
|
464
507
|
|
|
465
508
|
## Roadmap
|
|
466
509
|
|
|
467
|
-
|
|
510
|
+
Account resources, authentication, and the final parity audit will complete coverage in future releases. The [coverage ledger](https://github.com/namipsg/lumosovich/blob/main/spec/coverage.json) tracks implemented and remaining operations.
|
|
468
511
|
|
|
469
512
|
The goal is to cover the TMDB API while keeping the library:
|
|
470
513
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { FindEpisodeHit, MovieSearchHit, SearchPage, TvSearchHit } from './types.js';
|
|
2
|
+
export interface GuestSessionRatedOptions {
|
|
3
|
+
language?: string;
|
|
4
|
+
page?: number;
|
|
5
|
+
sortBy?: 'created_at.asc' | 'created_at.desc';
|
|
6
|
+
}
|
|
7
|
+
export interface GuestRatedMovie extends MovieSearchHit {
|
|
8
|
+
title: string;
|
|
9
|
+
rating: number;
|
|
10
|
+
}
|
|
11
|
+
export interface GuestRatedTv extends TvSearchHit {
|
|
12
|
+
name: string;
|
|
13
|
+
rating: number;
|
|
14
|
+
}
|
|
15
|
+
export interface GuestRatedEpisode extends FindEpisodeHit {
|
|
16
|
+
name: string;
|
|
17
|
+
show_id: number;
|
|
18
|
+
season_number: number;
|
|
19
|
+
episode_number: number;
|
|
20
|
+
rating: number;
|
|
21
|
+
}
|
|
22
|
+
export interface GuestSessionMethods {
|
|
23
|
+
ratedMovies(guestSessionId: string, options?: GuestSessionRatedOptions): Promise<SearchPage<GuestRatedMovie>>;
|
|
24
|
+
ratedTv(guestSessionId: string, options?: GuestSessionRatedOptions): Promise<SearchPage<GuestRatedTv>>;
|
|
25
|
+
ratedTvEpisodes(guestSessionId: string, options?: GuestSessionRatedOptions): Promise<SearchPage<GuestRatedEpisode>>;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { pathId } from './ids.js';
|
|
2
|
+
import { invalidResponse, validateSearchPage } from './validation.js';
|
|
3
|
+
function parameters(options) {
|
|
4
|
+
if (options.page !== undefined && (!Number.isSafeInteger(options.page) || options.page < 1)) {
|
|
5
|
+
throw new TypeError('page must be a positive integer');
|
|
6
|
+
}
|
|
7
|
+
if (options.sortBy !== undefined && !['created_at.asc', 'created_at.desc'].includes(options.sortBy)) {
|
|
8
|
+
throw new TypeError('sortBy must be created_at.asc or created_at.desc');
|
|
9
|
+
}
|
|
10
|
+
return { language: options.language, page: options.page, sort_by: options.sortBy };
|
|
11
|
+
}
|
|
12
|
+
function validateRatedPage(value, nameField, episodes = false) {
|
|
13
|
+
const page = validateSearchPage(value);
|
|
14
|
+
if (!page.results.every((item) => typeof item[nameField] === 'string' &&
|
|
15
|
+
typeof item.rating === 'number' && Number.isFinite(item.rating) &&
|
|
16
|
+
item.rating >= 0.5 && item.rating <= 10 && Number.isInteger(item.rating * 2) &&
|
|
17
|
+
(!episodes || (Number.isSafeInteger(item.show_id) && Number(item.show_id) > 0 &&
|
|
18
|
+
Number.isSafeInteger(item.season_number) && Number(item.season_number) >= 0 &&
|
|
19
|
+
Number.isSafeInteger(item.episode_number) && Number(item.episode_number) >= 0)))) {
|
|
20
|
+
invalidResponse();
|
|
21
|
+
}
|
|
22
|
+
return page;
|
|
23
|
+
}
|
|
24
|
+
export function createGuestSessionMethods(http) {
|
|
25
|
+
const path = (id) => `guest_session/${encodeURIComponent(pathId(id, 'guestSessionId'))}/rated`;
|
|
26
|
+
return {
|
|
27
|
+
async ratedMovies(guestSessionId, options = {}) {
|
|
28
|
+
return validateRatedPage(await http.get(`${path(guestSessionId)}/movies`, parameters(options)), 'title');
|
|
29
|
+
},
|
|
30
|
+
async ratedTv(guestSessionId, options = {}) {
|
|
31
|
+
return validateRatedPage(await http.get(`${path(guestSessionId)}/tv`, parameters(options)), 'name');
|
|
32
|
+
},
|
|
33
|
+
async ratedTvEpisodes(guestSessionId, options = {}) {
|
|
34
|
+
return validateRatedPage(await http.get(`${path(guestSessionId)}/tv/episodes`, parameters(options)), 'name', true);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
package/dist/http.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export declare class TmdbHttpClient {
|
|
|
13
13
|
private readonly fetchRequest;
|
|
14
14
|
constructor(options: LumosovichOptions);
|
|
15
15
|
get(path: string, parameters?: QueryParameters, options?: GetOptions): Promise<unknown>;
|
|
16
|
-
post(path: string, parameters: QueryParameters, body
|
|
16
|
+
post(path: string, parameters: QueryParameters, body?: Record<string, unknown>): Promise<unknown>;
|
|
17
17
|
delete(path: string, parameters: QueryParameters): Promise<unknown>;
|
|
18
18
|
private request;
|
|
19
19
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -423,11 +423,11 @@ function catalogOptions(options) {
|
|
|
423
423
|
}
|
|
424
424
|
return { ...pagedLanguage(options), region: options.region?.trim() };
|
|
425
425
|
}
|
|
426
|
-
function sessionParameters(
|
|
427
|
-
if (!
|
|
426
|
+
function sessionParameters(session2) {
|
|
427
|
+
if (!session2 || typeof session2 !== "object") {
|
|
428
428
|
throw new TypeError("Supply a sessionId or guestSessionId");
|
|
429
429
|
}
|
|
430
|
-
const { sessionId, guestSessionId } =
|
|
430
|
+
const { sessionId, guestSessionId } = session2;
|
|
431
431
|
if (Boolean(sessionId) === Boolean(guestSessionId) || sessionId !== void 0 && (typeof sessionId !== "string" || !sessionId.trim()) || guestSessionId !== void 0 && (typeof guestSessionId !== "string" || !guestSessionId.trim())) {
|
|
432
432
|
throw new TypeError("Supply exactly one nonempty sessionId or guestSessionId");
|
|
433
433
|
}
|
|
@@ -465,11 +465,11 @@ function createMovieCatalogMethods(http) {
|
|
|
465
465
|
async upcoming(options = {}) {
|
|
466
466
|
return validateCatalogPage(await http.get("movie/upcoming", catalogOptions(options)), true);
|
|
467
467
|
},
|
|
468
|
-
async accountStates(id,
|
|
468
|
+
async accountStates(id, session2) {
|
|
469
469
|
const validId = positiveId(id);
|
|
470
470
|
return validateAccountStates(await http.get(
|
|
471
471
|
`movie/${validId}/account_states`,
|
|
472
|
-
sessionParameters(
|
|
472
|
+
sessionParameters(session2),
|
|
473
473
|
{ includeLanguage: false }
|
|
474
474
|
), validId);
|
|
475
475
|
},
|
|
@@ -495,13 +495,13 @@ function createMovieCatalogMethods(http) {
|
|
|
495
495
|
const validId = positiveId(id);
|
|
496
496
|
return validateSearchPage(await http.get(`movie/${validId}/similar`, pagedLanguage(options)));
|
|
497
497
|
},
|
|
498
|
-
async rate(id, value,
|
|
498
|
+
async rate(id, value, session2) {
|
|
499
499
|
const validId = positiveId(id);
|
|
500
|
-
return validateRatingResponse(await http.post(`movie/${validId}/rating`, sessionParameters(
|
|
500
|
+
return validateRatingResponse(await http.post(`movie/${validId}/rating`, sessionParameters(session2), { value: ratingValue(value) }));
|
|
501
501
|
},
|
|
502
|
-
async deleteRating(id,
|
|
502
|
+
async deleteRating(id, session2) {
|
|
503
503
|
const validId = positiveId(id);
|
|
504
|
-
return validateRatingResponse(await http.delete(`movie/${validId}/rating`, sessionParameters(
|
|
504
|
+
return validateRatingResponse(await http.delete(`movie/${validId}/rating`, sessionParameters(session2)));
|
|
505
505
|
}
|
|
506
506
|
};
|
|
507
507
|
}
|
|
@@ -531,11 +531,11 @@ function createTvCatalogMethods(http) {
|
|
|
531
531
|
async topRated(options = {}) {
|
|
532
532
|
return validateSearchPage(await http.get("tv/top_rated", pagedLanguage(options)));
|
|
533
533
|
},
|
|
534
|
-
async accountStates(id,
|
|
534
|
+
async accountStates(id, session2) {
|
|
535
535
|
const validId = positiveId(id);
|
|
536
536
|
return validateAccountStates(await http.get(
|
|
537
537
|
`tv/${validId}/account_states`,
|
|
538
|
-
sessionParameters(
|
|
538
|
+
sessionParameters(session2),
|
|
539
539
|
{ includeLanguage: false }
|
|
540
540
|
), validId);
|
|
541
541
|
},
|
|
@@ -558,15 +558,15 @@ function createTvCatalogMethods(http) {
|
|
|
558
558
|
const validId = positiveId(id);
|
|
559
559
|
return validateSearchPage(await http.get(`tv/${validId}/similar`, pagedLanguage(options)));
|
|
560
560
|
},
|
|
561
|
-
async rate(id, value,
|
|
561
|
+
async rate(id, value, session2) {
|
|
562
562
|
const validId = positiveId(id);
|
|
563
|
-
return validateRatingResponse(await http.post(`tv/${validId}/rating`, sessionParameters(
|
|
563
|
+
return validateRatingResponse(await http.post(`tv/${validId}/rating`, sessionParameters(session2), {
|
|
564
564
|
value: ratingValue(value)
|
|
565
565
|
}));
|
|
566
566
|
},
|
|
567
|
-
async deleteRating(id,
|
|
567
|
+
async deleteRating(id, session2) {
|
|
568
568
|
const validId = positiveId(id);
|
|
569
|
-
return validateRatingResponse(await http.delete(`tv/${validId}/rating`, sessionParameters(
|
|
569
|
+
return validateRatingResponse(await http.delete(`tv/${validId}/rating`, sessionParameters(session2)));
|
|
570
570
|
}
|
|
571
571
|
};
|
|
572
572
|
}
|
|
@@ -763,11 +763,11 @@ function episodePath(seriesId, seasonNumber, episodeNumber) {
|
|
|
763
763
|
// src/tv-season-resources.ts
|
|
764
764
|
function createTvSeasonResourceMethods(http) {
|
|
765
765
|
return {
|
|
766
|
-
async accountStates(seriesId, seasonNumber,
|
|
766
|
+
async accountStates(seriesId, seasonNumber, session2) {
|
|
767
767
|
const path = seasonPath(seriesId, seasonNumber);
|
|
768
768
|
return validateSeasonAccountStates(await http.get(
|
|
769
769
|
`${path}/account_states`,
|
|
770
|
-
sessionParameters(
|
|
770
|
+
sessionParameters(session2),
|
|
771
771
|
{ includeLanguage: false }
|
|
772
772
|
));
|
|
773
773
|
},
|
|
@@ -839,11 +839,11 @@ function createTvSeasonResourceMethods(http) {
|
|
|
839
839
|
// src/tv-episode-resources.ts
|
|
840
840
|
function createTvEpisodeResourceMethods(http) {
|
|
841
841
|
return {
|
|
842
|
-
async accountStates(seriesId, seasonNumber, episodeNumber,
|
|
842
|
+
async accountStates(seriesId, seasonNumber, episodeNumber, session2) {
|
|
843
843
|
const path = episodePath(seriesId, seasonNumber, episodeNumber);
|
|
844
844
|
return validateAccountStateShape(await http.get(
|
|
845
845
|
`${path}/account_states`,
|
|
846
|
-
sessionParameters(
|
|
846
|
+
sessionParameters(session2),
|
|
847
847
|
{ includeLanguage: false }
|
|
848
848
|
));
|
|
849
849
|
},
|
|
@@ -900,17 +900,17 @@ function createTvEpisodeResourceMethods(http) {
|
|
|
900
900
|
videoParameters(options)
|
|
901
901
|
), { results: metadataChecks.videos });
|
|
902
902
|
},
|
|
903
|
-
async rate(seriesId, seasonNumber, episodeNumber, value,
|
|
903
|
+
async rate(seriesId, seasonNumber, episodeNumber, value, session2) {
|
|
904
904
|
const path = episodePath(seriesId, seasonNumber, episodeNumber);
|
|
905
905
|
return validateRatingResponse(await http.post(
|
|
906
906
|
`${path}/rating`,
|
|
907
|
-
sessionParameters(
|
|
907
|
+
sessionParameters(session2),
|
|
908
908
|
{ value: ratingValue(value) }
|
|
909
909
|
));
|
|
910
910
|
},
|
|
911
|
-
async deleteRating(seriesId, seasonNumber, episodeNumber,
|
|
911
|
+
async deleteRating(seriesId, seasonNumber, episodeNumber, session2) {
|
|
912
912
|
const path = episodePath(seriesId, seasonNumber, episodeNumber);
|
|
913
|
-
return validateRatingResponse(await http.delete(`${path}/rating`, sessionParameters(
|
|
913
|
+
return validateRatingResponse(await http.delete(`${path}/rating`, sessionParameters(session2)));
|
|
914
914
|
}
|
|
915
915
|
};
|
|
916
916
|
}
|
|
@@ -1191,25 +1191,25 @@ var TmdbHttpClient = class {
|
|
|
1191
1191
|
throw new TypeError("fetch must be a function");
|
|
1192
1192
|
}
|
|
1193
1193
|
}
|
|
1194
|
-
async get(path,
|
|
1195
|
-
return this.request("GET", path,
|
|
1194
|
+
async get(path, parameters4 = {}, options = {}) {
|
|
1195
|
+
return this.request("GET", path, parameters4, options);
|
|
1196
1196
|
}
|
|
1197
|
-
async post(path,
|
|
1198
|
-
return this.request("POST", path,
|
|
1197
|
+
async post(path, parameters4, body) {
|
|
1198
|
+
return this.request("POST", path, parameters4, { includeLanguage: false }, body);
|
|
1199
1199
|
}
|
|
1200
|
-
async delete(path,
|
|
1201
|
-
return this.request("DELETE", path,
|
|
1200
|
+
async delete(path, parameters4) {
|
|
1201
|
+
return this.request("DELETE", path, parameters4, { includeLanguage: false });
|
|
1202
1202
|
}
|
|
1203
|
-
async request(method, path,
|
|
1203
|
+
async request(method, path, parameters4, options, body) {
|
|
1204
1204
|
const url = new URL(path, API_BASE_URL);
|
|
1205
1205
|
if (options.includeLanguage !== false) {
|
|
1206
|
-
const language =
|
|
1206
|
+
const language = parameters4.language ?? this.language;
|
|
1207
1207
|
if (typeof language !== "string" || !language.trim()) {
|
|
1208
1208
|
throw new TypeError("language must not be empty");
|
|
1209
1209
|
}
|
|
1210
1210
|
url.searchParams.set("language", language);
|
|
1211
1211
|
}
|
|
1212
|
-
for (const [key, value] of Object.entries(
|
|
1212
|
+
for (const [key, value] of Object.entries(parameters4)) {
|
|
1213
1213
|
if (key !== "language" && value !== void 0) {
|
|
1214
1214
|
url.searchParams.set(key, String(value));
|
|
1215
1215
|
}
|
|
@@ -1541,12 +1541,12 @@ function region(value) {
|
|
|
1541
1541
|
return value;
|
|
1542
1542
|
}
|
|
1543
1543
|
function commonParameters(query, options) {
|
|
1544
|
-
const
|
|
1544
|
+
const parameters4 = pageParameters(query, options);
|
|
1545
1545
|
if (options.includeAdult !== void 0 && typeof options.includeAdult !== "boolean") {
|
|
1546
1546
|
throw new TypeError("includeAdult must be a boolean");
|
|
1547
1547
|
}
|
|
1548
1548
|
return {
|
|
1549
|
-
...
|
|
1549
|
+
...parameters4,
|
|
1550
1550
|
language: options.language,
|
|
1551
1551
|
include_adult: options.includeAdult
|
|
1552
1552
|
};
|
|
@@ -1577,9 +1577,9 @@ function createSearchMethods(http) {
|
|
|
1577
1577
|
return validateSearchPage(payload);
|
|
1578
1578
|
},
|
|
1579
1579
|
async movies(query, options = {}) {
|
|
1580
|
-
const
|
|
1580
|
+
const parameters4 = commonParameters(query, options);
|
|
1581
1581
|
const payload = await http.get("search/movie", {
|
|
1582
|
-
...
|
|
1582
|
+
...parameters4,
|
|
1583
1583
|
year: year(options.year, "year"),
|
|
1584
1584
|
primary_release_year: year(
|
|
1585
1585
|
options.primaryReleaseYear,
|
|
@@ -1611,6 +1611,165 @@ function createSearchMethods(http) {
|
|
|
1611
1611
|
};
|
|
1612
1612
|
}
|
|
1613
1613
|
|
|
1614
|
+
// src/list-validation.ts
|
|
1615
|
+
function positive2(value) {
|
|
1616
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
1617
|
+
}
|
|
1618
|
+
function count3(value) {
|
|
1619
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1620
|
+
}
|
|
1621
|
+
function finite(value) {
|
|
1622
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
1623
|
+
}
|
|
1624
|
+
function nullableString(value) {
|
|
1625
|
+
return value === null || typeof value === "string";
|
|
1626
|
+
}
|
|
1627
|
+
function movie2(value) {
|
|
1628
|
+
return isRecord(value) && positive2(value.id) && ["title", "original_title", "original_language", "overview", "release_date"].every((key) => typeof value[key] === "string") && typeof value.adult === "boolean" && typeof value.video === "boolean" && Array.isArray(value.genre_ids) && value.genre_ids.every(positive2) && finite(value.popularity) && nullableString(value.poster_path) && nullableString(value.backdrop_path) && finite(value.vote_average) && count3(value.vote_count) && (value.media_type === void 0 || value.media_type === "movie");
|
|
1629
|
+
}
|
|
1630
|
+
function validateListDetails(value, expectedId) {
|
|
1631
|
+
if (!isRecord(value) || !(value.id === expectedId || value.id === String(expectedId)) || !["created_by", "description", "iso_639_1", "name"].every((key) => typeof value[key] === "string") || !count3(value.favorite_count) || !count3(value.item_count) || !nullableString(value.poster_path) || !Array.isArray(value.items) || !value.items.every(movie2)) {
|
|
1632
|
+
invalidResponse();
|
|
1633
|
+
}
|
|
1634
|
+
return value;
|
|
1635
|
+
}
|
|
1636
|
+
function validateListItemStatus(value, expectedId) {
|
|
1637
|
+
if (!isRecord(value) || !(value.id === expectedId || value.id === String(expectedId)) || typeof value.item_present !== "boolean") {
|
|
1638
|
+
invalidResponse();
|
|
1639
|
+
}
|
|
1640
|
+
return value;
|
|
1641
|
+
}
|
|
1642
|
+
function validateListMutation(value) {
|
|
1643
|
+
if (!isRecord(value) || !positive2(value.status_code) || typeof value.status_message !== "string" || value.success !== void 0 && typeof value.success !== "boolean") {
|
|
1644
|
+
invalidResponse();
|
|
1645
|
+
}
|
|
1646
|
+
return value;
|
|
1647
|
+
}
|
|
1648
|
+
function validateCreateList(value) {
|
|
1649
|
+
const response = validateListMutation(value);
|
|
1650
|
+
if (typeof response.success !== "boolean" || !positive2(response.list_id)) invalidResponse();
|
|
1651
|
+
return response;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// src/lists.ts
|
|
1655
|
+
function nonempty(value, label) {
|
|
1656
|
+
if (typeof value !== "string" || !value.trim()) throw new TypeError(`${label} must not be empty`);
|
|
1657
|
+
return value;
|
|
1658
|
+
}
|
|
1659
|
+
function optionalLanguage(value) {
|
|
1660
|
+
return value === void 0 ? void 0 : nonempty(value, "language");
|
|
1661
|
+
}
|
|
1662
|
+
function session(options) {
|
|
1663
|
+
if (!isRecord(options)) throw new TypeError("sessionId is required");
|
|
1664
|
+
return nonempty(options.sessionId, "sessionId");
|
|
1665
|
+
}
|
|
1666
|
+
function createListMethods(http) {
|
|
1667
|
+
return {
|
|
1668
|
+
async create(input) {
|
|
1669
|
+
const sessionId = session(input);
|
|
1670
|
+
const name = nonempty(input.name, "name");
|
|
1671
|
+
if (input.description !== void 0 && typeof input.description !== "string") {
|
|
1672
|
+
throw new TypeError("description must be a string");
|
|
1673
|
+
}
|
|
1674
|
+
const language = optionalLanguage(input.language);
|
|
1675
|
+
return validateCreateList(await http.post("list", { session_id: sessionId }, {
|
|
1676
|
+
name,
|
|
1677
|
+
...input.description === void 0 ? {} : { description: input.description },
|
|
1678
|
+
...language === void 0 ? {} : { language }
|
|
1679
|
+
}));
|
|
1680
|
+
},
|
|
1681
|
+
async get(listId, options = {}) {
|
|
1682
|
+
positiveId(listId);
|
|
1683
|
+
if (!isRecord(options)) throw new TypeError("options must be an object");
|
|
1684
|
+
if (options.page !== void 0 && (typeof options.page !== "number" || !Number.isSafeInteger(options.page) || options.page < 1)) {
|
|
1685
|
+
throw new TypeError("page must be a positive integer");
|
|
1686
|
+
}
|
|
1687
|
+
return validateListDetails(await http.get(`list/${listId}`, {
|
|
1688
|
+
language: optionalLanguage(options.language),
|
|
1689
|
+
page: options.page
|
|
1690
|
+
}), listId);
|
|
1691
|
+
},
|
|
1692
|
+
async itemStatus(listId, movieId, options = {}) {
|
|
1693
|
+
positiveId(listId);
|
|
1694
|
+
positiveId(movieId);
|
|
1695
|
+
if (!isRecord(options)) throw new TypeError("options must be an object");
|
|
1696
|
+
return validateListItemStatus(await http.get(`list/${listId}/item_status`, {
|
|
1697
|
+
movie_id: movieId,
|
|
1698
|
+
language: optionalLanguage(options.language)
|
|
1699
|
+
}), listId);
|
|
1700
|
+
},
|
|
1701
|
+
async addMovie(listId, movieId, options) {
|
|
1702
|
+
positiveId(listId);
|
|
1703
|
+
positiveId(movieId);
|
|
1704
|
+
return validateListMutation(await http.post(`list/${listId}/add_item`, {
|
|
1705
|
+
session_id: session(options)
|
|
1706
|
+
}, { media_id: movieId }));
|
|
1707
|
+
},
|
|
1708
|
+
async removeMovie(listId, movieId, options) {
|
|
1709
|
+
positiveId(listId);
|
|
1710
|
+
positiveId(movieId);
|
|
1711
|
+
return validateListMutation(await http.post(`list/${listId}/remove_item`, {
|
|
1712
|
+
session_id: session(options)
|
|
1713
|
+
}, { media_id: movieId }));
|
|
1714
|
+
},
|
|
1715
|
+
async clear(listId, options) {
|
|
1716
|
+
positiveId(listId);
|
|
1717
|
+
const sessionId = session(options);
|
|
1718
|
+
if (typeof options.confirm !== "boolean") throw new TypeError("confirm must be a boolean");
|
|
1719
|
+
return validateListMutation(await http.post(`list/${listId}/clear`, {
|
|
1720
|
+
session_id: sessionId,
|
|
1721
|
+
confirm: options.confirm
|
|
1722
|
+
}));
|
|
1723
|
+
},
|
|
1724
|
+
async delete(listId, options) {
|
|
1725
|
+
positiveId(listId);
|
|
1726
|
+
return validateListMutation(await http.delete(`list/${listId}`, { session_id: session(options) }));
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/guest-sessions.ts
|
|
1732
|
+
function parameters2(options) {
|
|
1733
|
+
if (options.page !== void 0 && (!Number.isSafeInteger(options.page) || options.page < 1)) {
|
|
1734
|
+
throw new TypeError("page must be a positive integer");
|
|
1735
|
+
}
|
|
1736
|
+
if (options.sortBy !== void 0 && !["created_at.asc", "created_at.desc"].includes(options.sortBy)) {
|
|
1737
|
+
throw new TypeError("sortBy must be created_at.asc or created_at.desc");
|
|
1738
|
+
}
|
|
1739
|
+
return { language: options.language, page: options.page, sort_by: options.sortBy };
|
|
1740
|
+
}
|
|
1741
|
+
function validateRatedPage(value, nameField, episodes = false) {
|
|
1742
|
+
const page3 = validateSearchPage(value);
|
|
1743
|
+
if (!page3.results.every((item) => typeof item[nameField] === "string" && typeof item.rating === "number" && Number.isFinite(item.rating) && item.rating >= 0.5 && item.rating <= 10 && Number.isInteger(item.rating * 2) && (!episodes || Number.isSafeInteger(item.show_id) && Number(item.show_id) > 0 && Number.isSafeInteger(item.season_number) && Number(item.season_number) >= 0 && Number.isSafeInteger(item.episode_number) && Number(item.episode_number) >= 0))) {
|
|
1744
|
+
invalidResponse();
|
|
1745
|
+
}
|
|
1746
|
+
return page3;
|
|
1747
|
+
}
|
|
1748
|
+
function createGuestSessionMethods(http) {
|
|
1749
|
+
const path = (id) => `guest_session/${encodeURIComponent(pathId(id, "guestSessionId"))}/rated`;
|
|
1750
|
+
return {
|
|
1751
|
+
async ratedMovies(guestSessionId, options = {}) {
|
|
1752
|
+
return validateRatedPage(
|
|
1753
|
+
await http.get(`${path(guestSessionId)}/movies`, parameters2(options)),
|
|
1754
|
+
"title"
|
|
1755
|
+
);
|
|
1756
|
+
},
|
|
1757
|
+
async ratedTv(guestSessionId, options = {}) {
|
|
1758
|
+
return validateRatedPage(
|
|
1759
|
+
await http.get(`${path(guestSessionId)}/tv`, parameters2(options)),
|
|
1760
|
+
"name"
|
|
1761
|
+
);
|
|
1762
|
+
},
|
|
1763
|
+
async ratedTvEpisodes(guestSessionId, options = {}) {
|
|
1764
|
+
return validateRatedPage(
|
|
1765
|
+
await http.get(`${path(guestSessionId)}/tv/episodes`, parameters2(options)),
|
|
1766
|
+
"name",
|
|
1767
|
+
true
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1614
1773
|
// src/reference-validation.ts
|
|
1615
1774
|
function strings(value) {
|
|
1616
1775
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
@@ -1651,6 +1810,37 @@ function validateGenres(value) {
|
|
|
1651
1810
|
return value;
|
|
1652
1811
|
}
|
|
1653
1812
|
|
|
1813
|
+
// src/watch-providers.ts
|
|
1814
|
+
function parameters3(options) {
|
|
1815
|
+
if (options.watchRegion !== void 0 && (typeof options.watchRegion !== "string" || !options.watchRegion.trim())) {
|
|
1816
|
+
throw new TypeError("watchRegion must not be empty");
|
|
1817
|
+
}
|
|
1818
|
+
return { language: options.language, watch_region: options.watchRegion };
|
|
1819
|
+
}
|
|
1820
|
+
function count4(value) {
|
|
1821
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1822
|
+
}
|
|
1823
|
+
function validateProviders(value) {
|
|
1824
|
+
if (!isRecord(value) || !Array.isArray(value.results) || !value.results.every((item) => isRecord(item) && count4(item.provider_id) && item.provider_id > 0 && typeof item.provider_name === "string" && typeof item.logo_path === "string" && count4(item.display_priority) && isRecord(item.display_priorities) && Object.values(item.display_priorities).every(count4))) invalidResponse();
|
|
1825
|
+
return value;
|
|
1826
|
+
}
|
|
1827
|
+
function createWatchProviderMethods(http) {
|
|
1828
|
+
return {
|
|
1829
|
+
async regions(options = {}) {
|
|
1830
|
+
const value = await http.get("watch/providers/regions", { language: options.language });
|
|
1831
|
+
if (!isRecord(value)) invalidResponse();
|
|
1832
|
+
validateCountries(value.results);
|
|
1833
|
+
return value;
|
|
1834
|
+
},
|
|
1835
|
+
async movies(options = {}) {
|
|
1836
|
+
return validateProviders(await http.get("watch/providers/movie", parameters3(options)));
|
|
1837
|
+
},
|
|
1838
|
+
async tv(options = {}) {
|
|
1839
|
+
return validateProviders(await http.get("watch/providers/tv", parameters3(options)));
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1654
1844
|
// src/reference.ts
|
|
1655
1845
|
function createConfigurationMethods(http) {
|
|
1656
1846
|
return {
|
|
@@ -1710,6 +1900,9 @@ var Lumosovich = class {
|
|
|
1710
1900
|
tv;
|
|
1711
1901
|
people;
|
|
1712
1902
|
credits;
|
|
1903
|
+
lists;
|
|
1904
|
+
guestSessions;
|
|
1905
|
+
watchProviders;
|
|
1713
1906
|
images;
|
|
1714
1907
|
configuration;
|
|
1715
1908
|
certifications;
|
|
@@ -1752,6 +1945,9 @@ var Lumosovich = class {
|
|
|
1752
1945
|
this.tv = createTvMethods(http);
|
|
1753
1946
|
this.people = createPersonMethods(http);
|
|
1754
1947
|
this.credits = createCreditMethods(http);
|
|
1948
|
+
this.lists = createListMethods(http);
|
|
1949
|
+
this.guestSessions = createGuestSessionMethods(http);
|
|
1950
|
+
this.watchProviders = createWatchProviderMethods(http);
|
|
1755
1951
|
this.images = createImageMethods();
|
|
1756
1952
|
this.configuration = createConfigurationMethods(http);
|
|
1757
1953
|
this.certifications = createCertificationMethods(http);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { CreditMethods } from './person-types.js';
|
|
2
|
+
import type { ListMethods } from './list-types.js';
|
|
3
|
+
import type { GuestSessionMethods } from './guest-session-types.js';
|
|
4
|
+
import type { WatchProviderMethods } from './watch-provider-types.js';
|
|
2
5
|
import type { CertificationMethods, ConfigurationMethods, GenreMethods } from './reference-types.js';
|
|
3
6
|
import type { ExternalIdSource, FindOptions, FindResponse, LumosovichOptions, SearchMethods } from './types.js';
|
|
4
7
|
import type { MovieMethods, PersonMethods, TvMethods } from './detail-types.js';
|
|
@@ -7,6 +10,9 @@ import type { CollectionMethods, CompanyMethods } from './metadata-types.js';
|
|
|
7
10
|
import type { DiscoverMethods, KeywordMethods, NetworkMethods, ReviewMethods, TrendingMethods } from './catalog-types.js';
|
|
8
11
|
export { TmdbError } from './errors.js';
|
|
9
12
|
export type { TmdbErrorCode } from './errors.js';
|
|
13
|
+
export type { ClearListOptions, CreateListInput, CreateListResponse, ListDetails, ListDetailsOptions, ListItemStatus, ListMethods, ListMovie, ListMutationResponse, ListSessionOptions, } from './list-types.js';
|
|
14
|
+
export type { GuestRatedEpisode, GuestRatedMovie, GuestRatedTv, GuestSessionMethods, GuestSessionRatedOptions, } from './guest-session-types.js';
|
|
15
|
+
export type { WatchProviderEntry, WatchProviderListResponse, WatchProviderMethods, WatchProviderOptions, WatchProviderRegionsResponse, } from './watch-provider-types.js';
|
|
10
16
|
export type { CreditDetails, CreditMethods, PersonChangeListOptions, PersonChangeListPage, PersonChangesOptions, PersonChangesResponse, PersonCreditsResponse, PersonExternalIdsResponse, PersonImagesResponse, PersonPopularHit, PersonPopularPage, PersonResourceMethods, PersonTaggedImage, PersonTaggedImagesPage, PersonTranslationsResponse, } from './person-types.js';
|
|
11
17
|
export type { ImageMethods, ImageSize } from './images.js';
|
|
12
18
|
export type { DiscoverMethods, DiscoverCommonOptions, KeywordDetails, KeywordMethods, KeywordMoviesPage, MovieAccountStates, MovieCatalogMethods, MovieCatalogOptions, MovieCatalogPage, MovieChangeListOptions, MovieChangeListPage, MovieDiscoverOptions, MovieListHit, MovieListPage, NetworkAlternativeNamesResponse, NetworkDetails, NetworkImagesResponse, NetworkMethods, RatingResponse, RatingSession, ReviewDetails, ReviewMethods, ReviewPage, ReviewSummary, TrendingMethods, TrendingWindow, TvDiscoverOptions, } from './catalog-types.js';
|
|
@@ -33,6 +39,9 @@ export declare class Lumosovich {
|
|
|
33
39
|
readonly tv: TvMethods;
|
|
34
40
|
readonly people: PersonMethods;
|
|
35
41
|
readonly credits: CreditMethods;
|
|
42
|
+
readonly lists: ListMethods;
|
|
43
|
+
readonly guestSessions: GuestSessionMethods;
|
|
44
|
+
readonly watchProviders: WatchProviderMethods;
|
|
36
45
|
readonly images: ImageMethods;
|
|
37
46
|
readonly configuration: ConfigurationMethods;
|
|
38
47
|
readonly certifications: CertificationMethods;
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,9 @@ import { createDiscoverMethods } from './discover.js';
|
|
|
6
6
|
import { createKeywordMethods, createNetworkMethods, createReviewMethods, createTrendingMethods, } from './explore.js';
|
|
7
7
|
import { createSearchMethods } from './search.js';
|
|
8
8
|
import { createCreditMethods } from './person-resources.js';
|
|
9
|
+
import { createListMethods } from './lists.js';
|
|
10
|
+
import { createGuestSessionMethods } from './guest-sessions.js';
|
|
11
|
+
import { createWatchProviderMethods } from './watch-providers.js';
|
|
9
12
|
import { createCertificationMethods, createConfigurationMethods, createGenreMethods } from './reference.js';
|
|
10
13
|
import { validateFindResponse } from './validation.js';
|
|
11
14
|
export { TmdbError } from './errors.js';
|
|
@@ -24,6 +27,9 @@ export class Lumosovich {
|
|
|
24
27
|
tv;
|
|
25
28
|
people;
|
|
26
29
|
credits;
|
|
30
|
+
lists;
|
|
31
|
+
guestSessions;
|
|
32
|
+
watchProviders;
|
|
27
33
|
images;
|
|
28
34
|
configuration;
|
|
29
35
|
certifications;
|
|
@@ -58,6 +64,9 @@ export class Lumosovich {
|
|
|
58
64
|
this.tv = createTvMethods(http);
|
|
59
65
|
this.people = createPersonMethods(http);
|
|
60
66
|
this.credits = createCreditMethods(http);
|
|
67
|
+
this.lists = createListMethods(http);
|
|
68
|
+
this.guestSessions = createGuestSessionMethods(http);
|
|
69
|
+
this.watchProviders = createWatchProviderMethods(http);
|
|
61
70
|
this.images = createImageMethods();
|
|
62
71
|
this.configuration = createConfigurationMethods(http);
|
|
63
72
|
this.certifications = createCertificationMethods(http);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { LanguageOptions } from './reference-types.js';
|
|
2
|
+
import type { PageOptions, TmdbId } from './types.js';
|
|
3
|
+
/** Lists require a user session; guest sessions cannot manage lists. */
|
|
4
|
+
export interface ListSessionOptions {
|
|
5
|
+
sessionId: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CreateListInput extends ListSessionOptions {
|
|
8
|
+
name: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
/** Language stored on the list, separate from the client's response language. */
|
|
11
|
+
language?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ListDetailsOptions extends PageOptions, LanguageOptions {
|
|
14
|
+
}
|
|
15
|
+
export interface ClearListOptions extends ListSessionOptions {
|
|
16
|
+
/** TMDB requires an explicit confirmation query parameter. */
|
|
17
|
+
confirm: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface ListMovie {
|
|
20
|
+
id: TmdbId;
|
|
21
|
+
title: string;
|
|
22
|
+
original_title: string;
|
|
23
|
+
original_language: string;
|
|
24
|
+
overview: string;
|
|
25
|
+
adult: boolean;
|
|
26
|
+
video: boolean;
|
|
27
|
+
genre_ids: number[];
|
|
28
|
+
popularity: number;
|
|
29
|
+
poster_path: string | null;
|
|
30
|
+
backdrop_path: string | null;
|
|
31
|
+
release_date: string;
|
|
32
|
+
vote_average: number;
|
|
33
|
+
vote_count: number;
|
|
34
|
+
media_type?: 'movie';
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
export interface ListDetails {
|
|
38
|
+
/** TMDB's v3 list details example returns the identifier as a string. */
|
|
39
|
+
id: string | TmdbId;
|
|
40
|
+
created_by: string;
|
|
41
|
+
description: string;
|
|
42
|
+
favorite_count: number;
|
|
43
|
+
items: ListMovie[];
|
|
44
|
+
item_count: number;
|
|
45
|
+
iso_639_1: string;
|
|
46
|
+
name: string;
|
|
47
|
+
poster_path: string | null;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
export interface ListItemStatus {
|
|
51
|
+
/** TMDB may serialize this list identifier as a string. */
|
|
52
|
+
id: string | TmdbId;
|
|
53
|
+
item_present: boolean;
|
|
54
|
+
[key: string]: unknown;
|
|
55
|
+
}
|
|
56
|
+
export interface ListMutationResponse {
|
|
57
|
+
status_code: number;
|
|
58
|
+
status_message: string;
|
|
59
|
+
success?: boolean;
|
|
60
|
+
[key: string]: unknown;
|
|
61
|
+
}
|
|
62
|
+
export interface CreateListResponse extends ListMutationResponse {
|
|
63
|
+
success: boolean;
|
|
64
|
+
list_id: TmdbId;
|
|
65
|
+
}
|
|
66
|
+
export interface ListMethods {
|
|
67
|
+
create(input: CreateListInput): Promise<CreateListResponse>;
|
|
68
|
+
get(listId: TmdbId, options?: ListDetailsOptions): Promise<ListDetails>;
|
|
69
|
+
itemStatus(listId: TmdbId, movieId: TmdbId, options?: LanguageOptions): Promise<ListItemStatus>;
|
|
70
|
+
addMovie(listId: TmdbId, movieId: TmdbId, options: ListSessionOptions): Promise<ListMutationResponse>;
|
|
71
|
+
removeMovie(listId: TmdbId, movieId: TmdbId, options: ListSessionOptions): Promise<ListMutationResponse>;
|
|
72
|
+
clear(listId: TmdbId, options: ClearListOptions): Promise<ListMutationResponse>;
|
|
73
|
+
delete(listId: TmdbId, options: ListSessionOptions): Promise<ListMutationResponse>;
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CreateListResponse, ListDetails, ListItemStatus, ListMutationResponse } from './list-types.js';
|
|
2
|
+
export declare function validateListDetails(value: unknown, expectedId: number): ListDetails;
|
|
3
|
+
export declare function validateListItemStatus(value: unknown, expectedId: number): ListItemStatus;
|
|
4
|
+
export declare function validateListMutation(value: unknown): ListMutationResponse;
|
|
5
|
+
export declare function validateCreateList(value: unknown): CreateListResponse;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { invalidResponse, isRecord } from './validation.js';
|
|
2
|
+
function positive(value) {
|
|
3
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
4
|
+
}
|
|
5
|
+
function count(value) {
|
|
6
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
7
|
+
}
|
|
8
|
+
function finite(value) {
|
|
9
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
10
|
+
}
|
|
11
|
+
function nullableString(value) {
|
|
12
|
+
return value === null || typeof value === 'string';
|
|
13
|
+
}
|
|
14
|
+
function movie(value) {
|
|
15
|
+
return isRecord(value) && positive(value.id) &&
|
|
16
|
+
['title', 'original_title', 'original_language', 'overview', 'release_date']
|
|
17
|
+
.every((key) => typeof value[key] === 'string') &&
|
|
18
|
+
typeof value.adult === 'boolean' && typeof value.video === 'boolean' &&
|
|
19
|
+
Array.isArray(value.genre_ids) && value.genre_ids.every(positive) &&
|
|
20
|
+
finite(value.popularity) && nullableString(value.poster_path) &&
|
|
21
|
+
nullableString(value.backdrop_path) && finite(value.vote_average) &&
|
|
22
|
+
count(value.vote_count) && (value.media_type === undefined || value.media_type === 'movie');
|
|
23
|
+
}
|
|
24
|
+
export function validateListDetails(value, expectedId) {
|
|
25
|
+
if (!isRecord(value) ||
|
|
26
|
+
!(value.id === expectedId || value.id === String(expectedId)) ||
|
|
27
|
+
!['created_by', 'description', 'iso_639_1', 'name'].every((key) => typeof value[key] === 'string') ||
|
|
28
|
+
!count(value.favorite_count) || !count(value.item_count) ||
|
|
29
|
+
!nullableString(value.poster_path) || !Array.isArray(value.items) || !value.items.every(movie)) {
|
|
30
|
+
invalidResponse();
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
export function validateListItemStatus(value, expectedId) {
|
|
35
|
+
if (!isRecord(value) ||
|
|
36
|
+
!(value.id === expectedId || value.id === String(expectedId)) ||
|
|
37
|
+
typeof value.item_present !== 'boolean') {
|
|
38
|
+
invalidResponse();
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
export function validateListMutation(value) {
|
|
43
|
+
if (!isRecord(value) || !positive(value.status_code) || typeof value.status_message !== 'string' ||
|
|
44
|
+
(value.success !== undefined && typeof value.success !== 'boolean')) {
|
|
45
|
+
invalidResponse();
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
export function validateCreateList(value) {
|
|
50
|
+
const response = validateListMutation(value);
|
|
51
|
+
if (typeof response.success !== 'boolean' || !positive(response.list_id))
|
|
52
|
+
invalidResponse();
|
|
53
|
+
return response;
|
|
54
|
+
}
|
package/dist/lists.d.ts
ADDED
package/dist/lists.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { positiveId } from './ids.js';
|
|
2
|
+
import { validateCreateList, validateListDetails, validateListItemStatus, validateListMutation } from './list-validation.js';
|
|
3
|
+
import { isRecord } from './validation.js';
|
|
4
|
+
function nonempty(value, label) {
|
|
5
|
+
if (typeof value !== 'string' || !value.trim())
|
|
6
|
+
throw new TypeError(`${label} must not be empty`);
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
function optionalLanguage(value) {
|
|
10
|
+
return value === undefined ? undefined : nonempty(value, 'language');
|
|
11
|
+
}
|
|
12
|
+
function session(options) {
|
|
13
|
+
if (!isRecord(options))
|
|
14
|
+
throw new TypeError('sessionId is required');
|
|
15
|
+
return nonempty(options.sessionId, 'sessionId');
|
|
16
|
+
}
|
|
17
|
+
export function createListMethods(http) {
|
|
18
|
+
return {
|
|
19
|
+
async create(input) {
|
|
20
|
+
const sessionId = session(input);
|
|
21
|
+
const name = nonempty(input.name, 'name');
|
|
22
|
+
if (input.description !== undefined && typeof input.description !== 'string') {
|
|
23
|
+
throw new TypeError('description must be a string');
|
|
24
|
+
}
|
|
25
|
+
const language = optionalLanguage(input.language);
|
|
26
|
+
return validateCreateList(await http.post('list', { session_id: sessionId }, {
|
|
27
|
+
name,
|
|
28
|
+
...(input.description === undefined ? {} : { description: input.description }),
|
|
29
|
+
...(language === undefined ? {} : { language }),
|
|
30
|
+
}));
|
|
31
|
+
},
|
|
32
|
+
async get(listId, options = {}) {
|
|
33
|
+
positiveId(listId);
|
|
34
|
+
if (!isRecord(options))
|
|
35
|
+
throw new TypeError('options must be an object');
|
|
36
|
+
if (options.page !== undefined && (typeof options.page !== 'number' || !Number.isSafeInteger(options.page) || options.page < 1)) {
|
|
37
|
+
throw new TypeError('page must be a positive integer');
|
|
38
|
+
}
|
|
39
|
+
return validateListDetails(await http.get(`list/${listId}`, {
|
|
40
|
+
language: optionalLanguage(options.language), page: options.page,
|
|
41
|
+
}), listId);
|
|
42
|
+
},
|
|
43
|
+
async itemStatus(listId, movieId, options = {}) {
|
|
44
|
+
positiveId(listId);
|
|
45
|
+
positiveId(movieId);
|
|
46
|
+
if (!isRecord(options))
|
|
47
|
+
throw new TypeError('options must be an object');
|
|
48
|
+
return validateListItemStatus(await http.get(`list/${listId}/item_status`, {
|
|
49
|
+
movie_id: movieId, language: optionalLanguage(options.language),
|
|
50
|
+
}), listId);
|
|
51
|
+
},
|
|
52
|
+
async addMovie(listId, movieId, options) {
|
|
53
|
+
positiveId(listId);
|
|
54
|
+
positiveId(movieId);
|
|
55
|
+
return validateListMutation(await http.post(`list/${listId}/add_item`, {
|
|
56
|
+
session_id: session(options),
|
|
57
|
+
}, { media_id: movieId }));
|
|
58
|
+
},
|
|
59
|
+
async removeMovie(listId, movieId, options) {
|
|
60
|
+
positiveId(listId);
|
|
61
|
+
positiveId(movieId);
|
|
62
|
+
return validateListMutation(await http.post(`list/${listId}/remove_item`, {
|
|
63
|
+
session_id: session(options),
|
|
64
|
+
}, { media_id: movieId }));
|
|
65
|
+
},
|
|
66
|
+
async clear(listId, options) {
|
|
67
|
+
positiveId(listId);
|
|
68
|
+
const sessionId = session(options);
|
|
69
|
+
if (typeof options.confirm !== 'boolean')
|
|
70
|
+
throw new TypeError('confirm must be a boolean');
|
|
71
|
+
return validateListMutation(await http.post(`list/${listId}/clear`, {
|
|
72
|
+
session_id: sessionId, confirm: options.confirm,
|
|
73
|
+
}));
|
|
74
|
+
},
|
|
75
|
+
async delete(listId, options) {
|
|
76
|
+
positiveId(listId);
|
|
77
|
+
return validateListMutation(await http.delete(`list/${listId}`, { session_id: session(options) }));
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { WatchProvider } from './metadata-types.js';
|
|
2
|
+
import type { Country, LanguageOptions } from './reference-types.js';
|
|
3
|
+
export interface WatchProviderOptions extends LanguageOptions {
|
|
4
|
+
watchRegion?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface WatchProviderEntry extends WatchProvider {
|
|
7
|
+
display_priority: number;
|
|
8
|
+
display_priorities: Record<string, number>;
|
|
9
|
+
}
|
|
10
|
+
export interface WatchProviderRegionsResponse {
|
|
11
|
+
results: Country[];
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface WatchProviderListResponse {
|
|
15
|
+
results: WatchProviderEntry[];
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface WatchProviderMethods {
|
|
19
|
+
regions(options?: LanguageOptions): Promise<WatchProviderRegionsResponse>;
|
|
20
|
+
movies(options?: WatchProviderOptions): Promise<WatchProviderListResponse>;
|
|
21
|
+
tv(options?: WatchProviderOptions): Promise<WatchProviderListResponse>;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { validateCountries } from './reference-validation.js';
|
|
2
|
+
import { invalidResponse, isRecord } from './validation.js';
|
|
3
|
+
function parameters(options) {
|
|
4
|
+
if (options.watchRegion !== undefined &&
|
|
5
|
+
(typeof options.watchRegion !== 'string' || !options.watchRegion.trim())) {
|
|
6
|
+
throw new TypeError('watchRegion must not be empty');
|
|
7
|
+
}
|
|
8
|
+
return { language: options.language, watch_region: options.watchRegion };
|
|
9
|
+
}
|
|
10
|
+
function count(value) {
|
|
11
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
12
|
+
}
|
|
13
|
+
function validateProviders(value) {
|
|
14
|
+
if (!isRecord(value) || !Array.isArray(value.results) || !value.results.every((item) => isRecord(item) && count(item.provider_id) && item.provider_id > 0 &&
|
|
15
|
+
typeof item.provider_name === 'string' && typeof item.logo_path === 'string' &&
|
|
16
|
+
count(item.display_priority) && isRecord(item.display_priorities) &&
|
|
17
|
+
Object.values(item.display_priorities).every(count)))
|
|
18
|
+
invalidResponse();
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export function createWatchProviderMethods(http) {
|
|
22
|
+
return {
|
|
23
|
+
async regions(options = {}) {
|
|
24
|
+
const value = await http.get('watch/providers/regions', { language: options.language });
|
|
25
|
+
if (!isRecord(value))
|
|
26
|
+
invalidResponse();
|
|
27
|
+
validateCountries(value.results);
|
|
28
|
+
return value;
|
|
29
|
+
},
|
|
30
|
+
async movies(options = {}) {
|
|
31
|
+
return validateProviders(await http.get('watch/providers/movie', parameters(options)));
|
|
32
|
+
},
|
|
33
|
+
async tv(options = {}) {
|
|
34
|
+
return validateProviders(await http.get('watch/providers/tv', parameters(options)));
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|