Skip to content
Open
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
57 changes: 36 additions & 21 deletions src/helpers/search.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,29 +42,44 @@ export const getSearchOrigins = (el: HTMLElement): string[] => {
return originsAll?.split('/').map((country) => country.trim());
};

export const parseSearchPeople = (
el: HTMLElement,
type: 'directors' | 'actors'
): CSFDMovieCreator[] => {
let who: Creator;
if (type === 'directors') who = 'Režie:';
if (type === 'actors') who = 'Hrají:';
export const getSearchCreators = (
el: HTMLElement
): { directors: CSFDMovieCreator[]; actors: CSFDMovieCreator[] } => {
const result = { directors: [] as CSFDMovieCreator[], actors: [] as CSFDMovieCreator[] };

const peopleNode = Array.from(el && el.querySelectorAll('.article-content p')).find((el) =>
el.textContent.includes(who)
);
const pNodes = el ? el.querySelectorAll('.article-content p') : [];

let foundDirectors = false;
let foundActors = false;

if (peopleNode) {
const people = Array.from(peopleNode.querySelectorAll('a')) as unknown as HTMLElement[];
for (const node of pNodes) {
if (foundDirectors && foundActors) break;

return people.map((person) => {
return {
id: parseIdFromUrl(person.attributes.href),
name: person.innerText.trim(),
url: `https://www.csfd.cz${person.attributes.href}`
};
});
} else {
return [];
const text = node.textContent;
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

Add optional chaining on textContent for robustness.

Per coding guidelines, helpers should never assume an element exists. If node.textContent returns null or undefined, calling .includes() will throw a TypeError.

🛡️ Proposed fix
-    const text = node.textContent;
-    if (!foundDirectors && text.includes('Režie:')) {
+    const text = node.textContent ?? '';
+    if (!foundDirectors && text.includes('Režie:')) {

As per coding guidelines: "Never assume an element exists. CSFD changes layouts. Use optional chaining ?. or try/catch inside helpers for robust scraping."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const text = node.textContent;
const text = node.textContent ?? '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/helpers/search.helper.ts` at line 58, The code assumes node.textContent
exists; change the assignment so node.textContent is accessed with optional
chaining and a string fallback (e.g., use node.textContent?.[something] or
coalesce to ''), ensuring the resulting variable "text" is always a string
before any .includes() or other string ops; update the assignment that currently
reads const text = node.textContent to use optional chaining/coalescing so
subsequent uses of text (e.g., .includes) cannot throw if the node is missing
content.

if (!foundDirectors && text.includes('Režie:')) {
result.directors = (Array.from(node.querySelectorAll('a')) as unknown as HTMLElement[]).map(
(person) => {
return {
id: parseIdFromUrl(person.attributes.href),
name: person.innerText.trim(),
url: `https://www.csfd.cz${person.attributes.href}`
};
Comment on lines +63 to +66
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how other helpers handle URL construction and if language-specific URLs are used
rg -n "getUrlByLanguage|https://www.csfd" --type ts src/

Repository: bartholomej/node-csfd-api

Length of output: 1726


🏁 Script executed:

cat -n src/services/search.service.ts | head -60

Repository: bartholomej/node-csfd-api

Length of output: 2759


🏁 Script executed:

# Check if there are any comments explaining the URL construction pattern
rg -B 3 -A 1 "https://www.csfd.cz" src/helpers/search.helper.ts

Repository: bartholomej/node-csfd-api

Length of output: 461


🏁 Script executed:

# Check the function signature of the search helper to see what parameters it receives
rg -B 5 "export.*parse" src/helpers/search.helper.ts | head -40

Repository: bartholomej/node-csfd-api

Length of output: 51


🏁 Script executed:

sed -n '40,80p' src/services/search.service.ts

Repository: bartholomej/node-csfd-api

Length of output: 1289


🏁 Script executed:

cat -n src/helpers/search.helper.ts

Repository: bartholomej/node-csfd-api

Length of output: 3647


🏁 Script executed:

cat -n src/helpers/search-creator.helper.ts

Repository: bartholomej/node-csfd-api

Length of output: 662


🏁 Script executed:

cat -n src/helpers/search-user.helper.ts

Repository: bartholomej/node-csfd-api

Length of output: 1076


🏁 Script executed:

# Check if getSearchCreators is called with language parameter anywhere
rg -B 3 -A 3 "getSearchCreators" src/

Repository: bartholomej/node-csfd-api

Length of output: 1386


Fix hardcoded base URL in creator URLs within search results.

The helper hardcodes https://www.csfd.cz for creator URLs on lines 65 and 76, but the service layer constructs other URLs using getUrlByLanguage(language) for language-aware domains. Top-level creator results (via creatorMapper) also follow the language-aware pattern by using relative paths + baseUrl.

This causes nested creators within movie search results to ignore the selected language. Either:

  1. Return relative paths instead of full URLs and let the service layer construct them with baseUrl
  2. Pass language parameter to getSearchCreators and use getUrlByLanguage(language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/helpers/search.helper.ts` around lines 63 - 66, The creator URL is
hardcoded to https://www.csfd.cz causing language drift; update the creator
mapping so nested creators return relative paths (use person.attributes.href and
keep id from parseIdFromUrl(person.attributes.href) and name from
person.innerText.trim()) instead of full URLs, letting the service layer (which
uses baseUrl/getUrlByLanguage(language) when building final URLs) compose the
language-aware domain; alternatively, if you prefer building full URLs here,
modify getSearchCreators to accept a language parameter and use
getUrlByLanguage(language) when constructing the full URL in the creatorMapper.

}
);
foundDirectors = true;
} else if (!foundActors && text.includes('Hrají:')) {
result.actors = (Array.from(node.querySelectorAll('a')) as unknown as HTMLElement[]).map(
(person) => {
return {
id: parseIdFromUrl(person.attributes.href),
name: person.innerText.trim(),
url: `https://www.csfd.cz${person.attributes.href}`
};
}
);
foundActors = true;
}
}

return result;
};
7 changes: 2 additions & 5 deletions src/services/search.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
getSearchType,
getSearchUrl,
getSearchYear,
parseSearchPeople
getSearchCreators
} from '../helpers/search.helper';
import { CSFDLanguage, CSFDOptions } from '../types';
import { getUrlByLanguage, searchUrl } from '../vars';
Expand Down Expand Up @@ -56,10 +56,7 @@ export class SearchScraper {
colorRating: getSearchColorRating(m),
poster: getSearchPoster(m),
origins: getSearchOrigins(m),
creators: {
directors: parseSearchPeople(m, 'directors'),
actors: parseSearchPeople(m, 'actors')
}
creators: getSearchCreators(m)
};
};

Expand Down
18 changes: 9 additions & 9 deletions tests/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
getSearchType,
getSearchUrl,
getSearchYear,
parseSearchPeople
getSearchCreators
} from '../src/helpers/search.helper';
import { searchMock } from './mocks/search.html';

Expand Down Expand Up @@ -147,7 +147,7 @@ describe('Get Movie origins', () => {

describe('Get Movie creators', () => {
test('First movie directors', () => {
const movie = parseSearchPeople(moviesNode[0], 'directors');
const movie = getSearchCreators(moviesNode[0]).directors;
expect(movie).toEqual<CSFDMovieCreator[]>([
{
id: 3112,
Expand All @@ -162,7 +162,7 @@ describe('Get Movie creators', () => {
]);
});
test('Last movie actors', () => {
const movie = parseSearchPeople(moviesNode[moviesNode.length - 1], 'actors');
const movie = getSearchCreators(moviesNode[moviesNode.length - 1]).actors;
expect(movie).toEqual<CSFDMovieCreator[]>([
{
id: 101,
Expand All @@ -177,7 +177,7 @@ describe('Get Movie creators', () => {
]);
});
// test('Empty actors', () => {
// const movie = parseSearchPeople(moviesNode[5], 'actors');
// const movie = getSearchCreators(moviesNode[5]).actors;
// expect(movie).toEqual<CSFDCreator[]>([]);
// });
});
Expand Down Expand Up @@ -295,7 +295,7 @@ describe('Get TV series origins', () => {

describe('Get TV series creators', () => {
test('First TV series directors', () => {
const movie = parseSearchPeople(tvSeriesNode[0], 'directors');
const movie = getSearchCreators(tvSeriesNode[0]).directors;
expect(movie).toEqual<CSFDMovieCreator[]>([
{
id: 8877,
Expand All @@ -310,7 +310,7 @@ describe('Get TV series creators', () => {
]);
});
test('Last TV series actors', () => {
const movie = parseSearchPeople(tvSeriesNode[tvSeriesNode.length - 1], 'actors');
const movie = getSearchCreators(tvSeriesNode[tvSeriesNode.length - 1]).actors;
expect(movie).toEqual<CSFDMovieCreator[]>([
{
id: 74751,
Expand All @@ -325,12 +325,12 @@ describe('Get TV series creators', () => {
]);
});
test('Empty directors', () => {
const movie = parseSearchPeople(tvSeriesNode[3], 'directors');
const movie = getSearchCreators(tvSeriesNode[3]).directors;
expect(movie).toEqual<CSFDMovieCreator[]>([]);
});
test('Empty directors + some actors', () => {
const movie = parseSearchPeople(tvSeriesNode[3], 'actors');
const movieDirectors = parseSearchPeople(tvSeriesNode[3], 'directors');
const movie = getSearchCreators(tvSeriesNode[3]).actors;
const movieDirectors = getSearchCreators(tvSeriesNode[3]).directors;
expect(movie).toEqual<CSFDMovieCreator[]>([
{
id: 61834,
Expand Down
Loading