movie-agent 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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +464 -0
  3. package/bin/movie-agent +209 -0
  4. package/dist/agent.d.ts +89 -0
  5. package/dist/agent.d.ts.map +1 -0
  6. package/dist/agent.js +365 -0
  7. package/dist/agent.js.map +1 -0
  8. package/dist/cache.d.ts +75 -0
  9. package/dist/cache.d.ts.map +1 -0
  10. package/dist/cache.js +133 -0
  11. package/dist/cache.js.map +1 -0
  12. package/dist/config.d.ts +17 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +31 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/discover.d.ts +38 -0
  17. package/dist/discover.d.ts.map +1 -0
  18. package/dist/discover.js +121 -0
  19. package/dist/discover.js.map +1 -0
  20. package/dist/factory.d.ts +87 -0
  21. package/dist/factory.d.ts.map +1 -0
  22. package/dist/factory.js +118 -0
  23. package/dist/factory.js.map +1 -0
  24. package/dist/filters.d.ts +61 -0
  25. package/dist/filters.d.ts.map +1 -0
  26. package/dist/filters.js +97 -0
  27. package/dist/filters.js.map +1 -0
  28. package/dist/format.d.ts +33 -0
  29. package/dist/format.d.ts.map +1 -0
  30. package/dist/format.js +85 -0
  31. package/dist/format.js.map +1 -0
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +20 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/mood.d.ts +7 -0
  37. package/dist/mood.d.ts.map +1 -0
  38. package/dist/mood.js +21 -0
  39. package/dist/mood.js.map +1 -0
  40. package/dist/providers.d.ts +10 -0
  41. package/dist/providers.d.ts.map +1 -0
  42. package/dist/providers.js +70 -0
  43. package/dist/providers.js.map +1 -0
  44. package/dist/ranking.d.ts +57 -0
  45. package/dist/ranking.d.ts.map +1 -0
  46. package/dist/ranking.js +198 -0
  47. package/dist/ranking.js.map +1 -0
  48. package/dist/tmdbApi.d.ts +79 -0
  49. package/dist/tmdbApi.d.ts.map +1 -0
  50. package/dist/tmdbApi.js +88 -0
  51. package/dist/tmdbApi.js.map +1 -0
  52. package/dist/types.d.ts +99 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/types.js +3 -0
  55. package/dist/types.js.map +1 -0
  56. package/dist/validate.d.ts +13 -0
  57. package/dist/validate.d.ts.map +1 -0
  58. package/dist/validate.js +47 -0
  59. package/dist/validate.js.map +1 -0
  60. package/package.json +72 -0
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ // src/filters.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.filterByPlatforms = filterByPlatforms;
5
+ exports.filterByRuntime = filterByRuntime;
6
+ exports.filterByYear = filterByYear;
7
+ exports.applyFilters = applyFilters;
8
+ /**
9
+ * Filter a movie by checking if it's available on any of the user's platforms
10
+ * @param movie The movie object with available platforms
11
+ * @param userPlatforms Array of platform names the user has access to
12
+ * @returns true if the movie is available on at least one of the user's platforms
13
+ */
14
+ function filterByPlatforms(movie, userPlatforms) {
15
+ if (!movie.platforms || movie.platforms.length === 0) {
16
+ return false;
17
+ }
18
+ if (!userPlatforms || userPlatforms.length === 0) {
19
+ return false;
20
+ }
21
+ // Check if any of the movie's platforms match any of the user's platforms
22
+ return movie.platforms.some(platform => userPlatforms.includes(platform));
23
+ }
24
+ /**
25
+ * Filter a movie by runtime constraints
26
+ * @param movie The movie object with runtime information
27
+ * @param constraints Runtime constraints (min and/or max)
28
+ * @returns true if the movie's runtime satisfies the constraints
29
+ */
30
+ function filterByRuntime(movie, constraints) {
31
+ // If runtime is not specified, we can't filter
32
+ if (movie.runtime === undefined || movie.runtime === null) {
33
+ return false;
34
+ }
35
+ const { min, max } = constraints;
36
+ // Check minimum runtime constraint
37
+ if (min !== undefined && movie.runtime < min) {
38
+ return false;
39
+ }
40
+ // Check maximum runtime constraint
41
+ if (max !== undefined && movie.runtime > max) {
42
+ return false;
43
+ }
44
+ return true;
45
+ }
46
+ /**
47
+ * Filter a movie by release year
48
+ * @param movie The movie object with release_date
49
+ * @param year Either a specific year (number) or a year range object
50
+ * @returns true if the movie's release year matches the criteria
51
+ */
52
+ function filterByYear(movie, year) {
53
+ if (!movie.release_date) {
54
+ return false;
55
+ }
56
+ // Extract year from release_date (format: YYYY-MM-DD)
57
+ const releaseYear = parseInt(movie.release_date.split('-')[0], 10);
58
+ if (isNaN(releaseYear)) {
59
+ return false;
60
+ }
61
+ // If year is a number, check for exact match
62
+ if (typeof year === 'number') {
63
+ return releaseYear === year;
64
+ }
65
+ // If year is a range, check if release year falls within range
66
+ const { from, to } = year;
67
+ if (from !== undefined && releaseYear < from) {
68
+ return false;
69
+ }
70
+ if (to !== undefined && releaseYear > to) {
71
+ return false;
72
+ }
73
+ return true;
74
+ }
75
+ /**
76
+ * Apply multiple filters to an array of movies
77
+ * @param movies Array of movies to filter
78
+ * @param options Filter options to apply
79
+ * @returns Filtered array of movies that pass all specified filters
80
+ */
81
+ function applyFilters(movies, options) {
82
+ let filtered = movies;
83
+ // Apply platform filter if specified
84
+ if (options.platforms && options.platforms.length > 0) {
85
+ filtered = filtered.filter(movie => filterByPlatforms(movie, options.platforms));
86
+ }
87
+ // Apply runtime filter if specified
88
+ if (options.runtime) {
89
+ filtered = filtered.filter(movie => filterByRuntime(movie, options.runtime));
90
+ }
91
+ // Apply year filter if specified
92
+ if (options.year !== undefined) {
93
+ filtered = filtered.filter(movie => filterByYear(movie, options.year));
94
+ }
95
+ return filtered;
96
+ }
97
+ //# sourceMappingURL=filters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.js","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { MovieRecommendation, StreamingPlatform, AgentResponse, ResponseMetadata } from './types';
2
+ /**
3
+ * Builds a concise, non-spoiler description (50-100 words)
4
+ * @param overview - The movie overview from TMDb
5
+ * @returns Formatted description within word count constraints
6
+ */
7
+ export declare function buildDescription(overview: string): string;
8
+ /**
9
+ * Formats a movie with streaming providers into a MovieRecommendation
10
+ * @param movie - TMDb movie object with id, title, release_date, runtime, overview, genres
11
+ * @param providers - Streaming provider information
12
+ * @param reason - Why this movie was recommended
13
+ * @returns Formatted MovieRecommendation
14
+ */
15
+ export declare function toRecommendation(movie: {
16
+ id: number;
17
+ title: string;
18
+ release_date: string;
19
+ runtime: number;
20
+ overview: string;
21
+ genres: Array<{
22
+ id: number;
23
+ name: string;
24
+ }>;
25
+ }, providers: StreamingPlatform[], reason: string): MovieRecommendation;
26
+ /**
27
+ * Formats the complete agent response with recommendations and metadata
28
+ * @param recommendations - Array of MovieRecommendation objects (3-5 items)
29
+ * @param metadata - Request metadata including input parameters
30
+ * @returns Complete AgentResponse object
31
+ */
32
+ export declare function formatResponse(recommendations: MovieRecommendation[], metadata: Omit<ResponseMetadata, 'requestTimestamp' | 'totalResults'>): AgentResponse;
33
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAuCzD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE;IACL,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7C,EACD,SAAS,EAAE,iBAAiB,EAAE,EAC9B,MAAM,EAAE,MAAM,GACb,mBAAmB,CAcrB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,eAAe,EAAE,mBAAmB,EAAE,EACtC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,kBAAkB,GAAG,cAAc,CAAC,GACpE,aAAa,CAkBf"}
package/dist/format.js ADDED
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildDescription = buildDescription;
4
+ exports.toRecommendation = toRecommendation;
5
+ exports.formatResponse = formatResponse;
6
+ /**
7
+ * Builds a concise, non-spoiler description (50-100 words)
8
+ * @param overview - The movie overview from TMDb
9
+ * @returns Formatted description within word count constraints
10
+ */
11
+ function buildDescription(overview) {
12
+ // TODO: Implement intelligent description builder
13
+ // - Truncate to 50-100 words
14
+ // - Avoid spoilers (could use AI or heuristics)
15
+ // - Ensure complete sentences
16
+ if (!overview) {
17
+ return 'No description available.';
18
+ }
19
+ const words = overview.split(/\s+/);
20
+ const minWords = 50;
21
+ const maxWords = 100;
22
+ if (words.length <= maxWords) {
23
+ return overview;
24
+ }
25
+ // Truncate to max words and try to end at a sentence boundary
26
+ const truncated = words.slice(0, maxWords).join(' ');
27
+ // Find the last sentence-ending punctuation within our limit
28
+ const lastPeriod = truncated.lastIndexOf('.');
29
+ const lastExclamation = truncated.lastIndexOf('!');
30
+ const lastQuestion = truncated.lastIndexOf('?');
31
+ const lastSentenceEnd = Math.max(lastPeriod, lastExclamation, lastQuestion);
32
+ if (lastSentenceEnd > 0) {
33
+ // Check if we have enough words before this point
34
+ const beforeSentenceEnd = truncated.substring(0, lastSentenceEnd + 1);
35
+ const beforeWords = beforeSentenceEnd.split(/\s+/).length;
36
+ if (beforeWords >= minWords) {
37
+ return beforeSentenceEnd;
38
+ }
39
+ }
40
+ // If we can't find a good sentence boundary, add ellipsis
41
+ return truncated + '...';
42
+ }
43
+ /**
44
+ * Formats a movie with streaming providers into a MovieRecommendation
45
+ * @param movie - TMDb movie object with id, title, release_date, runtime, overview, genres
46
+ * @param providers - Streaming provider information
47
+ * @param reason - Why this movie was recommended
48
+ * @returns Formatted MovieRecommendation
49
+ */
50
+ function toRecommendation(movie, providers, reason) {
51
+ const releaseYear = new Date(movie.release_date).getFullYear();
52
+ const description = buildDescription(movie.overview);
53
+ const genres = movie.genres.map(g => g.name);
54
+ return {
55
+ title: movie.title,
56
+ releaseYear,
57
+ runtime: movie.runtime,
58
+ description,
59
+ genres,
60
+ streamingPlatforms: providers,
61
+ matchReason: reason,
62
+ };
63
+ }
64
+ /**
65
+ * Formats the complete agent response with recommendations and metadata
66
+ * @param recommendations - Array of MovieRecommendation objects (3-5 items)
67
+ * @param metadata - Request metadata including input parameters
68
+ * @returns Complete AgentResponse object
69
+ */
70
+ function formatResponse(recommendations, metadata) {
71
+ // Enforce 3-5 recommendations
72
+ if (recommendations.length < 3 || recommendations.length > 5) {
73
+ throw new Error(`Expected 3-5 recommendations, got ${recommendations.length}`);
74
+ }
75
+ const response = {
76
+ recommendations,
77
+ metadata: {
78
+ requestTimestamp: new Date().toISOString(),
79
+ totalResults: recommendations.length,
80
+ inputParameters: metadata.inputParameters,
81
+ },
82
+ };
83
+ return response;
84
+ }
85
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":";;AAYA,4CAuCC;AASD,4CAyBC;AAQD,wCAqBC;AA3GD;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,QAAgB;IAC/C,kDAAkD;IAClD,6BAA6B;IAC7B,gDAAgD;IAChD,8BAA8B;IAE9B,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,2BAA2B,CAAC;IACrC,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,EAAE,CAAC;IACpB,MAAM,QAAQ,GAAG,GAAG,CAAC;IAErB,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC7B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,8DAA8D;IAC9D,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAErD,6DAA6D;IAC7D,MAAM,UAAU,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC9C,MAAM,eAAe,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,YAAY,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,eAAe,EAAE,YAAY,CAAC,CAAC;IAE5E,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;QACxB,kDAAkD;QAClD,MAAM,iBAAiB,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,eAAe,GAAG,CAAC,CAAC,CAAC;QACtE,MAAM,WAAW,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QAE1D,IAAI,WAAW,IAAI,QAAQ,EAAE,CAAC;YAC5B,OAAO,iBAAiB,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,0DAA0D;IAC1D,OAAO,SAAS,GAAG,KAAK,CAAC;AAC3B,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAC9B,KAOC,EACD,SAA8B,EAC9B,MAAc;IAEd,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/D,MAAM,WAAW,GAAG,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAE7C,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,WAAW;QACX,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,WAAW;QACX,MAAM;QACN,kBAAkB,EAAE,SAAS;QAC7B,WAAW,EAAE,MAAM;KACpB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,cAAc,CAC5B,eAAsC,EACtC,QAAqE;IAErE,8BAA8B;IAC9B,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,qCAAqC,eAAe,CAAC,MAAM,EAAE,CAC9D,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAkB;QAC9B,eAAe;QACf,QAAQ,EAAE;YACR,gBAAgB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC1C,YAAY,EAAE,eAAe,CAAC,MAAM;YACpC,eAAe,EAAE,QAAQ,CAAC,eAAe;SAC1C;KACF,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { MovieAgent } from './agent';
2
+ export { MovieAgentFactory, MovieAgentConfig } from './factory';
3
+ export { UserInput, AgentResponse, MovieRecommendation, StreamingPlatform, ErrorResponse, } from './types';
4
+ export { default as TmdbApiClient } from './tmdbApi';
5
+ export { MovieAgentFactory as default } from './factory';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGrC,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGhE,OAAO,EACL,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,GACd,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,WAAW,CAAC;AAGrD,OAAO,EAAE,iBAAiB,IAAI,OAAO,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ // src/index.ts - Main entry point for the movie-agent package
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.default = exports.TmdbApiClient = exports.MovieAgentFactory = exports.MovieAgent = void 0;
8
+ // Export the main MovieAgent class
9
+ var agent_1 = require("./agent");
10
+ Object.defineProperty(exports, "MovieAgent", { enumerable: true, get: function () { return agent_1.MovieAgent; } });
11
+ // Export the factory for easy configuration
12
+ var factory_1 = require("./factory");
13
+ Object.defineProperty(exports, "MovieAgentFactory", { enumerable: true, get: function () { return factory_1.MovieAgentFactory; } });
14
+ // Export TMDb API client for advanced use cases
15
+ var tmdbApi_1 = require("./tmdbApi");
16
+ Object.defineProperty(exports, "TmdbApiClient", { enumerable: true, get: function () { return __importDefault(tmdbApi_1).default; } });
17
+ // Default export is the factory for convenience
18
+ var factory_2 = require("./factory");
19
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return factory_2.MovieAgentFactory; } });
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,8DAA8D;;;;;;AAE9D,mCAAmC;AACnC,iCAAqC;AAA5B,mGAAA,UAAU,OAAA;AAEnB,4CAA4C;AAC5C,qCAAgE;AAAvD,4GAAA,iBAAiB,OAAA;AAW1B,gDAAgD;AAChD,qCAAqD;AAA5C,yHAAA,OAAO,OAAiB;AAEjC,gDAAgD;AAChD,qCAAyD;AAAhD,kGAAA,iBAAiB,OAAW"}
package/dist/mood.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Maps a mood to an array of movie genres.
3
+ * @param mood - The mood to map.
4
+ * @returns Array of genres, or [] if unknown mood.
5
+ */
6
+ export declare function moodToGenres(mood: string): string[];
7
+ //# sourceMappingURL=mood.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mood.d.ts","sourceRoot":"","sources":["../src/mood.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAWnD"}
package/dist/mood.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.moodToGenres = moodToGenres;
4
+ /**
5
+ * Maps a mood to an array of movie genres.
6
+ * @param mood - The mood to map.
7
+ * @returns Array of genres, or [] if unknown mood.
8
+ */
9
+ function moodToGenres(mood) {
10
+ const mapping = {
11
+ happy: ['Comedy', 'Family', 'Musical'],
12
+ thoughtful: ['Drama', 'Documentary', 'Biography'],
13
+ excited: ['Action', 'Adventure', 'Thriller'],
14
+ relaxed: ['Romance', 'Comedy', 'Animation'],
15
+ scared: ['Horror', 'Thriller', 'Mystery'],
16
+ };
17
+ const genres = mapping[mood.toLowerCase()] || [];
18
+ // Ensure uniqueness
19
+ return Array.from(new Set(genres));
20
+ }
21
+ //# sourceMappingURL=mood.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mood.js","sourceRoot":"","sources":["../src/mood.ts"],"names":[],"mappings":";;AAKA,oCAWC;AAhBD;;;;GAIG;AACH,SAAgB,YAAY,CAAC,IAAY;IACvC,MAAM,OAAO,GAA6B;QACxC,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC;QACtC,UAAU,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,WAAW,CAAC;QACjD,OAAO,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,CAAC;QAC5C,OAAO,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC;QAC3C,MAAM,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC;KAC1C,CAAC;IACF,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IACjD,oBAAoB;IACpB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import TmdbApiClient from './tmdbApi';
2
+ /**
3
+ * Fetch and normalize Canadian watch providers for a movie.
4
+ * @param movieId TMDb movie ID
5
+ * @param region Region code (default: "CA")
6
+ * @param client Optional TmdbApiClient for testing
7
+ * @returns Array of normalized platform names
8
+ */
9
+ export declare function getCanadianProviders(movieId: number | string, region?: string, client?: TmdbApiClient): Promise<string[]>;
10
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AACA,OAAO,aAAa,MAAM,WAAW,CAAC;AAmBtC;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,MAAM,SAAO,EACb,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,EAAE,CAAC,CAwCnB"}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getCanadianProviders = getCanadianProviders;
7
+ // src/providers.ts
8
+ const tmdbApi_1 = __importDefault(require("./tmdbApi"));
9
+ const cache_1 = require("./cache");
10
+ const config_1 = __importDefault(require("./config"));
11
+ // Allowed platforms mapping (add more as needed)
12
+ const PLATFORM_MAP = {
13
+ Netflix: 'Netflix',
14
+ 'Amazon Prime Video': 'Prime Video',
15
+ 'Disney Plus': 'Disney+',
16
+ Crave: 'Crave',
17
+ 'Apple TV Plus': 'Apple TV+',
18
+ 'Paramount Plus': 'Paramount+',
19
+ 'CBC Gem': 'CBC Gem',
20
+ MUBI: 'MUBI',
21
+ hayu: 'hayu',
22
+ fuboTV: 'fuboTV',
23
+ // Add more mappings as needed
24
+ };
25
+ /**
26
+ * Fetch and normalize Canadian watch providers for a movie.
27
+ * @param movieId TMDb movie ID
28
+ * @param region Region code (default: "CA")
29
+ * @param client Optional TmdbApiClient for testing
30
+ * @returns Array of normalized platform names
31
+ */
32
+ async function getCanadianProviders(movieId, region = 'CA', client) {
33
+ try {
34
+ // Check cache first
35
+ const cache = (0, cache_1.getCache)(config_1.default.CACHE_TTL);
36
+ const cacheKey = (0, cache_1.generateProvidersCacheKey)(movieId, region);
37
+ const cachedResult = cache.get(cacheKey);
38
+ if (cachedResult) {
39
+ return cachedResult;
40
+ }
41
+ // Cache miss - fetch from API
42
+ const apiClient = client ?? new tmdbApi_1.default();
43
+ const data = await apiClient.getWatchProviders(Number(movieId));
44
+ if (!data || !data.results || !data.results[region]) {
45
+ // Cache empty result to avoid repeated API calls
46
+ cache.set(cacheKey, []);
47
+ return [];
48
+ }
49
+ const regionData = data.results[region];
50
+ // Only consider 'flatrate' (subscription) providers
51
+ const flatrate = regionData.flatrate || [];
52
+ if (!Array.isArray(flatrate) || flatrate.length === 0) {
53
+ cache.set(cacheKey, []);
54
+ return [];
55
+ }
56
+ // Map provider names to allowed platforms
57
+ const platforms = flatrate
58
+ .map((p) => PLATFORM_MAP[p.provider_name])
59
+ .filter(Boolean);
60
+ // Remove duplicates
61
+ const result = Array.from(new Set(platforms));
62
+ // Store in cache
63
+ cache.set(cacheKey, result);
64
+ return result;
65
+ }
66
+ catch {
67
+ return [];
68
+ }
69
+ }
70
+ //# sourceMappingURL=providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":";;;;;AA2BA,oDA4CC;AAvED,mBAAmB;AACnB,wDAAsC;AACtC,mCAA8D;AAC9D,sDAA8B;AAE9B,iDAAiD;AACjD,MAAM,YAAY,GAA2B;IAC3C,OAAO,EAAE,SAAS;IAClB,oBAAoB,EAAE,aAAa;IACnC,aAAa,EAAE,SAAS;IACxB,KAAK,EAAE,OAAO;IACd,eAAe,EAAE,WAAW;IAC5B,gBAAgB,EAAE,YAAY;IAC9B,SAAS,EAAE,SAAS;IACpB,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,8BAA8B;CAC/B,CAAC;AAEF;;;;;;GAMG;AACI,KAAK,UAAU,oBAAoB,CACxC,OAAwB,EACxB,MAAM,GAAG,IAAI,EACb,MAAsB;IAEtB,IAAI,CAAC;QACH,oBAAoB;QACpB,MAAM,KAAK,GAAG,IAAA,gBAAQ,EAAC,gBAAM,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,QAAQ,GAAG,IAAA,iCAAyB,EAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAW,QAAQ,CAAC,CAAC;QAEnD,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO,YAAY,CAAC;QACtB,CAAC;QAED,8BAA8B;QAC9B,MAAM,SAAS,GAAG,MAAM,IAAI,IAAI,iBAAa,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACpD,iDAAiD;YACjD,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACxB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxC,oDAAoD;QACpD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,IAAI,EAAE,CAAC;QAC3C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtD,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACxB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,0CAA0C;QAC1C,MAAM,SAAS,GAAG,QAAQ;aACvB,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;aAC9C,MAAM,CAAC,OAAO,CAAC,CAAC;QACnB,oBAAoB;QACpB,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QAE9C,iBAAiB;QACjB,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAE5B,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { MovieDetails } from './tmdbApi';
2
+ /**
3
+ * User input for ranking context
4
+ */
5
+ export interface RankingInput {
6
+ /** User's mood to match against genres */
7
+ mood?: string;
8
+ /** Explicit genre preferences */
9
+ genres?: string[];
10
+ /** User's available streaming platforms */
11
+ platforms?: string[];
12
+ /** Runtime constraints */
13
+ runtime?: {
14
+ min?: number;
15
+ max?: number;
16
+ };
17
+ /** Release year preferences */
18
+ year?: {
19
+ preferred?: number;
20
+ from?: number;
21
+ to?: number;
22
+ };
23
+ }
24
+ /**
25
+ * Extended movie type with platforms information
26
+ */
27
+ export interface RankableMovie extends MovieDetails {
28
+ platforms?: string[];
29
+ }
30
+ /**
31
+ * Scoring context derived from input
32
+ */
33
+ export interface ScoringContext {
34
+ targetGenres: string[];
35
+ userPlatforms: string[];
36
+ runtimeMin?: number;
37
+ runtimeMax?: number;
38
+ preferredYear?: number;
39
+ yearFrom?: number;
40
+ yearTo?: number;
41
+ }
42
+ /**
43
+ * Calculate overall score for a movie
44
+ * @param movie - Movie to score
45
+ * @param input - User input for ranking
46
+ * @param context - Optional pre-built scoring context
47
+ * @returns Overall score (0-100)
48
+ */
49
+ export declare function scoreMovie(movie: RankableMovie, input: RankingInput, context?: ScoringContext): number;
50
+ /**
51
+ * Rank movies by score
52
+ * @param movies - Array of movies to rank
53
+ * @param input - User input for ranking
54
+ * @returns Sorted array of movies (highest score first)
55
+ */
56
+ export declare function rankMovies(movies: RankableMovie[], input: RankingInput): RankableMovie[];
57
+ //# sourceMappingURL=ranking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ranking.d.ts","sourceRoot":"","sources":["../src/ranking.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,0BAA0B;IAC1B,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF,+BAA+B;IAC/B,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoMD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,YAAY,EACnB,OAAO,CAAC,EAAE,cAAc,GACvB,MAAM,CAkBR;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,aAAa,EAAE,EACvB,KAAK,EAAE,YAAY,GAClB,aAAa,EAAE,CAejB"}
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scoreMovie = scoreMovie;
4
+ exports.rankMovies = rankMovies;
5
+ const mood_1 = require("./mood");
6
+ /**
7
+ * Scoring weights for different criteria
8
+ */
9
+ const WEIGHTS = {
10
+ GENRE_MATCH: 40, // Mood/genre alignment
11
+ PLATFORM_MATCH: 30, // Platform availability
12
+ RUNTIME_MATCH: 15, // Runtime compatibility
13
+ YEAR_MATCH: 10, // Release year preference
14
+ POPULARITY: 5, // Popularity as tiebreaker
15
+ };
16
+ /**
17
+ * Build scoring context from user input
18
+ * @param input - User ranking input
19
+ * @returns Scoring context
20
+ */
21
+ function buildScoringContext(input) {
22
+ // Determine target genres from mood or explicit genres
23
+ let targetGenres = [];
24
+ if (input.genres && input.genres.length > 0) {
25
+ targetGenres = input.genres;
26
+ }
27
+ else if (input.mood) {
28
+ targetGenres = (0, mood_1.moodToGenres)(input.mood);
29
+ }
30
+ return {
31
+ targetGenres,
32
+ userPlatforms: input.platforms || [],
33
+ runtimeMin: input.runtime?.min,
34
+ runtimeMax: input.runtime?.max,
35
+ preferredYear: input.year?.preferred,
36
+ yearFrom: input.year?.from,
37
+ yearTo: input.year?.to,
38
+ };
39
+ }
40
+ /**
41
+ * Calculate genre match score (0-1)
42
+ * @param movie - Movie to score
43
+ * @param context - Scoring context
44
+ * @returns Score between 0 and 1
45
+ */
46
+ function scoreGenreMatch(movie, context) {
47
+ if (context.targetGenres.length === 0) {
48
+ return 0.5; // Neutral score if no genre preference
49
+ }
50
+ if (!movie.genres || movie.genres.length === 0) {
51
+ return 0;
52
+ }
53
+ const movieGenreNames = movie.genres.map(g => g.name);
54
+ const matchCount = context.targetGenres.filter(targetGenre => movieGenreNames.includes(targetGenre)).length;
55
+ // Score is the ratio of matched genres to target genres
56
+ return matchCount / context.targetGenres.length;
57
+ }
58
+ /**
59
+ * Calculate platform availability score (0-1)
60
+ * @param movie - Movie to score
61
+ * @param context - Scoring context
62
+ * @returns Score between 0 and 1
63
+ */
64
+ function scorePlatformMatch(movie, context) {
65
+ if (context.userPlatforms.length === 0) {
66
+ return 0.5; // Neutral score if no platform preference
67
+ }
68
+ if (!movie.platforms || movie.platforms.length === 0) {
69
+ return 0; // Not available on any platform
70
+ }
71
+ // Check if movie is available on any user platform
72
+ const isAvailable = movie.platforms.some(platform => context.userPlatforms.includes(platform));
73
+ return isAvailable ? 1 : 0;
74
+ }
75
+ /**
76
+ * Calculate runtime compatibility score (0-1)
77
+ * @param movie - Movie to score
78
+ * @param context - Scoring context
79
+ * @returns Score between 0 and 1
80
+ */
81
+ function scoreRuntimeMatch(movie, context) {
82
+ if (context.runtimeMin === undefined && context.runtimeMax === undefined) {
83
+ return 0.5; // Neutral score if no runtime preference
84
+ }
85
+ if (movie.runtime === undefined || movie.runtime === null) {
86
+ return 0; // Can't score without runtime info
87
+ }
88
+ const { runtimeMin, runtimeMax } = context;
89
+ // Perfect match if within range
90
+ if ((runtimeMin === undefined || movie.runtime >= runtimeMin) &&
91
+ (runtimeMax === undefined || movie.runtime <= runtimeMax)) {
92
+ return 1;
93
+ }
94
+ // Calculate how far outside the range
95
+ let distance = 0;
96
+ if (runtimeMin !== undefined && movie.runtime < runtimeMin) {
97
+ distance = runtimeMin - movie.runtime;
98
+ }
99
+ else if (runtimeMax !== undefined && movie.runtime > runtimeMax) {
100
+ distance = movie.runtime - runtimeMax;
101
+ }
102
+ // Penalize based on distance (exponential decay)
103
+ // Movies 30+ minutes outside range get very low scores
104
+ return Math.max(0, Math.exp(-distance / 30));
105
+ }
106
+ /**
107
+ * Calculate release year preference score (0-1)
108
+ * @param movie - Movie to score
109
+ * @param context - Scoring context
110
+ * @returns Score between 0 and 1
111
+ */
112
+ function scoreYearMatch(movie, context) {
113
+ if (context.preferredYear === undefined &&
114
+ context.yearFrom === undefined &&
115
+ context.yearTo === undefined) {
116
+ return 0.5; // Neutral score if no year preference
117
+ }
118
+ if (!movie.release_date) {
119
+ return 0; // Can't score without release date
120
+ }
121
+ const releaseYear = parseInt(movie.release_date.split('-')[0], 10);
122
+ if (isNaN(releaseYear)) {
123
+ return 0;
124
+ }
125
+ // Perfect match for preferred year
126
+ if (context.preferredYear !== undefined) {
127
+ if (releaseYear === context.preferredYear) {
128
+ return 1;
129
+ }
130
+ // Decay score based on distance from preferred year
131
+ const distance = Math.abs(releaseYear - context.preferredYear);
132
+ return Math.max(0, Math.exp(-distance / 5));
133
+ }
134
+ // Check if within acceptable range
135
+ if ((context.yearFrom === undefined || releaseYear >= context.yearFrom) &&
136
+ (context.yearTo === undefined || releaseYear <= context.yearTo)) {
137
+ return 1;
138
+ }
139
+ // Outside range
140
+ return 0;
141
+ }
142
+ /**
143
+ * Normalize popularity score (0-1)
144
+ * @param movie - Movie to score
145
+ * @returns Normalized score between 0 and 1
146
+ */
147
+ function scorePopularity(movie) {
148
+ if (movie.popularity === undefined || movie.popularity === null) {
149
+ return 0;
150
+ }
151
+ // TMDb popularity typically ranges from 0-1000+
152
+ // Use logarithmic scale to normalize
153
+ const maxPopularity = 1000;
154
+ const normalized = Math.min(movie.popularity, maxPopularity) / maxPopularity;
155
+ return normalized;
156
+ }
157
+ /**
158
+ * Calculate overall score for a movie
159
+ * @param movie - Movie to score
160
+ * @param input - User input for ranking
161
+ * @param context - Optional pre-built scoring context
162
+ * @returns Overall score (0-100)
163
+ */
164
+ function scoreMovie(movie, input, context) {
165
+ const ctx = context || buildScoringContext(input);
166
+ const genreScore = scoreGenreMatch(movie, ctx);
167
+ const platformScore = scorePlatformMatch(movie, ctx);
168
+ const runtimeScore = scoreRuntimeMatch(movie, ctx);
169
+ const yearScore = scoreYearMatch(movie, ctx);
170
+ const popularityScore = scorePopularity(movie);
171
+ // Calculate weighted total
172
+ const totalScore = genreScore * WEIGHTS.GENRE_MATCH +
173
+ platformScore * WEIGHTS.PLATFORM_MATCH +
174
+ runtimeScore * WEIGHTS.RUNTIME_MATCH +
175
+ yearScore * WEIGHTS.YEAR_MATCH +
176
+ popularityScore * WEIGHTS.POPULARITY;
177
+ return totalScore;
178
+ }
179
+ /**
180
+ * Rank movies by score
181
+ * @param movies - Array of movies to rank
182
+ * @param input - User input for ranking
183
+ * @returns Sorted array of movies (highest score first)
184
+ */
185
+ function rankMovies(movies, input) {
186
+ // Build context once for efficiency
187
+ const context = buildScoringContext(input);
188
+ // Score all movies
189
+ const scoredMovies = movies.map(movie => ({
190
+ movie,
191
+ score: scoreMovie(movie, input, context),
192
+ }));
193
+ // Sort by score (descending)
194
+ scoredMovies.sort((a, b) => b.score - a.score);
195
+ // Return sorted movies
196
+ return scoredMovies.map(item => item.movie);
197
+ }
198
+ //# sourceMappingURL=ranking.js.map