1111import { AxiosRequestConfig } from 'axios' ;
1212import http from './httpClient' ;
1313import appConfig from '../config/app' ;
14+ import History from '../models/History' ;
1415import type { EpisodeInfo , SeasonDetail , SeriesDetail } from '../types/interfaces' ;
1516
1617/**
@@ -26,6 +27,8 @@ export const __clearCaches = (): void => {
2627 seriesCache . clear ( ) ;
2728} ;
2829
30+ const useMulti = Boolean ( appConfig . MULTI_DOMAIN ) ;
31+
2932/**
3033 * Constructs parameters object for OMDB API requests.
3134 * @param query - Search term for title search or IMDB ID for specific item lookup
@@ -51,7 +54,7 @@ const constructOmdbParams = (query: string, search: boolean, type: string): obje
5154 * For ID lookups: Detailed movie object or {Response: "False", Error: string}
5255 * @throws Will throw an error if the API request fails
5356 */
54- export const fetchOmdbData = async (
57+ const fetchOmdbData = async (
5558 query : string ,
5659 search = true ,
5760 type = ''
@@ -86,7 +89,7 @@ export const fetchOmdbData = async (
8689 * If poster isn't available or request fails, sets default 'no-binger' image.
8790 * Updates are performed in parallel using Promise.all
8891 */
89- export const fetchAndUpdatePosters = async ( show : any [ ] ) : Promise < void > => {
92+ const fetchAndUpdatePosters = async ( show : any [ ] ) : Promise < void > => {
9093 const fallback = `${ appConfig . APP_URL } /images/no-binger.jpg` ;
9194 await Promise . all (
9295 show . map ( async ( x : any ) => {
@@ -112,7 +115,7 @@ export const fetchAndUpdatePosters = async (show: any[]): Promise<void> => {
112115 * @param season - Season number to retrieve
113116 * @returns {Promise<SeriesDetail> } Object containing total seasons and cached season details
114117 */
115- export const getSeriesDetail = async ( id : string , season : number ) : Promise < SeriesDetail > => {
118+ const getSeriesDetail = async ( id : string , season : number ) : Promise < SeriesDetail > => {
116119 if ( ! id ) return { totalSeasons : 0 , currentSeason : { season : 0 , episodes : [ ] } } ;
117120
118121 const buildOptions = ( s : number ) : AxiosRequestConfig => ( {
@@ -171,6 +174,113 @@ export const getSeriesDetail = async (id: string, season: number): Promise<Serie
171174 } ;
172175} ;
173176
177+ /**
178+ * Builds canonical URL for a movie/series.
179+ * @param {string } appUrl - Base application URL
180+ * @param {string } id - IMDB ID of the movie/show
181+ * @param {string } type - Type of content ('movie' or 'series')
182+ * @param {string } [season] - Season number (for series)
183+ * @param {string } [episode] - Episode number (for series)
184+ * @returns {string } Canonical URL for the content
185+ */
186+ const buildCanonical = (
187+ appUrl : string ,
188+ id : string ,
189+ type : string ,
190+ season ?: string ,
191+ episode ?: string
192+ ) => {
193+ return season && episode
194+ ? `${ appUrl } /view/${ id } /${ type } /${ season } /${ episode } `
195+ : `${ appUrl } /view/${ id } /${ type } ` ;
196+ } ;
197+
198+ /**
199+ * Builds video source URLs for movie/series iframe embedding.
200+ * @param {string } id - IMDB ID of the movie/show
201+ * @param {'movie' | 'series' } kind - Type of content ('movie' or 'series')
202+ * @param {string } [season] - Season number (required for series)
203+ * @param {string } [episode] - Episode number (required for series)
204+ * @returns {Object } Object containing source URLs and current server
205+ */
206+ const buildSources = (
207+ id : string ,
208+ kind : 'movie' | 'series' ,
209+ season ?: string ,
210+ episode ?: string
211+ ) => {
212+ if ( kind === 'series' ) {
213+ const server1Src = `https://${ appConfig . VIDSRC_DOMAIN } /embed/tv?imdb=${ id } &season=${ season } &episode=${ episode } ` ;
214+ const server2Src = useMulti
215+ ? `https://${ appConfig . MULTI_DOMAIN } /?video_id=${ id } &s=${ season } &e=${ episode } `
216+ : '' ;
217+ const iframeSrc = useMulti ? server2Src : server1Src ;
218+ const currentServer = useMulti ? '2' : '1' ;
219+ return { server1Src, server2Src, iframeSrc, currentServer} ;
220+ }
221+
222+ const server1Src = `https://${ appConfig . VIDSRC_DOMAIN } /embed/movie/${ id } ` ;
223+ const server2Src = useMulti ? `https://${ appConfig . MULTI_DOMAIN } /?video_id=${ id } ` : '' ;
224+ const iframeSrc = useMulti ? server2Src : server1Src ;
225+ const currentServer = useMulti ? '2' : '1' ;
226+ return { server1Src, server2Src, iframeSrc, currentServer} ;
227+ } ;
228+
229+ /**
230+ * Gets resume redirect URL for a series based on last watched episode.
231+ * @param {string } userId - User ID
232+ * @param {string } imdbId - IMDB ID of the series
233+ * @returns {Promise<string|null> } Redirect URL if valid progress exists, null otherwise
234+ */
235+ const getResumeRedirect = async ( userId : string , imdbId : string ) : Promise < string | null > => {
236+ const history = await History . findOne ( { userId, imdbId} ) ;
237+ if ( ! history ) return null ;
238+
239+ const { lastSeason, lastEpisode} = history as any ;
240+ const valid =
241+ Number . isInteger ( lastSeason ) &&
242+ Number . isInteger ( lastEpisode ) &&
243+ lastSeason > 0 &&
244+ lastEpisode > 0 ;
245+
246+ return valid ? `/view/${ imdbId } /series/${ lastSeason } /${ lastEpisode } ` : null ;
247+ } ;
248+
249+ /**
250+ * Updates or creates a movie watched record.
251+ * @param {string } userId - User ID
252+ * @param {string } imdbId - IMDB ID of the movie
253+ * @returns {Promise<any> } The updated or created history record
254+ */
255+ const upsertMovieWatched = async ( userId : string , imdbId : string ) => {
256+ return History . findOneAndUpdate (
257+ { userId, imdbId} ,
258+ { $set : { type : 'movie' , watched : true } } ,
259+ { upsert : true , new : true }
260+ ) ;
261+ } ;
262+
263+ /**
264+ * Updates or creates a series progress record.
265+ * @param {string } userId - User ID
266+ * @param {string } imdbId - IMDB ID of the series
267+ * @param {string } season - Season number
268+ * @param {string } episode - Episode number
269+ * @returns {Promise<void> }
270+ */
271+ const upsertSeriesProgress = async (
272+ userId : string ,
273+ imdbId : string ,
274+ season : string ,
275+ episode : string
276+ ) => {
277+ await History . findOneAndUpdate (
278+ { userId, imdbId} ,
279+ { $set : { type : 'series' , lastSeason : Number ( season ) , lastEpisode : Number ( episode ) } } ,
280+ { upsert : true }
281+ ) ;
282+ } ;
283+
174284/**
175285 * Configuration flag indicating if authentication should be enabled.
176286 * @type {boolean }
@@ -182,4 +292,16 @@ export const getSeriesDetail = async (id: string, season: number): Promise<Serie
182292 * app.use(passport.session());
183293 * }
184294 */
185- export const useAuth = appConfig . MONGO_DB_URI !== '' ;
295+ const useAuth = appConfig . MONGO_DB_URI !== '' ;
296+
297+ export {
298+ buildCanonical ,
299+ buildSources ,
300+ fetchAndUpdatePosters ,
301+ fetchOmdbData ,
302+ getResumeRedirect ,
303+ getSeriesDetail ,
304+ upsertMovieWatched ,
305+ upsertSeriesProgress ,
306+ useAuth ,
307+ } ;
0 commit comments