Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 41 additions & 17 deletions src/helpers/movie.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,25 @@ import { addProtocol, getColor, parseISO8601Duration, parseIdFromUrl } from './g
*/
export const getLocalizedCreatorLabel = (
language: string | undefined,
key: 'directors' | 'writers' | 'cinematography' | 'music' | 'actors' | 'basedOn' | 'producers' | 'filmEditing' | 'costumeDesign' | 'productionDesign' | 'casting' | 'sound' | 'makeup'
key:
| 'directors'
| 'writers'
| 'cinematography'
| 'music'
| 'actors'
| 'basedOn'
| 'producers'
| 'filmEditing'
| 'costumeDesign'
| 'productionDesign'
| 'casting'
| 'sound'
| 'makeup'
): CSFDCreatorGroups | CSFDCreatorGroupsEnglish | CSFDCreatorGroupsSlovak => {
const labels: Record<string, Record<string, CSFDCreatorGroups | CSFDCreatorGroupsEnglish | CSFDCreatorGroupsSlovak>> = {
const labels: Record<
string,
Record<string, CSFDCreatorGroups | CSFDCreatorGroupsEnglish | CSFDCreatorGroupsSlovak>
> = {
en: {
directors: 'Directed by',
writers: 'Screenplay',
Expand Down Expand Up @@ -123,23 +139,23 @@ export const getMovieRatingCount = (el: HTMLElement): number => {
}
};

export const getMovieYear = (el: string): number => {
try {
const jsonLd = JSON.parse(el);
export const getMovieYear = (jsonLd: any): number => {
if (jsonLd && jsonLd.dateCreated) {
return +jsonLd.dateCreated;
} catch (error) {
console.error('node-csfd-api: Error parsing JSON-LD', error);
return null;
}
return null;
};
Comment on lines +142 to 147
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

getMovieYear can silently return NaN instead of null for non-numeric dateCreated.

If jsonLd.dateCreated is present but not a numeric string (e.g., "2018-05-01"), the unary + on line 144 will produce NaN, which propagates as a valid return rather than falling back to null. Consider adding a guard:

🛡️ Proposed fix
 export const getMovieYear = (jsonLd: any): number => {
   if (jsonLd && jsonLd.dateCreated) {
-    return +jsonLd.dateCreated;
+    const year = +jsonLd.dateCreated;
+    return Number.isNaN(year) ? null : year;
   }
   return null;
 };
🤖 Prompt for AI Agents
In `@src/helpers/movie.helper.ts` around lines 142 - 147, getMovieYear currently
does +jsonLd.dateCreated which yields NaN for non-numeric strings; update
getMovieYear to validate and return null for invalid values: if
jsonLd.dateCreated is a finite number return it, if it's a string try to extract
a year (e.g., match a 4-digit year or parse Date and use getFullYear) and return
that as a number, otherwise return null; reference getMovieYear and
jsonLd.dateCreated when making the guards and conversion.


export const getMovieDuration = (jsonLdRaw: string, el: HTMLElement): number => {
let duration = null;
export const getMovieDuration = (jsonLd: any, el: HTMLElement): number => {
try {
if (jsonLd && jsonLd.duration) {
return parseISO8601Duration(jsonLd.duration);
}
} catch (e) {
// ignore
}

try {
const jsonLd = JSON.parse(jsonLdRaw);
duration = jsonLd.duration;
return parseISO8601Duration(duration);
} catch (error) {
const origin = el.querySelector('.origin').innerText;
const timeString = origin.split(',');
if (timeString.length > 2) {
Expand All @@ -151,11 +167,13 @@ export const getMovieDuration = (jsonLdRaw: string, el: HTMLElement): number =>
const hoursMinsRaw = timeRaw.split('min')[0];
const hoursMins = hoursMinsRaw.split('h');
// Resolve hours + minutes format
duration = hoursMins.length > 1 ? +hoursMins[0] * 60 + +hoursMins[1] : +hoursMins[0];
const duration = hoursMins.length > 1 ? +hoursMins[0] * 60 + +hoursMins[1] : +hoursMins[0];
return duration;
} else {
return null;
}
} catch (e) {
return null;
}
};

Expand Down Expand Up @@ -241,7 +259,10 @@ const parseMoviePeople = (el: HTMLElement): CSFDMovieCreator[] => {
);
};

export const getMovieGroup = (el: HTMLElement, group: CSFDCreatorGroups | CSFDCreatorGroupsEnglish | CSFDCreatorGroupsSlovak): CSFDMovieCreator[] => {
export const getMovieGroup = (
el: HTMLElement,
group: CSFDCreatorGroups | CSFDCreatorGroupsEnglish | CSFDCreatorGroupsSlovak
): CSFDMovieCreator[] => {
const creators = el.querySelectorAll('.creators h4');
const element = creators.filter((elem) => elem.textContent.trim().includes(group))[0];
if (element?.parentNode) {
Expand Down Expand Up @@ -278,7 +299,10 @@ const getBoxContent = (el: HTMLElement, box: string): HTMLElement => {
?.parentNode;
};

export const getMovieBoxMovies = (el: HTMLElement, boxName: CSFDBoxContent): CSFDMovieListItem[] => {
export const getMovieBoxMovies = (
el: HTMLElement,
boxName: CSFDBoxContent
): CSFDMovieListItem[] => {
const movieListItem: CSFDMovieListItem[] = [];
const box = getBoxContent(el, boxName);
const movieTitleNodes = box?.querySelectorAll('.article-header .film-title-name');
Expand Down
25 changes: 20 additions & 5 deletions src/services/movie.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ export class MovieScraper {
const pageClasses = movieHtml.querySelector('.page-content').classNames.split(' ');
const asideNode = movieHtml.querySelector('.aside-movie-profile');
const movieNode = movieHtml.querySelector('.main-movie-profile');
const jsonLd = movieHtml.querySelector('script[type="application/ld+json"]').innerText;
const jsonLdString = movieHtml.querySelector('script[type="application/ld+json"]')?.innerText;
let jsonLd: any = null;
try {
jsonLd = JSON.parse(jsonLdString);
} catch (e) {
console.error('node-csfd-api: Error parsing JSON-LD', e);
}
return this.buildMovie(+movieId, movieNode, asideNode, pageClasses, jsonLd, options);
}

Expand All @@ -50,7 +56,7 @@ export class MovieScraper {
el: HTMLElement,
asideEl: HTMLElement,
pageClasses: string[],
jsonLd: string,
jsonLd: any,
options: CSFDOptions
): CSFDMovie {
return {
Expand All @@ -73,14 +79,23 @@ export class MovieScraper {
creators: {
directors: getMovieGroup(el, getLocalizedCreatorLabel(options?.language, 'directors')),
writers: getMovieGroup(el, getLocalizedCreatorLabel(options?.language, 'writers')),
cinematography: getMovieGroup(el, getLocalizedCreatorLabel(options?.language, 'cinematography')),
cinematography: getMovieGroup(
el,
getLocalizedCreatorLabel(options?.language, 'cinematography')
),
music: getMovieGroup(el, getLocalizedCreatorLabel(options?.language, 'music')),
actors: getMovieGroup(el, getLocalizedCreatorLabel(options?.language, 'actors')),
basedOn: getMovieGroup(el, getLocalizedCreatorLabel(options?.language, 'basedOn')),
producers: getMovieGroup(el, getLocalizedCreatorLabel(options?.language, 'producers')),
filmEditing: getMovieGroup(el, getLocalizedCreatorLabel(options?.language, 'filmEditing')),
costumeDesign: getMovieGroup(el, getLocalizedCreatorLabel(options?.language, 'costumeDesign')),
productionDesign: getMovieGroup(el, getLocalizedCreatorLabel(options?.language, 'productionDesign'))
costumeDesign: getMovieGroup(
el,
getLocalizedCreatorLabel(options?.language, 'costumeDesign')
),
productionDesign: getMovieGroup(
el,
getLocalizedCreatorLabel(options?.language, 'productionDesign')
)
},
vod: getMovieVods(asideEl),
tags: getMovieTags(asideEl),
Expand Down
19 changes: 10 additions & 9 deletions tests/movie.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@ const getNode = (node: HTMLElement): HTMLElement => {
return node.querySelector('.main-movie-profile') as HTMLElement;
};

const getJsonLd = (node: HTMLElement): string => {
return node.querySelector('script[type="application/ld+json"]')?.innerText ?? '{}';
const getJsonLd = (node: HTMLElement): any => {
const text = node.querySelector('script[type="application/ld+json"]')?.innerText;
return text ? JSON.parse(text) : null;
};

const getMovie = (
node: HTMLElement
): { pClasses: string[]; aside: HTMLElement; pNode: HTMLElement; jsonLd: string } => {
): { pClasses: string[]; aside: HTMLElement; pNode: HTMLElement; jsonLd: any } => {
return {
pClasses: getPageClasses(node),
aside: getAsideNode(node),
Expand Down Expand Up @@ -190,7 +191,7 @@ describe('Get Movie trivia', () => {
expect(movie).toEqual<string[]>([
'Celosvětová premiéra proběhla 3. září 2018 na Mezinárodním filmovém festivalu v Benátkách.(BMW12)',
'Natáčanie filmu prebiehalo v kanadskom meste Vancouver a začalo 17.7.2017.(MikaelSVK)',
'Ve filmu zazní píseň „A Better Place For Us“, kterou odehrál jako hudebník režisér S. Craig Zahler.(Rominator)',
'Ve filmu zazní píseň „A Better Place For Us“, kterou odehrál jako hudebník režisér S. Craig Zahler.(Rominator)'
]);
});
test('Movie Blank trivia', () => {
Expand All @@ -200,9 +201,9 @@ describe('Get Movie trivia', () => {
test('Movie Series trivia', () => {
const movie = getMovieTrivia(seriesNode);
expect(movie).toEqual<string[]>([
"Ernst-Hugo Järegård (Stig Helmer) se právě díky roli v seriálu v Dánsku výrazně zviditelnil a byl dokonce považován za nový sexuální symbol.(TomikZlesa)",
"Plánovanú 3. sériu narušila predčasná smrť niektorých hlavných hercov, ale po 25 rokoch predsa len vznikla.(misterz)",
"Když nastane stav beztíže, jako hudební podkres hraje Bachovo „Preludium F moll“. Tento hudební motiv, spolu se záběrem na vznášejícího se Pontopidana (Lars Mikkelsen), je jednoznačným odkazem na podobnou scénu se stavem beztíže z filmu Solaris (1972).(Kaleidoskop)",
'Ernst-Hugo Järegård (Stig Helmer) se právě díky roli v seriálu v Dánsku výrazně zviditelnil a byl dokonce považován za nový sexuální symbol.(TomikZlesa)',
'Plánovanú 3. sériu narušila predčasná smrť niektorých hlavných hercov, ale po 25 rokoch predsa len vznikla.(misterz)',
'Když nastane stav beztíže, jako hudební podkres hraje Bachovo „Preludium F moll“. Tento hudební motiv, spolu se záběrem na vznášejícího se Pontopidana (Lars Mikkelsen), je jednoznačným odkazem na podobnou scénu se stavem beztíže z filmu Solaris (1972).(Kaleidoskop)'
]);
});
test('Movie empty node', () => {
Expand Down Expand Up @@ -263,12 +264,12 @@ describe('Get VOD', () => {
expect(movie).toEqual<CSFDVod[]>([
{
title: 'Lepší.TV',
url: 'https://www.lepsi.tv/top_tv/serial/kralovstvi-cast-prvni-online?utm_source=csfd&utm_content=csfd',
url: 'https://www.lepsi.tv/top_tv/serial/kralovstvi-cast-prvni-online?utm_source=csfd&utm_content=csfd'
},
{
title: 'KVIFF.TV',
url: 'https://kviff.tv/katalog/kralovstvi-cast-druha-prijd-kralovstvi-tve'
},
}
]);
});
test('Get vods rich', () => {
Expand Down