@weroperking/invenio-scraper 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.
Files changed (80) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +407 -0
  3. package/dist/__fixtures__/download-avatar.json +126 -0
  4. package/dist/__fixtures__/download-merlin-s1e1.json +90 -0
  5. package/dist/__fixtures__/search-titanic.json +245 -0
  6. package/dist/__fixtures__/stream-merlin-s1e1.json +32 -0
  7. package/dist/__fixtures__/stream-titanic.json +41 -0
  8. package/dist/constants.d.ts +14 -0
  9. package/dist/constants.d.ts.map +1 -0
  10. package/dist/constants.js +22 -0
  11. package/dist/constants.js.map +1 -0
  12. package/dist/details.d.ts +20 -0
  13. package/dist/details.d.ts.map +1 -0
  14. package/dist/details.js +273 -0
  15. package/dist/details.js.map +1 -0
  16. package/dist/details.test.d.ts +2 -0
  17. package/dist/details.test.d.ts.map +1 -0
  18. package/dist/details.test.js +65 -0
  19. package/dist/details.test.js.map +1 -0
  20. package/dist/download.d.ts +35 -0
  21. package/dist/download.d.ts.map +1 -0
  22. package/dist/download.js +258 -0
  23. package/dist/download.js.map +1 -0
  24. package/dist/download.test.d.ts +2 -0
  25. package/dist/download.test.d.ts.map +1 -0
  26. package/dist/download.test.js +202 -0
  27. package/dist/download.test.js.map +1 -0
  28. package/dist/errors.d.ts +31 -0
  29. package/dist/errors.d.ts.map +1 -0
  30. package/dist/errors.js +53 -0
  31. package/dist/errors.js.map +1 -0
  32. package/dist/index.d.ts +11 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +9 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/index.test.d.ts +2 -0
  37. package/dist/index.test.d.ts.map +1 -0
  38. package/dist/index.test.js +10 -0
  39. package/dist/index.test.js.map +1 -0
  40. package/dist/logger.d.ts +10 -0
  41. package/dist/logger.d.ts.map +1 -0
  42. package/dist/logger.js +15 -0
  43. package/dist/logger.js.map +1 -0
  44. package/dist/search.d.ts +4 -0
  45. package/dist/search.d.ts.map +1 -0
  46. package/dist/search.js +102 -0
  47. package/dist/search.js.map +1 -0
  48. package/dist/search.test.d.ts +2 -0
  49. package/dist/search.test.d.ts.map +1 -0
  50. package/dist/search.test.js +67 -0
  51. package/dist/search.test.js.map +1 -0
  52. package/dist/series.d.ts +15 -0
  53. package/dist/series.d.ts.map +1 -0
  54. package/dist/series.js +111 -0
  55. package/dist/series.js.map +1 -0
  56. package/dist/series.test.d.ts +2 -0
  57. package/dist/series.test.d.ts.map +1 -0
  58. package/dist/series.test.js +78 -0
  59. package/dist/series.test.js.map +1 -0
  60. package/dist/session.d.ts +64 -0
  61. package/dist/session.d.ts.map +1 -0
  62. package/dist/session.js +417 -0
  63. package/dist/session.js.map +1 -0
  64. package/dist/session.test.d.ts +2 -0
  65. package/dist/session.test.d.ts.map +1 -0
  66. package/dist/session.test.js +111 -0
  67. package/dist/session.test.js.map +1 -0
  68. package/dist/stream.d.ts +17 -0
  69. package/dist/stream.d.ts.map +1 -0
  70. package/dist/stream.js +118 -0
  71. package/dist/stream.js.map +1 -0
  72. package/dist/stream.test.d.ts +2 -0
  73. package/dist/stream.test.d.ts.map +1 -0
  74. package/dist/stream.test.js +77 -0
  75. package/dist/stream.test.js.map +1 -0
  76. package/dist/types.d.ts +293 -0
  77. package/dist/types.d.ts.map +1 -0
  78. package/dist/types.js +2 -0
  79. package/dist/types.js.map +1 -0
  80. package/package.json +86 -0
@@ -0,0 +1,31 @@
1
+ export declare class MovieboxApiError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class MovieboxHttpError extends MovieboxApiError {
5
+ readonly status: number;
6
+ readonly url: string;
7
+ constructor(message: string, status: number, url: string);
8
+ }
9
+ export declare class EmptyResponseError extends MovieboxApiError {
10
+ constructor(url: string);
11
+ }
12
+ export declare class UnsuccessfulResponseError extends MovieboxApiError {
13
+ readonly response: unknown;
14
+ constructor(url: string, response: unknown);
15
+ }
16
+ export declare class GeoBlockedError extends MovieboxHttpError {
17
+ constructor(url: string, status: number);
18
+ }
19
+ export interface MirrorFailure {
20
+ url: string;
21
+ error: Error;
22
+ }
23
+ export declare class MirrorExhaustedError extends MovieboxApiError {
24
+ readonly failures: MirrorFailure[];
25
+ constructor(failures: MirrorFailure[]);
26
+ }
27
+ export declare class RetryLimitExceededError extends MovieboxApiError {
28
+ readonly attempts: number;
29
+ constructor(message: string, attempts: number);
30
+ }
31
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,iBAAkB,SAAQ,gBAAgB;IACrD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAMzD;AAED,qBAAa,kBAAmB,SAAQ,gBAAgB;gBAC1C,GAAG,EAAE,MAAM;CAIxB;AAED,qBAAa,yBAA0B,SAAQ,gBAAgB;IAC7D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;gBAEf,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO;CAK3C;AAED,qBAAa,eAAgB,SAAQ,iBAAiB;gBACxC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAIxC;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;CACd;AAED,qBAAa,oBAAqB,SAAQ,gBAAgB;IACxD,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,CAAC;gBAEvB,QAAQ,EAAE,aAAa,EAAE;CAKtC;AAED,qBAAa,uBAAwB,SAAQ,gBAAgB;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAK9C"}
package/dist/errors.js ADDED
@@ -0,0 +1,53 @@
1
+ export class MovieboxApiError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'MovieboxApiError';
5
+ }
6
+ }
7
+ export class MovieboxHttpError extends MovieboxApiError {
8
+ status;
9
+ url;
10
+ constructor(message, status, url) {
11
+ super(message);
12
+ this.name = 'MovieboxHttpError';
13
+ this.status = status;
14
+ this.url = url;
15
+ }
16
+ }
17
+ export class EmptyResponseError extends MovieboxApiError {
18
+ constructor(url) {
19
+ super(`Moviebox API returned an empty response for ${url}`);
20
+ this.name = 'EmptyResponseError';
21
+ }
22
+ }
23
+ export class UnsuccessfulResponseError extends MovieboxApiError {
24
+ response;
25
+ constructor(url, response) {
26
+ super(`Moviebox API reported failure for ${url}`);
27
+ this.name = 'UnsuccessfulResponseError';
28
+ this.response = response;
29
+ }
30
+ }
31
+ export class GeoBlockedError extends MovieboxHttpError {
32
+ constructor(url, status) {
33
+ super(`Moviebox content is not available in your region for ${url}`, status, url);
34
+ this.name = 'GeoBlockedError';
35
+ }
36
+ }
37
+ export class MirrorExhaustedError extends MovieboxApiError {
38
+ failures;
39
+ constructor(failures) {
40
+ super('All Moviebox mirrors failed');
41
+ this.name = 'MirrorExhaustedError';
42
+ this.failures = failures;
43
+ }
44
+ }
45
+ export class RetryLimitExceededError extends MovieboxApiError {
46
+ attempts;
47
+ constructor(message, attempts) {
48
+ super(message);
49
+ this.name = 'RetryLimitExceededError';
50
+ this.attempts = attempts;
51
+ }
52
+ }
53
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,gBAAgB;IAC5C,MAAM,CAAS;IACf,GAAG,CAAS;IAErB,YAAY,OAAe,EAAE,MAAc,EAAE,GAAW;QACtD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,gBAAgB;IACtD,YAAY,GAAW;QACrB,KAAK,CAAC,+CAA+C,GAAG,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,MAAM,OAAO,yBAA0B,SAAQ,gBAAgB;IACpD,QAAQ,CAAU;IAE3B,YAAY,GAAW,EAAE,QAAiB;QACxC,KAAK,CAAC,qCAAqC,GAAG,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;QACxC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,iBAAiB;IACpD,YAAY,GAAW,EAAE,MAAc;QACrC,KAAK,CAAC,wDAAwD,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;QAClF,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAOD,MAAM,OAAO,oBAAqB,SAAQ,gBAAgB;IAC/C,QAAQ,CAAkB;IAEnC,YAAY,QAAyB;QACnC,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,uBAAwB,SAAQ,gBAAgB;IAClD,QAAQ,CAAS;IAE1B,YAAY,OAAe,EAAE,QAAgB;QAC3C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ export { MovieboxSession } from './session.js';
2
+ export { search } from './search.js';
3
+ export { getMovieDetails } from './details.js';
4
+ export { getSeriesDetails, getEpisodeQualities } from './series.js';
5
+ export { getMovieStreamUrl, getEpisodeStreamUrl } from './stream.js';
6
+ export { downloadMovie, downloadEpisode, downloadMediaFile } from './download.js';
7
+ export { createLogger, createNoopLogger } from './logger.js';
8
+ export { MovieboxApiError, MovieboxHttpError, EmptyResponseError, UnsuccessfulResponseError, GeoBlockedError, MirrorExhaustedError, RetryLimitExceededError } from './errors.js';
9
+ export type { SearchParams, SearchFilter, SearchResultPage, NormalizedSearchResult, SearchResultType, MovieDetails, MovieDownloadOption, MovieSubtitleOption, SeriesDetails, SeriesSeasonSummary, EpisodeQualities, EpisodeDownloadOption, EpisodeSubtitleOption, StreamOption, StreamResult, DownloadQuality, DownloadMode, DownloadProgress } from './types.js';
10
+ export type { Logger } from './logger.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,eAAe,EACf,oBAAoB,EACpB,uBAAuB,EACxB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,gBAAgB,EACjB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { MovieboxSession } from './session.js';
2
+ export { search } from './search.js';
3
+ export { getMovieDetails } from './details.js';
4
+ export { getSeriesDetails, getEpisodeQualities } from './series.js';
5
+ export { getMovieStreamUrl, getEpisodeStreamUrl } from './stream.js';
6
+ export { downloadMovie, downloadEpisode, downloadMediaFile } from './download.js';
7
+ export { createLogger, createNoopLogger } from './logger.js';
8
+ export { MovieboxApiError, MovieboxHttpError, EmptyResponseError, UnsuccessfulResponseError, GeoBlockedError, MirrorExhaustedError, RetryLimitExceededError } from './errors.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,eAAe,EACf,oBAAoB,EACpB,uBAAuB,EACxB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { MovieboxSession, getMovieDetails, search } from './index.js';
3
+ describe('package exports', () => {
4
+ it('exposes session and helpers', () => {
5
+ expect(typeof MovieboxSession).toBe('function');
6
+ expect(typeof search).toBe('function');
7
+ expect(typeof getMovieDetails).toBe('function');
8
+ });
9
+ });
10
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEtE,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAChD,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvC,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import pinoLogger from 'pino';
2
+ import type { LevelWithSilent } from 'pino';
3
+ export type Logger = Pick<ReturnType<typeof pinoLogger>, 'debug' | 'info' | 'warn' | 'error'>;
4
+ export interface LoggerOptions {
5
+ level?: LevelWithSilent;
6
+ name?: string;
7
+ }
8
+ export declare function createLogger(options?: LoggerOptions): Logger;
9
+ export declare function createNoopLogger(): Logger;
10
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,MAAM,CAAC;AAC9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAE5C,MAAM,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAE9F,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,aAAkB,GAAG,MAAM,CAGhE;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAQzC"}
package/dist/logger.js ADDED
@@ -0,0 +1,15 @@
1
+ import pinoLogger from 'pino';
2
+ export function createLogger(options = {}) {
3
+ const { level = 'info', name = 'moviebox-js-sdk' } = options;
4
+ return pinoLogger({ level, name });
5
+ }
6
+ export function createNoopLogger() {
7
+ const noop = () => { };
8
+ return {
9
+ debug: noop,
10
+ info: noop,
11
+ warn: noop,
12
+ error: noop
13
+ };
14
+ }
15
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,MAAM,CAAC;AAU9B,MAAM,UAAU,YAAY,CAAC,UAAyB,EAAE;IACtD,MAAM,EAAE,KAAK,GAAG,MAAM,EAAE,IAAI,GAAG,iBAAiB,EAAE,GAAG,OAAO,CAAC;IAC7D,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,GAAG,EAAE,GAAE,CAAC,CAAC;IACtB,OAAO;QACL,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;KACK,CAAC;AACrB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { MovieboxSession } from './session.js';
2
+ import type { SearchParams, SearchResultPage } from './types.js';
3
+ export declare function search(session: MovieboxSession, params: SearchParams): Promise<SearchResultPage>;
4
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAKV,YAAY,EACZ,gBAAgB,EAEjB,MAAM,YAAY,CAAC;AAepB,wBAAsB,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyBtG"}
package/dist/search.js ADDED
@@ -0,0 +1,102 @@
1
+ const SUBJECT_FILTER_TO_CODE = {
2
+ all: 0,
3
+ movie: 1,
4
+ tv: 2,
5
+ music: 6
6
+ };
7
+ const SUBJECT_CODE_TO_LITERAL = {
8
+ 1: 'movie',
9
+ 2: 'tv',
10
+ 6: 'music'
11
+ };
12
+ export async function search(session, params) {
13
+ const payload = {
14
+ keyword: params.query,
15
+ page: params.page ?? 1,
16
+ perPage: params.perPage ?? 24,
17
+ subjectType: SUBJECT_FILTER_TO_CODE[params.type ?? 'all'] ?? SUBJECT_FILTER_TO_CODE.all
18
+ };
19
+ const data = await session.postJson('/wefeed-h5-bff/web/subject/search', payload);
20
+ const results = data.items.map((item) => normalizeSearchItem(session, item));
21
+ const page = toInt(data.pager.page) ?? data.pager.page;
22
+ const perPage = toInt(data.pager.perPage) ?? data.pager.perPage;
23
+ const totalCount = toInt(data.pager.totalCount) ?? data.pager.totalCount;
24
+ const nextPage = data.pager.hasMore ? toInt(data.pager.nextPage) : null;
25
+ return {
26
+ results,
27
+ page,
28
+ perPage,
29
+ totalCount,
30
+ hasMore: data.pager.hasMore,
31
+ nextPage,
32
+ raw: data
33
+ };
34
+ }
35
+ function normalizeSearchItem(session, item) {
36
+ const releaseYear = parseReleaseYear(item.releaseDate);
37
+ const genres = normalizeDelimitedField(item.genre);
38
+ const subtitles = normalizeDelimitedField(item.subtitles);
39
+ const type = SUBJECT_CODE_TO_LITERAL[item.subjectType] ?? 'unknown';
40
+ const posterUrl = item.image?.url ?? item.cover?.url ?? null;
41
+ const rating = parseRating(item.imdbRatingValue);
42
+ return {
43
+ id: item.subjectId,
44
+ title: item.title,
45
+ type,
46
+ description: item.description ?? '',
47
+ releaseDate: item.releaseDate ?? null,
48
+ releaseYear,
49
+ rating,
50
+ genres,
51
+ country: item.countryName ?? null,
52
+ pageUrl: session.buildDetailUrl(item.detailPath, item.subjectId),
53
+ posterUrl,
54
+ subtitles,
55
+ hasResource: Boolean(item.hasResource),
56
+ raw: item
57
+ };
58
+ }
59
+ function parseReleaseYear(releaseDate) {
60
+ if (!releaseDate) {
61
+ return null;
62
+ }
63
+ const year = Number.parseInt(releaseDate.slice(0, 4), 10);
64
+ return Number.isFinite(year) ? year : null;
65
+ }
66
+ function normalizeDelimitedField(value) {
67
+ if (!value) {
68
+ return [];
69
+ }
70
+ if (Array.isArray(value)) {
71
+ return value.map((entry) => entry.trim()).filter(Boolean);
72
+ }
73
+ return value
74
+ .split(',')
75
+ .map((entry) => entry.trim())
76
+ .filter(Boolean);
77
+ }
78
+ function parseRating(value) {
79
+ if (typeof value === 'number') {
80
+ return value;
81
+ }
82
+ if (typeof value === 'string') {
83
+ const parsed = parseFloat(value);
84
+ if (Number.isFinite(parsed)) {
85
+ return parsed;
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+ function toInt(value) {
91
+ if (typeof value === 'number' && Number.isFinite(value)) {
92
+ return value;
93
+ }
94
+ if (typeof value === 'string') {
95
+ const parsed = parseInt(value, 10);
96
+ if (Number.isFinite(parsed)) {
97
+ return parsed;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=search.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.test.d.ts","sourceRoot":"","sources":["../src/search.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import fixture from './__fixtures__/search-titanic.json' with { type: 'json' };
4
+ import { search } from './search.js';
5
+ import { MovieboxSession } from './session.js';
6
+ describe('search', () => {
7
+ let fetchMock;
8
+ let session;
9
+ beforeEach(() => {
10
+ fetchMock = vi.fn();
11
+ session = new MovieboxSession({ baseUrl: 'https://moviebox.test', fetch: fetchMock });
12
+ });
13
+ afterEach(() => {
14
+ vi.clearAllMocks();
15
+ });
16
+ it('sends a search request and normalizes results', async () => {
17
+ fetchMock.mockResolvedValue(new Response(JSON.stringify(fixture), {
18
+ status: 200,
19
+ headers: { 'Content-Type': 'application/json' }
20
+ }));
21
+ const result = await search(session, { query: 'Titanic', type: 'movie', perPage: 4, page: 1 });
22
+ expect(fetchMock).toHaveBeenCalledTimes(1);
23
+ const [requestInput, requestInit] = fetchMock.mock.calls[0];
24
+ const requestUrl = typeof requestInput === 'string'
25
+ ? requestInput
26
+ : requestInput instanceof URL
27
+ ? requestInput.toString()
28
+ : requestInput.url;
29
+ expect(requestUrl).toBe('https://moviebox.test/wefeed-h5-bff/web/subject/search');
30
+ expect(requestInit?.method).toBe('POST');
31
+ expect(requestInit?.headers).toMatchObject({ 'Content-Type': 'application/json' });
32
+ const body = requestInit?.body;
33
+ const parsedBody = typeof body === 'string' ? JSON.parse(body) : undefined;
34
+ expect(parsedBody).toEqual({
35
+ keyword: 'Titanic',
36
+ page: 1,
37
+ perPage: 4,
38
+ subjectType: 1
39
+ });
40
+ expect(result.page).toBe(1);
41
+ expect(result.perPage).toBe(5);
42
+ expect(result.totalCount).toBe(146);
43
+ expect(result.hasMore).toBe(true);
44
+ expect(result.nextPage).toBe(2);
45
+ expect(result.results).toHaveLength(5);
46
+ const first = result.results[0];
47
+ const second = result.results[1];
48
+ if (!first || !second) {
49
+ throw new Error('Search results should include at least two items for this fixture.');
50
+ }
51
+ expect(first.id).toBe('5390197429792821032');
52
+ expect(first.title).toBe('Titanic');
53
+ expect(first.type).toBe('movie');
54
+ expect(first.releaseYear).toBe(1997);
55
+ expect(first.genres).toEqual(['Drama', 'Romance']);
56
+ expect(first.subtitles).toContain('English');
57
+ expect(first.subtitles).toContain('Español');
58
+ expect(first.pageUrl).toBe('https://moviebox.test/detail/titanic-m7a9yt0abq6?id=5390197429792821032');
59
+ expect(first.posterUrl).toBe('https://pbcdnw.aoneroom.com/image/2025/11/01/0e68a08216fe532db1eef333150967fc.jpg');
60
+ expect(first.rating).toBeCloseTo(7.9);
61
+ expect(second.id).toBe('206379412718240440');
62
+ expect(second.releaseYear).toBe(1950);
63
+ expect(second.subtitles).toContain('English');
64
+ expect(second.hasResource).toBe(true);
65
+ });
66
+ });
67
+ //# sourceMappingURL=search.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.test.js","sourceRoot":"","sources":["../src/search.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { MovieboxSession } from './session.js';
2
+ import type { EpisodeQualities, SeriesDetails } from './types.js';
3
+ interface GetSeriesDetailsParams {
4
+ detailPath: string;
5
+ }
6
+ interface GetEpisodeQualitiesParams {
7
+ detailPath: string;
8
+ season: number;
9
+ episode: number;
10
+ subjectId?: string;
11
+ }
12
+ export declare function getSeriesDetails(session: MovieboxSession, params: GetSeriesDetailsParams): Promise<SeriesDetails>;
13
+ export declare function getEpisodeQualities(session: MovieboxSession, params: GetEpisodeQualitiesParams): Promise<EpisodeQualities>;
14
+ export {};
15
+ //# sourceMappingURL=series.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"series.d.ts","sourceRoot":"","sources":["../src/series.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EACV,gBAAgB,EAKhB,aAAa,EAEd,MAAM,YAAY,CAAC;AAEpB,UAAU,sBAAsB;IAC9B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,yBAAyB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,aAAa,CAAC,CAexB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,gBAAgB,CAAC,CAqB3B"}
package/dist/series.js ADDED
@@ -0,0 +1,111 @@
1
+ import { extractNuxtState, normalizeCaptions, normalizeDetailPath, normalizeDownloadOptions, resolveRating, selectBest, selectWorst, splitDelimited, toNumber } from './details.js';
2
+ import { MovieboxApiError } from './errors.js';
3
+ export async function getSeriesDetails(session, params) {
4
+ const normalizedPath = normalizeDetailPath(params.detailPath);
5
+ const html = await session.fetchHtml(normalizedPath, {
6
+ headers: {
7
+ Accept: 'text/html,application/xhtml+xml'
8
+ }
9
+ });
10
+ const nuxtState = extractNuxtState(html);
11
+ const resData = isRawSeriesResData(nuxtState.resData) ? nuxtState.resData : undefined;
12
+ if (!resData?.subject?.subjectId) {
13
+ throw new MovieboxApiError('Series detail state is missing subject information.');
14
+ }
15
+ return normalizeSeriesDetails(resData);
16
+ }
17
+ export async function getEpisodeQualities(session, params) {
18
+ const slug = extractDetailSlug(params.detailPath);
19
+ const normalizedPath = normalizeDetailPath(params.detailPath);
20
+ const subjectId = params.subjectId ?? (await resolveSubjectId(session, normalizedPath));
21
+ const downloadData = await session.fetchJson('/wefeed-h5-bff/web/subject/download', {
22
+ searchParams: {
23
+ subjectId,
24
+ se: params.season,
25
+ ep: params.episode
26
+ },
27
+ headers: {
28
+ Referer: session.buildUrl(`/movies/${slug}`)
29
+ }
30
+ });
31
+ return normalizeEpisodeQualities(downloadData);
32
+ }
33
+ function normalizeSeriesDetails(resData) {
34
+ const subject = resData.subject;
35
+ const releaseDate = subject.releaseDate ?? null;
36
+ const releaseYear = releaseDate ? parseInt(releaseDate.slice(0, 4), 10) || null : null;
37
+ const posterUrl = subject.cover?.url ?? null;
38
+ const rating = resolveRating(subject);
39
+ const ratingCount = typeof subject.imdbRatingCount === 'number' ? subject.imdbRatingCount : null;
40
+ const genres = subject.genre ?? [];
41
+ const availableSubtitles = splitDelimited(subject.subtitles ?? []);
42
+ const seasons = normalizeSeasons(resData.resource?.seasons ?? []);
43
+ return {
44
+ id: subject.subjectId,
45
+ detailPath: subject.detailPath,
46
+ title: subject.title,
47
+ synopsis: subject.description,
48
+ releaseDate,
49
+ releaseYear,
50
+ genres,
51
+ country: subject.countryName ?? null,
52
+ posterUrl,
53
+ rating,
54
+ ratingCount,
55
+ hasResource: Boolean(subject.hasResource),
56
+ availableSubtitleLanguages: availableSubtitles,
57
+ seasons
58
+ };
59
+ }
60
+ function normalizeSeasons(seasons) {
61
+ return seasons
62
+ .map((season) => {
63
+ const resolutions = (season.resolutions ?? [])
64
+ .map((entry) => entry.resolution)
65
+ .filter((resolution) => Number.isFinite(resolution));
66
+ const uniqueResolutions = Array.from(new Set(resolutions)).sort((a, b) => a - b);
67
+ return {
68
+ seasonNumber: season.se,
69
+ episodeCount: toNumber(season.maxEp),
70
+ availableResolutions: uniqueResolutions
71
+ };
72
+ })
73
+ .sort((a, b) => a.seasonNumber - b.seasonNumber);
74
+ }
75
+ function normalizeEpisodeQualities(data) {
76
+ const downloads = normalizeDownloadOptions(data.downloads);
77
+ const captions = normalizeCaptions(data.captions);
78
+ return {
79
+ downloads,
80
+ bestDownload: selectBest(downloads),
81
+ worstDownload: selectWorst(downloads),
82
+ captions
83
+ };
84
+ }
85
+ async function resolveSubjectId(session, normalizedPath) {
86
+ const html = await session.fetchHtml(normalizedPath, {
87
+ headers: {
88
+ Accept: 'text/html,application/xhtml+xml'
89
+ }
90
+ });
91
+ const nuxtState = extractNuxtState(html);
92
+ const resData = isRawSeriesResData(nuxtState.resData) ? nuxtState.resData : undefined;
93
+ const subjectId = resData?.subject?.subjectId;
94
+ if (!subjectId) {
95
+ throw new MovieboxApiError('Unable to resolve subjectId for episode download request.');
96
+ }
97
+ return subjectId;
98
+ }
99
+ function extractDetailSlug(detailPath) {
100
+ const parts = detailPath.split('/').filter(Boolean);
101
+ return parts.at(-1) ?? detailPath;
102
+ }
103
+ function isRawSeriesResData(value) {
104
+ if (!value || typeof value !== 'object') {
105
+ return false;
106
+ }
107
+ const candidate = value;
108
+ const subject = candidate.subject;
109
+ return Boolean(subject && typeof subject.subjectId === 'string' && typeof subject.detailPath === 'string');
110
+ }
111
+ //# sourceMappingURL=series.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"series.js","sourceRoot":"","sources":["../src/series.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=series.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"series.test.d.ts","sourceRoot":"","sources":["../src/series.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import downloadFixture from './__fixtures__/download-merlin-s1e1.json' with { type: 'json' };
6
+ import { getEpisodeQualities, getSeriesDetails } from './series.js';
7
+ import { MovieboxSession } from './session.js';
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const detailHtml = readFileSync(resolve(__dirname, '__fixtures__', 'detail-merlin.html'), 'utf8');
11
+ const toUrlString = (input) => {
12
+ if (typeof input === 'string') {
13
+ return input;
14
+ }
15
+ if (input instanceof URL) {
16
+ return input.toString();
17
+ }
18
+ return input.url;
19
+ };
20
+ describe('series support', () => {
21
+ let fetchMock;
22
+ let session;
23
+ beforeEach(() => {
24
+ fetchMock = vi.fn();
25
+ session = new MovieboxSession({ baseUrl: 'https://moviebox.test', fetch: fetchMock });
26
+ });
27
+ afterEach(() => {
28
+ vi.clearAllMocks();
29
+ });
30
+ it('extracts season metadata from series detail page', async () => {
31
+ fetchMock.mockResolvedValueOnce(new Response(detailHtml, {
32
+ status: 200,
33
+ headers: { 'Content-Type': 'text/html' }
34
+ }));
35
+ const details = await getSeriesDetails(session, { detailPath: 'merlin-sMxCiIO6fZ9' });
36
+ expect(fetchMock).toHaveBeenCalledTimes(1);
37
+ const [request] = fetchMock.mock.calls[0] ?? [];
38
+ expect(request).toBeDefined();
39
+ expect(toUrlString(request)).toBe('https://moviebox.test/detail/merlin-sMxCiIO6fZ9');
40
+ expect(details.id).toBe('8382755684005333552');
41
+ expect(details.title).toBe('Merlin');
42
+ expect(details.releaseYear).toBe(2009);
43
+ expect(details.availableSubtitleLanguages).toEqual(expect.arrayContaining(['English', 'Français', 'Türkçe']));
44
+ expect(details.seasons).toHaveLength(5);
45
+ const firstSeason = details.seasons[0];
46
+ expect(firstSeason).toEqual({
47
+ seasonNumber: 1,
48
+ episodeCount: 13,
49
+ availableResolutions: [360, 480]
50
+ });
51
+ });
52
+ it('fetches episode download and caption data', async () => {
53
+ fetchMock.mockResolvedValueOnce(new Response(JSON.stringify(downloadFixture), {
54
+ status: 200,
55
+ headers: { 'Content-Type': 'application/json' }
56
+ }));
57
+ const result = await getEpisodeQualities(session, {
58
+ detailPath: 'merlin-sMxCiIO6fZ9',
59
+ season: 1,
60
+ episode: 1,
61
+ subjectId: '8382755684005333552'
62
+ });
63
+ expect(fetchMock).toHaveBeenCalledTimes(1);
64
+ const [requestInput, requestInit] = fetchMock.mock.calls[0] ?? [];
65
+ expect(requestInput).toBeDefined();
66
+ const requestUrl = toUrlString(requestInput);
67
+ expect(requestUrl).toContain('/wefeed-h5-bff/web/subject/download');
68
+ expect(requestUrl).toContain('subjectId=8382755684005333552');
69
+ expect(requestUrl).toContain('se=1');
70
+ expect(requestUrl).toContain('ep=1');
71
+ expect(requestInit?.headers).toMatchObject({ Referer: 'https://moviebox.test/movies/merlin-sMxCiIO6fZ9' });
72
+ expect(result.downloads).toHaveLength(downloadFixture.data.downloads.length);
73
+ expect(result.bestDownload?.resolution).toBe(480);
74
+ expect(result.worstDownload?.resolution).toBe(360);
75
+ expect(result.captions).toHaveLength(downloadFixture.data.captions.length);
76
+ });
77
+ });
78
+ //# sourceMappingURL=series.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"series.test.js","sourceRoot":"","sources":["../src/series.test.ts"],"names":[],"mappings":"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"}