Skip to content

Conversation

@bartholomej
Copy link
Owner

@bartholomej bartholomej commented Sep 15, 2025

What's new and why I made this pull request?

Resolves #43

I've prepared the first version of the parsing for series, seasons, and episodes. Ready for testing – feel free to join in!

Just use await csfd.movie(1431651) method and there are some brand new properties:

{
  seasons: CSFDSeason[] | null;
  episodes: CSFDEpisode[] | null;
  parent: CSFDParent | null;
  episodeCode: string | null;
  seasonName: string | null;
}

Pull request type

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Tests
  • Other

Summary by CodeRabbit

  • New Features

    • Better series support: explicit seasons, episodes, parent links and episode identifiers surfaced in movie data.
  • Updates

    • Improved parsing of series/season titles and list types; movie records now include season/episode lists, parent info, episode codes and season names; new HTML parsing helpers exposed.
  • Tests

    • Expanded test suites and added multiple comprehensive HTML mocks for series/season/episode scenarios.
  • Chore

    • Demo adjusted to target a different movie and creator fetch disabled.

✏️ Tip: You can customize this high-level summary in your review settings.

@deepsource-io
Copy link

deepsource-io bot commented Oct 13, 2025

Here's the code health analysis summary for commits 9a7b755..f6c21cc. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource JavaScript LogoJavaScript❌ Failure
❗ 2 occurences introduced
View Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

@coderabbitai
Copy link

coderabbitai bot commented Oct 13, 2025

📝 Walkthrough

Walkthrough

Adds DTO fields for series/season/episode metadata, a URL-id parser, new DOM-parsing helpers to detect/extract seasons, episodes, episode codes and parent info, integrates them into the movie service to populate new fields, updates tests and large HTML mocks, and tweaks demo invocation.

Changes

Cohort / File(s) Change Summary
DTO Type Extensions
src/dto/movie.ts
Added CSFDParent, CSFDSeason; extended CSFDMovie with seasons, episodes, parent, episodeCode, seasonName; removed CSFDCreatorGroupsEnglish.
Global Parsing Utilities
src/helpers/global.helper.ts
Added parseLastIdFromUrl(url: string): number (extracts numeric id from 4th path segment or returns null).
Movie Parsing Helpers
src/helpers/movie.helper.ts
Exported CSFDParent, CSFDSeason, added parseLastIdFromUrl import; added getSerieasAndSeasonTitle(), getSeasonsOrEpisodes(), getEpisodeCode(), detectSeasonOrEpisodeListType(), getSeasonorEpisodeParent(); signature formatting updates.
Service Integration
src/services/movie.service.ts
Integrated new helpers: compute series-aware title/type, detect season vs episode lists, populate seasons/episodes, parent, episodeCode, and seasonName; preserved existing fields.
Tests & Mocks
tests/*.test.ts, tests/mocks/*
Reworked series tests into pattern suites; added mocks tests/mocks/series1-season1.mock.ts, tests/mocks/series1-season1-episode.mock.ts, tests/mocks/series2-episode.mock.ts; updated tests/fetchers.test.ts, tests/movie.test.ts, tests/series.test.ts.
Demo
demo.ts
Changed demo movie ID from 10135 to 621073; commented out creator() invocation.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Client
    participant MovieService as MovieService
    participant MovieHelpers as MovieHelpers
    participant GlobalHelper as GlobalHelper
    participant DOM as HTMLElement

    Client->>MovieService: buildMovie(el: HTMLElement)
    MovieService->>MovieHelpers: getMovieType(el)
    MovieHelpers-->>MovieService: type

    alt type == "série"
        MovieService->>MovieHelpers: getSerieasAndSeasonTitle(el)
        MovieHelpers-->>MovieService: {seriesName, seasonName}
        MovieService->>MovieHelpers: detectSeasonOrEpisodeListType(el)
        MovieHelpers-->>MovieService: listType

        alt listType == "seasons" or "episodes"
            MovieService->>MovieHelpers: getSeasonsOrEpisodes(el, serie?)
            MovieHelpers->>GlobalHelper: parseLastIdFromUrl(url)
            GlobalHelper-->>MovieHelpers: id
            MovieHelpers-->>MovieService: CSFDSeason[]
            MovieService->>MovieService: set seasons/episodes
        end

        MovieService->>MovieHelpers: getSeasonorEpisodeParent(el, serie?)
        MovieHelpers-->>MovieService: CSFDParent | null
        MovieService->>MovieService: set parent
    end

    alt type == "epizoda"
        MovieService->>MovieHelpers: getEpisodeCode(el)
        MovieHelpers-->>MovieService: episodeCode
        MovieService->>MovieService: set episodeCode
    end

    MovieService-->>Client: CSFDMovie (includes new fields)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 I hopped through markup, nibbling tags so neat,
Found seasons and episodes hidden in the sheet.
IDs from the last slash, a breadcrumb so bright,
Parents and codes stitched into the night.
Demo hops onward — a small, curious delight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'TV series, seasons and episodes' clearly and concisely describes the main feature addition in the changeset.
Description check ✅ Passed The description clearly explains the changes, includes linked issue reference (#43), specifies new properties added, and properly marks feature and refactoring checkboxes.
Linked Issues check ✅ Passed All code changes successfully implement the requested feature to add scrapers for episodes and series, with new properties and helpers to parse series, seasons, and episodes [#43].
Out of Scope Changes check ✅ Passed All changes are directly related to implementing TV series, seasons, and episodes parsing functionality aligned with the linked issue #43; no out-of-scope changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link

codecov-commenter commented Oct 13, 2025

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.43%. Comparing base (eba372c) to head (20a5f51).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #60      +/-   ##
==========================================
- Coverage   99.52%   99.43%   -0.10%     
==========================================
  Files          28       33       +5     
  Lines         630      705      +75     
  Branches      143      175      +32     
==========================================
+ Hits          627      701      +74     
- Misses          3        4       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (3)
src/dto/movie.ts (1)

166-171: Clarify episode/season child type or keep CSFDSeason strict.

If you intend to reuse this for both seasons and episodes, consider:

  • Keep CSFDSeason strict (non-null fields) and ensure the parser filters out incomplete items (see helper fix).
  • Or add an alias for readability:
export type CSFDEpisode = CSFDSeason;

Confirm whether URLs stored here should be absolute; current helpers return relative links.

demo.ts (1)

11-11: Commented creator example is OK.

Consider leaving a short note why it’s disabled.

src/services/movie.service.ts (1)

6-8: Rename helper: getSerieasAndSeasonTitle → getSeriesAndSeasonTitle.

Fix the spelling in import and usage for clarity and future discoverability.

Apply:

-  getSerieasAndSeasonTitle
+  getSeriesAndSeasonTitle

and

-const { seriesName = null, seasonName = null } = type === 'série' ? getSerieasAndSeasonTitle(el) : {};
+const { seriesName = null, seasonName = null } = type === 'série' ? getSeriesAndSeasonTitle(el) : {};

Also applies to: 26-30, 61-66

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 983461b and da3c2fe.

📒 Files selected for processing (5)
  • demo.ts (1 hunks)
  • src/dto/movie.ts (3 hunks)
  • src/helpers/global.helper.ts (1 hunks)
  • src/helpers/movie.helper.ts (2 hunks)
  • src/services/movie.service.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/services/movie.service.ts (2)
src/helpers/movie.helper.ts (12)
  • getSerieasAndSeasonTitle (23-39)
  • detectSeasonOrEpisodeListType (231-237)
  • getMovieTitle (41-43)
  • getMovieYear (82-90)
  • getMovieDescriptions (178-182)
  • getMovieGroup (261-269)
  • getMovieVods (276-289)
  • getMovieTags (334-337)
  • getMovieBoxMovies (298-312)
  • getSeasonsOrEpisodes (200-218)
  • getSeasonorEpisodeParent (239-259)
  • getEpisodeCode (220-229)
src/dto/global.ts (1)
  • CSFDFilmTypes (25-36)
demo.ts (2)
src/index.ts (2)
  • csfd (51-57)
  • movie (28-30)
src/services/movie.service.ts (1)
  • movie (36-52)
src/helpers/movie.helper.ts (2)
src/helpers/global.helper.ts (2)
  • parseIdFromUrl (4-12)
  • parseLastIdFromUrl (14-22)
src/dto/movie.ts (2)
  • CSFDSeason (166-171)
  • CSFDParent (27-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (4)
src/dto/movie.ts (1)

20-25: New CSFDMovie fields look good; ensure DTOs match emitted data.

The additions are coherent. After fixing helper nullability (see other comments), this shape should be stable.

Please confirm tests cover population of: seasons, episodes, parent, episodeCode, seasonName.

demo.ts (1)

5-5: Demo change is fine.

No issues; keep demo noise out of CI runs if applicable.

src/helpers/movie.helper.ts (2)

220-229: Episode code extraction LGTM; consider unit test.

Regex-based extraction is fine. Add a simple test to lock behavior with/without parentheses.


231-237: List type detection LGTM; add fallback test.

Heuristic is simple and readable. Add tests for pages lacking the header to ensure null is returned.

Comment on lines 23 to 103
export const getSerieasAndSeasonTitle = (el: HTMLElement): { seriesName: string; seasonName: string | null } => {
const titleElement = el.querySelector('h1');
if (!titleElement) {
return { seriesName: null, seasonName: null };
}

const fullText = titleElement.innerText.trim();

// Check if there's a series part indicated by ' - '
if (fullText.includes(' - ')) {
const [seriesName, seasonName] = fullText.split(' - ').map(part => part.trim());
return { seriesName, seasonName };
}

// If no series part found, return just the name
return { seriesName: fullText, seasonName: null };
};
Copy link

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

Fix name and nullability: getSeriesAndSeasonTitle.

  • Typo in exported name.
  • Return type must allow nulls when

    is missing.

Apply:

-export const getSerieasAndSeasonTitle = (el: HTMLElement): { seriesName: string; seasonName: string | null } => {
+export const getSeriesAndSeasonTitle = (
+  el: HTMLElement
+): { seriesName: string | null; seasonName: string | null } => {
   const titleElement = el.querySelector('h1');
   if (!titleElement) {
-    return { seriesName: null, seasonName: null };
+    return { seriesName: null, seasonName: null };
   }
 
   const fullText = titleElement.innerText.trim();
 
   // Check if there's a series part indicated by ' - '
   if (fullText.includes(' - ')) {
     const [seriesName, seasonName] = fullText.split(' - ').map(part => part.trim());
     return { seriesName, seasonName };
   }
 
   // If no series part found, return just the name
-  return { seriesName: fullText, seasonName: null };
+  return { seriesName: fullText, seasonName: null };
 };

Remember to update all imports/usages (movie.service.ts).


🏁 Script executed:

#!/bin/bash
rg -n "getSerieasAndSeasonTitle|getSeriesAndSeasonTitle" --type=ts

Length of output: 344


🏁 Script executed:

#!/bin/bash
# Inspect the import and usage context in movie.service.ts
sed -n '25,35p' src/services/movie.service.ts
echo "---"
sed -n '58,68p' src/services/movie.service.ts

Length of output: 657


Fix name and nullability: getSeriesAndSeasonTitle.

  • Typo in exported name: getSerieasAndSeasonTitlegetSeriesAndSeasonTitle
  • Return type must allow nulls for seriesName (code returns null when <h1> is missing, but type declares string)

Apply:

-export const getSerieasAndSeasonTitle = (el: HTMLElement): { seriesName: string; seasonName: string | null } => {
+export const getSeriesAndSeasonTitle = (
+  el: HTMLElement
+): { seriesName: string | null; seasonName: string | null } => {
   const titleElement = el.querySelector('h1');
   if (!titleElement) {
     return { seriesName: null, seasonName: null };
   }
 
   const fullText = titleElement.innerText.trim();
 
   // Check if there's a series part indicated by ' - '
   if (fullText.includes(' - ')) {
     const [seriesName, seasonName] = fullText.split(' - ').map(part => part.trim());
     return { seriesName, seasonName };
   }
 
   // If no series part found, return just the name
   return { seriesName: fullText, seasonName: null };
 };

Update import in src/services/movie.service.ts line 29.

📝 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
export const getSerieasAndSeasonTitle = (el: HTMLElement): { seriesName: string; seasonName: string | null } => {
const titleElement = el.querySelector('h1');
if (!titleElement) {
return { seriesName: null, seasonName: null };
}
const fullText = titleElement.innerText.trim();
// Check if there's a series part indicated by ' - '
if (fullText.includes(' - ')) {
const [seriesName, seasonName] = fullText.split(' - ').map(part => part.trim());
return { seriesName, seasonName };
}
// If no series part found, return just the name
return { seriesName: fullText, seasonName: null };
};
export const getSeriesAndSeasonTitle = (
el: HTMLElement
): { seriesName: string | null; seasonName: string | null } => {
const titleElement = el.querySelector('h1');
if (!titleElement) {
return { seriesName: null, seasonName: null };
}
const fullText = titleElement.innerText.trim();
// Check if there's a series part indicated by ' - '
if (fullText.includes(' - ')) {
const [seriesName, seasonName] = fullText.split(' - ').map(part => part.trim());
return { seriesName, seasonName };
}
// If no series part found, return just the name
return { seriesName: fullText, seasonName: null };
};
🤖 Prompt for AI Agents
In src/helpers/movie.helper.ts around lines 23 to 39, rename the exported
function from getSerieasAndSeasonTitle to getSeriesAndSeasonTitle and change the
return type to { seriesName: string | null; seasonName: string | null } so the
function signature matches the code path that returns null when the <h1> is
missing; update any internal references accordingly. Then update the import in
src/services/movie.service.ts at line 29 to import getSeriesAndSeasonTitle
(instead of the misspelled name). Ensure the file exports the corrected
identifier and run a quick search to update any remaining call sites.

Comment on lines 239 to 323
export const getSeasonorEpisodeParent = (el: HTMLElement, serie?: { id: number; name: string; }): CSFDParent | null => {
const parents = el.querySelectorAll('.film-header h2 a');
if (parents.length === 0) {
if (!serie) return null;
return { series: serie, season: null };
}

const [parentSeries, parentSeason] = parents;

const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href'));
const seriesName = parentSeries?.textContent?.trim() || null;
const seasonName = parentSeason?.textContent?.trim() || null;

const series = seriesId && seriesName ? { id: seriesId, name: seriesName } : null;
const season = seasonId && seasonName ? { id: seasonId, name: seasonName } : null;

if (!series && !season) return null;

return { series, season };
}
Copy link

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

Avoid passing undefined to parseIdFromUrl and align with CSFDParent nullability.

  • parentSeason may be missing; ensure string fallback for href.
  • With CSFDParent updated to allow nulls, this function becomes type-safe.

Apply:

-  const [parentSeries, parentSeason] = parents;
+  const [parentSeries, parentSeason] = parents;
 
-  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
-  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href'));
+  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href') || '');
+  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href') || '');
   const seriesName = parentSeries?.textContent?.trim() || null;
   const seasonName = parentSeason?.textContent?.trim() || null;
 
   const series = seriesId && seriesName ? { id: seriesId, name: seriesName } : null;
   const season = seasonId && seasonName ? { id: seasonId, name: seasonName } : null;
 
   if (!series && !season) return null;
 
   return { series, season };

Add tests for: 0 parents (uses provided serie), 1 parent (series only), 2 parents (series + season).


🏁 Script executed:

#!/bin/bash
rg -n "getSeasonorEpisodeParent" --type=ts -C2

Length of output: 1191


Now let me search the codebase directly for the implementations needed to verify the review:

Let me search the codebase now:Now I need to check the codebase directly using shell scripts:

#!/bin/bash

# Find parseIdFromUrl function definition
echo "=== Searching for parseIdFromUrl definition ==="
rg -n "parseIdFromUrl" --type=ts -A 5 -B 2

echo ""
echo "=== Searching for CSFDParent type definition ==="
rg -n "CSFDParent" --type=ts -A 3 -B 2

echo ""
echo "=== Searching for test files related to getSeasonorEpisodeParent ==="
fd "\.test\.|\.spec\." --type=f | head -20

Based on my comprehensive verification, I need to note that:

  1. Optional chaining behavior: When a reference is null or undefined, the expression returns undefined instead of causing an error. The code uses parentSeason?.getAttribute('href'), which will return undefined if parentSeason is null/undefined.

  2. The concern in the review: The suggestion to add || '' assumes that parseIdFromUrl either:

    • Doesn't accept undefined as a parameter, OR
    • Doesn't handle it correctly
  3. The code pattern: The current code destructures from parents array which could have 0, 1, or 2 elements. When there's only 1 element, parentSeason will be undefined.

Without access to the actual codebase files through shell execution, and given the review comment's specific concerns about type safety and the request for tests, I'll provide a rewritten comment based on the general TypeScript best practices evident from the web search results:


Type-safe handling of optional element and improved test coverage needed.

The function currently relies on array destructuring that may assign undefined to parentSeason when fewer than 2 elements exist. This causes getAttribute('href') to return undefined, which is then passed to parseIdFromUrl.

Ensure parseIdFromUrl handles undefined input appropriately, or add explicit fallback:

  • parentSeason?.getAttribute('href') || '' provides a safer empty string fallback
  • This aligns with strict null checking requirements

Tests needed for edge cases:

  • 0 parents (uses provided serie parameter)
  • 1 parent (series only, season is undefined)
  • 2 parents (series + season)

#!/bin/bash

echo "=== Searching for parseIdFromUrl definition ==="
rg -n "parseIdFromUrl\s*=" --type=ts -A 10 | head -30

echo ""
echo "=== Searching for CSFDParent type definition ==="
rg -n "type CSFDParent|interface CSFDParent" --type=ts -A 5

echo ""
echo "=== Searching for test files ==="
find . -name "*.test.ts" -o -name "*.spec.ts" | grep -i "movie\|helper"

echo ""
echo "=== Checking if CSFDParent has been updated ==="
rg -n "CSFDParent" --type=ts | grep -v "import\|export"

🏁 Script executed:

#!/bin/bash

echo "=== Search for parseIdFromUrl implementation ==="
rg -n "const parseIdFromUrl|function parseIdFromUrl|export.*parseIdFromUrl" --type=ts -A 8

echo ""
echo "=== Search for CSFDParent type definition ==="
rg -n "type CSFDParent|interface CSFDParent" --type=ts -A 6

echo ""
echo "=== Search for test files containing getSeasonorEpisodeParent ==="
rg -n "getSeasonorEpisodeParent" --type=ts -l | grep -E "\.(test|spec)\.ts$"

echo ""
echo "=== Check all files importing or using parseIdFromUrl ==="
rg -n "parseIdFromUrl" --type=ts -B 3 -A 1

Length of output: 8474


Add string fallback for optional chaining and expand test coverage.

The issue is valid: parentSeason?.getAttribute('href') returns undefined when parentSeason is null/undefined, and parseIdFromUrl expects a string type parameter (line 4, src/helpers/global.helper.ts). While the function handles falsy values gracefully, TypeScript will flag this as a type error in strict mode.

Apply the suggested fix:

-  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
-  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href'));
+  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href') || '');
+  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href') || '');

This ensures a string is always passed. Empty string will be falsy in parseIdFromUrl and safely return null.

Also add tests for edge cases: 0 parents (uses provided serie), 1 parent (series only), 2 parents (series + season). No existing tests found for getSeasonorEpisodeParent.

🤖 Prompt for AI Agents
In src/helpers/movie.helper.ts around lines 239 to 259, the calls
parentSeries?.getAttribute('href') and parentSeason?.getAttribute('href') can be
undefined and TypeScript strict mode flags that parseIdFromUrl expects a string;
change those calls to provide a string fallback (e.g.
parentSeries?.getAttribute('href') || '') so parseIdFromUrl always receives a
string and still returns null for falsy input, and add unit tests covering the
three edge cases: 0 parents (passes through provided serie), 1 parent (series
only), and 2 parents (series + season) to validate outputs.

Comment on lines +99 to 105
similar: getMovieBoxMovies(asideEl, 'Podobné'),
seasons: seasonOrEpisodeListType === 'seasons' ? getSeasonsOrEpisodes(el) : null,
episodes: seasonOrEpisodeListType === 'episodes' ? getSeasonsOrEpisodes(el) : null,
parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }),
episodeCode: type === 'epizoda' ? getEpisodeCode(el) : null,
seasonName,
};
Copy link

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

Parent/seasons/episodes population: verify nullability contract and absolute URLs.

  • After adjusting CSFDParent to allow nullable members, this assignment is fine.
  • Ensure getSeasonsOrEpisodes returns items with non-null id/name/url and absolute URLs to avoid downstream consumers breaking.

To verify field presence:


🏁 Script executed:

#!/bin/bash
rg -nC2 "seasons:|episodes:|parent:|episodeCode:|seasonName" src --type=ts

Length of output: 3403



---

🏁 Script executed:

```shell
#!/bin/bash
# Find CSFDParent type definition and getSeasonsOrEpisodes return type
rg -nC5 "type CSFDParent|interface CSFDParent|getSeasonsOrEpisodes.*return|export.*getSeasonsOrEpisodes" src --type=ts

Length of output: 1168


🏁 Script executed:

#!/bin/bash
# Find CSFDSeason type definition and related types
rg -nC3 "type CSFDSeason|interface CSFDSeason" src --type=ts

Length of output: 279


🏁 Script executed:

#!/bin/bash
# Search for getSeasonorEpisodeParent implementation details
rg -nC8 "getSeasonorEpisodeParent\s*=" src --type=ts

Length of output: 1162


🏁 Script executed:

#!/bin/bash
# Search for getSeasonsOrEpisodes implementation
rg -nC8 "getSeasonsOrEpisodes\s*=" src --type=ts

Length of output: 1169



---

🏁 Script executed:

```shell
#!/bin/bash
# Get complete getSeasonorEpisodeParent implementation
rg -nA20 "export const getSeasonorEpisodeParent" src/helpers/movie.helper.ts

Length of output: 934


🏁 Script executed:

#!/bin/bash
# Get complete getSeasonsOrEpisodes implementation including URL handling
rg -nA25 "export const getSeasonsOrEpisodes" src/helpers/movie.helper.ts

Length of output: 1166


🏁 Script executed:

#!/bin/bash
# Check for any recent changes to CSFDParent or related code
cat src/dto/movie.ts | head -40

Length of output: 919


Critical type contract violations in parent/seasons/episodes population require fixes.

The original review comment identified valid concerns that remain unaddressed:

  1. CSFDParent type contract violated: The getSeasonorEpisodeParent function (line 243) returns { series: serie, season: null }, and line 258 can return objects where both season and series are null, but the CSFDParent interface requires both as non-nullable { id: number; name: string; } objects.

  2. getSeasonsOrEpisodes returns nullable fields: The function (lines 213-214) assigns null to name and url fields via the || null fallback, violating the CSFDSeason interface contract which requires both fields as non-nullable strings.

  3. URLs are relative, not absolute: Line 214 returns getAttribute('href') which produces relative URLs (e.g., /film/...), not absolute URLs (e.g., https://www.csfd.cz/film/...), causing downstream consumers to fail.

Requires action:

  • Make CSFDParent.season and CSFDParent.series optional/nullable in the type definition
  • Ensure getSeasonsOrEpisodes provides non-null name/url or handle null cases
  • Prepend https://www.csfd.cz to relative URLs in getSeasonsOrEpisodes and getSeasonorEpisodeParent
🤖 Prompt for AI Agents
In src/services/movie.service.ts around lines 99-105, the
parent/seasons/episodes population violates the TypeScript contracts and returns
relative/nullable fields; update the CSFDParent type so season and series are
optional or nullable, modify getSeasonsOrEpisodes to never return null for
name/url (e.g., coerce empty strings or filter out items with missing values)
and ensure you build absolute URLs by prepending "https://www.csfd.cz" to any
relative href returned by getAttribute('href'), and update
getSeasonorEpisodeParent to return values that match the updated CSFDParent
shape (use nullable/optional fields and construct absolute URLs for any hrefs it
returns).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
src/helpers/movie.helper.ts (3)

23-39: Fix typo and nullability in getSeriesAndSeasonTitle; update imports/usages.

Rename function and allow seriesName to be null when <h1> is missing.

Apply:

-export const getSerieasAndSeasonTitle = (el: HTMLElement): { seriesName: string; seasonName: string | null } => {
+export const getSeriesAndSeasonTitle = (
+  el: HTMLElement
+): { seriesName: string | null; seasonName: string | null } => {
   const titleElement = el.querySelector('h1');
   if (!titleElement) {
     return { seriesName: null, seasonName: null };
   }
 
   const fullText = titleElement.innerText.trim();
 
   // Check if there's a series part indicated by ' - '
   if (fullText.includes(' - ')) {
     const [seriesName, seasonName] = fullText.split(' - ').map(part => part.trim());
     return { seriesName, seasonName };
   }
 
   // If no series part found, return just the name
   return { seriesName: fullText, seasonName: null };
 };

Remember to update all imports/usages in src/services/movie.service.ts.


200-218: Ensure non-null id/name/url and build absolute URLs in getSeasonsOrEpisodes.

Current mapper can emit nulls and relative URLs; downstream consumers will break.

Apply:

-export const getSeasonsOrEpisodes = (el: HTMLElement, serie?: { id: number; title: string; }): CSFDSeason[] | null => {
+export const getSeasonsOrEpisodes = (
+  el: HTMLElement,
+  _serie?: { id: number; title: string }
+): CSFDSeason[] | null => {
   const childrenList = el.querySelector('.film-episodes-list');
   if (!childrenList) return null;
 
-  const childrenNodes = childrenList.querySelectorAll('.film-title');
-  if (!childrenNodes?.length) return [];
-
-  return childrenNodes.map((season) => {
-    const nameContainer = season.querySelector('.film-title-name');
-    const infoContainer = season.querySelector('.info');
-
-    return {
-      id: parseLastIdFromUrl(nameContainer?.getAttribute('href') || ''),
-      name: nameContainer?.textContent?.trim() || null,
-      url: nameContainer?.getAttribute('href') || null,
-      info: infoContainer?.textContent?.replace(/[{()}]/g, '').trim() || null,
-    };
-  });
+  const childrenNodes = childrenList.querySelectorAll('.film-title');
+  if (!childrenNodes?.length) return [];
+
+  const items: CSFDSeason[] = [];
+  for (const node of childrenNodes) {
+    const nameEl = node.querySelector('.film-title-name');
+    const infoEl = node.querySelector('.info');
+    const href = nameEl?.getAttribute('href') || '';
+    const id = parseLastIdFromUrl(href);
+    const name = nameEl?.textContent?.trim();
+    if (!id || !name || !href) continue;
+    items.push({
+      id,
+      name,
+      url: `https://www.csfd.cz${href}`,
+      info: infoEl?.textContent?.replace(/[{()}]/g, '').trim() || null,
+    });
+  }
+  return items;
 }

Note: _serie is currently unused; either remove the param or pass it from the caller for future enrichment.


239-259: Do not pass undefined to parseIdFromUrl; confirm CSFDParent nullability. Also, consider renaming to getSeasonOrEpisodeParent.

Provide string fallbacks for hrefs; result remains unchanged functionally. If CSFDParent permits nullable members, this is type-safe; otherwise, update the DTO.

Apply:

   const [parentSeries, parentSeason] = parents;
 
-  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
-  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href'));
+  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href') || '');
+  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href') || '');
   const seriesName = parentSeries?.textContent?.trim() || null;
   const seasonName = parentSeason?.textContent?.trim() || null;
 
   const series = seriesId && seriesName ? { id: seriesId, name: seriesName } : null;
   const season = seasonId && seasonName ? { id: seasonId, name: seasonName } : null;
 
   if (!series && !season) return null;
 
   return { series, season };

Optional: rename the function to getSeasonOrEpisodeParent for readability and update imports.

src/services/movie.service.ts (1)

6-8: Rename import and usage to getSeriesAndSeasonTitle; avoid unsafe type assertion.

Fix the helper name everywhere; consider avoiding as CSFDFilmTypes if getMovieType may return other values.

Apply:

 import {
   detectSeasonOrEpisodeListType,
   getEpisodeCode,
@@
-  getSeasonorEpisodeParent,
-  getSeasonsOrEpisodes,
-  getSerieasAndSeasonTitle
+  getSeasonorEpisodeParent,
+  getSeasonsOrEpisodes,
+  getSeriesAndSeasonTitle
 } from '../helpers/movie.helper';
@@
-    const type = getMovieType(el) as CSFDFilmTypes;
-    const { seriesName = null, seasonName = null } = type === 'série' ? getSerieasAndSeasonTitle(el) : {};
+    const type = getMovieType(el) as CSFDFilmTypes;
+    const { seriesName = null, seasonName = null } = type === 'série' ? getSeriesAndSeasonTitle(el) : {};

Optional: instead of an assertion, narrow type by comparing the extracted text to your union of known values.

Also applies to: 27-30, 61-66

🧹 Nitpick comments (3)
demo.ts (1)

5-5: Use an ID that showcases series/seasons to exercise new fields.

Align the demo with the PR description (1431651) or another known series/season/episode ID so the new properties are visible when running the demo.

Apply:

-csfd.movie(621073).then((movie) => console.log(movie));
+csfd.movie(1431651).then((movie) => console.log(movie));
src/helpers/movie.helper.ts (2)

220-229: Avoid capturing the year as episode code.

The parentheses match can grab (2018); exclude years.

Apply:

-  const match = text.match(/\(([^)]+)\)/);
+  const match = text.match(/\((?!(?:19|20)\d{2}\b)([^)]+)\)/);

Add tests for typical formats: S01E03, 1/6, #1.1, and year-only titles.


231-237: Make list-type detection robust to case/spacing.

Normalize text to lower-case; keep logic unchanged.

Apply:

-  const headerText = el.querySelector('.box-header h3')?.innerText.trim() ?? '';
+  const headerText = el.querySelector('.box-header h3')?.innerText?.trim().toLowerCase() ?? '';
-  if (headerText.includes('Série')) return 'seasons';
-  if (headerText.startsWith('Epizody')) return 'episodes';
+  if (headerText.includes('série')) return 'seasons';
+  if (headerText.startsWith('epizody')) return 'episodes';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da3c2fe and 2039636.

📒 Files selected for processing (5)
  • demo.ts (1 hunks)
  • src/dto/movie.ts (3 hunks)
  • src/helpers/global.helper.ts (1 hunks)
  • src/helpers/movie.helper.ts (2 hunks)
  • src/services/movie.service.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/dto/movie.ts
  • src/helpers/global.helper.ts
🔇 Additional comments (3)
demo.ts (1)

11-11: Commenting out creator call is fine for a focused demo.

Keeps output concise while testing series/seasons/episodes.

src/services/movie.service.ts (2)

102-105: Parent and seasonName wiring look good given helper fixes.

Once the helper naming/URL/nullability fixes land, these fields should resolve correctly.

Ensure CSFDParent allows nullable series/season as returned by getSeasonorEpisodeParent.


100-105: Episodes assignment likely mismatched with DTO type; pass series context and ensure mapper type aligns.

getSeasonsOrEpisodes returns CSFDSeason[], but episodes typically expects CSFDEpisode[]. Either make a dedicated episodes mapper or unify the item type. Also, pass the series context if you intend to use it.

Apply:

-      seasons: seasonOrEpisodeListType === 'seasons' ? getSeasonsOrEpisodes(el) : null,
-      episodes: seasonOrEpisodeListType === 'episodes' ? getSeasonsOrEpisodes(el) : null,
+      // provide context; adjust mapper/DTO if episodes use a distinct type
+      seasons: seasonOrEpisodeListType === 'seasons' ? getSeasonsOrEpisodes(el, { id: movieId, title }) : null,
+      episodes: seasonOrEpisodeListType === 'episodes' ? getSeasonsOrEpisodes(el, { id: movieId, title }) : null,

Run to verify DTO expectations:

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/dto/movie.ts (1)

27-30: CSFDParent fields must be nullable.

getSeasonorEpisodeParent can produce missing season/series; the current non-null types are incorrect.

Apply:

-export interface CSFDParent {
-  season: { id: number; name: string; };
-  series: { id: number; name: string; };
-}
+/**
+ * Parent relationship for seasons/episodes.
+ * At least one of season/series is non-null at runtime.
+ */
+export interface CSFDParent {
+  season: { id: number; name: string } | null;
+  series: { id: number; name: string } | null;
+}
🧹 Nitpick comments (2)
src/dto/movie.ts (1)

20-25: Episodes typed as CSFDSeason[]: define a dedicated CSFDEpisode and use it.

Episodes are not seasons; align the public API with the PR description and future parsing needs.

Apply:

 export interface CSFDMovie extends CSFDScreening {
@@
-  episodes: CSFDSeason[] | null;
+  episodes: CSFDEpisode[] | null;
@@
 }
 
+/**
+ * Episode metadata parsed from CSFD.
+ */
+export interface CSFDEpisode {
+  id: number;
+  name: string;
+  url: string;      // absolute
+  info: string | null;
+  episodeCode?: string | null;
+}
src/services/movie.service.ts (1)

103-104: episodeCode/seasonName population: OK, but ensure DTO alignment.

These fields match the DTO. Once CSFDEpisode is introduced, consider moving episodeCode into the per‑episode shape to avoid top-level sparsity.

I can open a follow‑up PR to adjust types and helper returns once you confirm the desired shapes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2039636 and 8ed4dae.

📒 Files selected for processing (5)
  • demo.ts (1 hunks)
  • src/dto/movie.ts (3 hunks)
  • src/helpers/global.helper.ts (1 hunks)
  • src/helpers/movie.helper.ts (2 hunks)
  • src/services/movie.service.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/helpers/global.helper.ts
  • demo.ts
  • src/helpers/movie.helper.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/services/movie.service.ts (2)
src/helpers/movie.helper.ts (8)
  • getMovieType (271-274)
  • getSerieasAndSeasonTitle (23-39)
  • detectSeasonOrEpisodeListType (231-237)
  • getMovieTitle (41-43)
  • getMovieYear (82-90)
  • getSeasonsOrEpisodes (200-218)
  • getSeasonorEpisodeParent (239-259)
  • getEpisodeCode (220-229)
src/dto/global.ts (1)
  • CSFDFilmTypes (25-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (2)
src/services/movie.service.ts (2)

65-69: Title resolution looks good.

Preferring seriesName for 'série' pages is appropriate.


61-66: Fix helper function name typo; 'série' and 'seriál' are distinct types.

getSerieasAndSeasonTitle is misspelled (should be getSeriesAndSeasonTitle). However, 'série' and 'seriál' are separate valid types in CSFDFilmTypes and are intentionally used in different contexts—no normalization needed.

Comment on lines +102 to 105
parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }),
episodeCode: type === 'epizoda' ? getEpisodeCode(el) : null,
seasonName,
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Parent computation bug: incorrect fallback sets a parent for films.

parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title })

  • For non‑series films, this passes the current movie as a “series” fallback, yielding a bogus parent.
  • Compute parent only for seasons/episodes and avoid passing the fallback unless you truly know the series context.

Apply:

-      parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }),
+      // Only derive parent on episode/season pages; no fallback to current movieId for films
+      parent:
+        (type === 'epizoda' || type === 'série')
+          ? getSeasonorEpisodeParent(el)
+          : null,

Optionally, if you must supply a series fallback, pass it only when you have strong evidence of the series context (e.g., header breadcrumbs parsed).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/services/movie.service.ts around lines 102-105, the parent field
currently computes a fallback parent for non-series films causing incorrect
parent assignments; change the logic to compute parent only when the item is a
season or episode (i.e., when type indicates 'seriál' or 'epizoda' as
appropriate) and do not call getSeasonorEpisodeParent for films. If you must
provide a series fallback, only pass a fallback object when you have explicit
series context (e.g., header/breadcrumbs); otherwise set parent to null for
non-series entries.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (6)
src/helpers/global.helper.ts (1)

14-22: Type signature and zero-coercion issues persist.

The past review correctly identified these critical issues, but they remain unfixed in the current code:

  1. Type mismatch: Function declares return type number but executes return null (lines 18, 20), which will fail under strictNullChecks.
  2. Zero-coercion bug: Pattern +id || null (line 18) incorrectly coerces the valid ID 0 to null, losing data.

Apply the previously suggested fix:

-export const parseLastIdFromUrl = (url: string): number => {
+export const parseLastIdFromUrl = (url: string): number | null => {
   if (url) {
-    const idSlug = url?.split('/')[3];
-    const id = idSlug?.split('-')[0];
-    return +id || null;
+    const idSlug = url.split('/')[3];
+    const num = Number((idSlug ?? '').split('-')[0]);
+    return Number.isFinite(num) ? num : null;
   } else {
     return null;
   }
 };

Also apply the same pattern to parseIdFromUrl (lines 4-12) for consistency.

src/dto/movie.ts (2)

27-30: Make CSFDParent members nullable to match implementation.

The implementation getSeasonorEpisodeParent (lines 243, 253-254 in movie.helper.ts) explicitly returns null for either season or series, but this interface declares both as non-null. This creates a type contract violation.

Apply the previously suggested fix:

 export interface CSFDParent {
-  season: { id: number; name: string; };
-  series: { id: number; name: string; };
+  season: { id: number; name: string } | null;
+  series: { id: number; name: string } | null;
 }

166-171: Fix type contract: CSFDSeason fields vs getSeasonsOrEpisodes implementation.

The CSFDSeason interface declares name and url as non-null strings, but getSeasonsOrEpisodes (lines 213-214 in movie.helper.ts) explicitly returns them as string | null via || null fallbacks. This type mismatch will cause issues.

Either widen the DTO to match reality:

 export interface CSFDSeason {
   id: number;
-  name: string;
-  url: string;
+  name: string | null;
+  url: string | null;
   info: string | null;
 }

Or fix the helper to filter incomplete nodes and guarantee non-null values (preferred approach, as suggested in the past review).

src/helpers/movie.helper.ts (3)

23-39: Fix function name typo and return type mismatch.

Two issues flagged in past review remain:

  1. Typo: getSerieasAndSeasonTitle should be getSeriesAndSeasonTitle (note the extra "a")
  2. Type mismatch: Return type declares seriesName: string but line 26 returns { seriesName: null, seasonName: null }

Apply the previously suggested fix:

-export const getSerieasAndSeasonTitle = (el: HTMLElement): { seriesName: string; seasonName: string | null } => {
+export const getSeriesAndSeasonTitle = (
+  el: HTMLElement
+): { seriesName: string | null; seasonName: string | null } => {
   const titleElement = el.querySelector('h1');
   if (!titleElement) {
     return { seriesName: null, seasonName: null };
   }
 
   const fullText = titleElement.innerText.trim();
 
   // Check if there's a series part indicated by ' - '
   if (fullText.includes(' - ')) {
     const [seriesName, seasonName] = fullText.split(' - ').map(part => part.trim());
     return { seriesName, seasonName };
   }
 
   // If no series part found, return just the name
   return { seriesName: fullText, seasonName: null };
 };

Remember to update the import in movie.service.ts line 29.


200-218: Filter invalid entries and build absolute URLs.

The past review correctly identified that this function returns nullable id/name/url values and relative URLs, violating the CSFDSeason contract (which declares them non-null). This should be fixed to maintain type safety.

Apply a filtering approach to ensure only valid entries are returned:

-export const getSeasonsOrEpisodes = (el: HTMLElement, serie?: { id: number; title: string; }): CSFDSeason[] | null => {
+export const getSeasonsOrEpisodes = (
+  el: HTMLElement,
+  _serie?: { id: number; title: string }
+): CSFDSeason[] | null => {
   const childrenList = el.querySelector('.film-episodes-list');
   if (!childrenList) return null;
 
   const childrenNodes = childrenList.querySelectorAll('.film-title');
   if (!childrenNodes?.length) return [];
 
-  return childrenNodes.map((season) => {
-    const nameContainer = season.querySelector('.film-title-name');
-    const infoContainer = season.querySelector('.info');
-
-    return {
-      id: parseLastIdFromUrl(nameContainer?.getAttribute('href') || ''),
-      name: nameContainer?.textContent?.trim() || null,
-      url: nameContainer?.getAttribute('href') || null,
-      info: infoContainer?.textContent?.replace(/[{()}]/g, '').trim() || null,
-    };
-  });
+  const items: CSFDSeason[] = [];
+  for (const node of childrenNodes) {
+    const nameEl = node.querySelector('.film-title-name');
+    const infoEl = node.querySelector('.info');
+    const href = nameEl?.getAttribute('href') || '';
+    const id = parseLastIdFromUrl(href);
+    const name = nameEl?.textContent?.trim();
+    if (!id || !name || !href) continue;
+    items.push({
+      id,
+      name,
+      url: `https://www.csfd.cz${href}`,
+      info: infoEl?.textContent?.replace(/[{()}]/g, '').trim() || null,
+    });
+  }
+  return items;
 }

This ensures CSFDSeason can maintain non-null contracts for id, name, and url.


239-259: Add string fallback for optional chaining to prevent undefined.

The past review correctly identified that getAttribute('href') returns undefined when the element doesn't exist (lines 248-249), but parseIdFromUrl expects a string parameter. TypeScript strict mode will flag this.

Apply the previously suggested fix:

   const [parentSeries, parentSeason] = parents;
 
-  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
-  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href'));
+  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href') || '');
+  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href') || '');
   const seriesName = parentSeries?.textContent?.trim() || null;
   const seasonName = parentSeason?.textContent?.trim() || null;
 
   const series = seriesId && seriesName ? { id: seriesId, name: seriesName } : null;
   const season = seasonId && seasonName ? { id: seasonId, name: seasonName } : null;
 
   if (!series && !season) return null;
 
   return { series, season };

This ensures parseIdFromUrl always receives a string, and the empty string will correctly return null per the function's logic.

🧹 Nitpick comments (1)
src/helpers/movie.helper.ts (1)

231-237: Consider using nullish coalescing for consistency.

Minor suggestion: Use ?? instead of logical OR for the default value to be more explicit about handling null/undefined.

-  const headerText = el.querySelector('.box-header h3')?.innerText.trim() ?? '';
+  const headerText = el.querySelector('.box-header h3')?.innerText?.trim() ?? '';

This ensures trim() is only called if innerText exists.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ed4dae and 80336e9.

📒 Files selected for processing (5)
  • demo.ts (1 hunks)
  • src/dto/movie.ts (3 hunks)
  • src/helpers/global.helper.ts (1 hunks)
  • src/helpers/movie.helper.ts (2 hunks)
  • src/services/movie.service.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • demo.ts
  • src/services/movie.service.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/helpers/movie.helper.ts (2)
src/helpers/global.helper.ts (2)
  • parseIdFromUrl (4-12)
  • parseLastIdFromUrl (14-22)
src/dto/movie.ts (2)
  • CSFDSeason (166-171)
  • CSFDParent (27-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (3)
src/dto/movie.ts (1)

20-25: LGTM: New fields appropriately nullable.

The new fields on CSFDMovie correctly declare nullable types (| null), which aligns with the conditional parsing logic in movie.helper.ts.

src/helpers/movie.helper.ts (2)

9-16: LGTM: Imports added correctly.

New imports for CSFDParent, CSFDSeason, and parseLastIdFromUrl are properly structured and necessary for the new functionality.


220-229: LGTM: Episode code extraction logic is sound.

The function properly handles missing elements and uses regex to extract the episode code from parentheses. The nullable return type correctly reflects the optional nature of this field.

@bartholomej bartholomej force-pushed the tv-series branch 2 times, most recently from 288840c to c788bb9 Compare November 19, 2025 12:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (7)
src/helpers/global.helper.ts (1)

14-22: Type mismatch and 0-coercion bug remain unfixed.

The past review flagged these critical issues, but the code has not been updated:

  1. Type signature mismatch: Function declares number return type but executes return null (lines 18, 20). This will fail under strictNullChecks.

  2. 0-coercion bug: Pattern +id || null (line 18) incorrectly converts the valid ID 0 to null. Use Number.isFinite() instead.

Apply the fix from the previous review:

-export const parseLastIdFromUrl = (url: string): number => {
+export const parseLastIdFromUrl = (url: string): number | null => {
   if (url) {
-    const idSlug = url?.split('/')[3];
-    const id = idSlug?.split('-')[0];
-    return +id || null;
+    const idSlug = url.split('/')[3];
+    const num = Number((idSlug ?? '').split('-')[0]);
+    return Number.isFinite(num) ? num : null;
   } else {
     return null;
   }
 };

Also apply the same fix to parseIdFromUrl (lines 4-12) for consistency.

src/dto/movie.ts (2)

27-30: CSFDParent type contract violation remains unfixed.

Past review correctly identified that getSeasonorEpisodeParent (in movie.helper.ts:243, 258) returns objects where season or series can be null, but this interface declares both as non-nullable.

Apply the suggested fix:

 export interface CSFDParent {
-  season: { id: number; name: string; };
-  series: { id: number; name: string; };
+  season: { id: number; name: string } | null;
+  series: { id: number; name: string } | null;
 }

166-171: CSFDSeason type contract mismatch remains unfixed.

Past review correctly identified that getSeasonsOrEpisodes (movie.helper.ts:213-214) returns name and url as string | null via || null fallbacks, but this interface declares both as non-nullable strings.

Either widen the DTO to accept nulls:

 export interface CSFDSeason {
   id: number;
-  name: string;
-  url: string;
+  name: string | null;
+  url: string | null;
   info: string | null;
 }

Or fix getSeasonsOrEpisodes to filter out incomplete nodes and build absolute URLs (preferred approach detailed in the helper file review).

src/helpers/movie.helper.ts (3)

23-39: Function name typo and type mismatch remain unfixed.

Past review flagged two issues that persist:

  1. Typo: getSerieasAndSeasonTitle should be getSeriesAndSeasonTitle
  2. Type mismatch: Return type declares seriesName: string but line 26 returns null

Apply the fix:

-export const getSerieasAndSeasonTitle = (el: HTMLElement): { seriesName: string; seasonName: string | null } => {
+export const getSeriesAndSeasonTitle = (
+  el: HTMLElement
+): { seriesName: string | null; seasonName: string | null } => {
   const titleElement = el.querySelector('h1');
   if (!titleElement) {
     return { seriesName: null, seasonName: null };
   }
 
   const fullText = titleElement.innerText.trim();
 
   // Check if there's a series part indicated by ' - '
   if (fullText.includes(' - ')) {
     const [seriesName, seasonName] = fullText.split(' - ').map(part => part.trim());
     return { seriesName, seasonName };
   }
 
   // If no series part found, return just the name
   return { seriesName: fullText, seasonName: null };
 };

Update the import in src/services/movie.service.ts line 29.


200-218: Type contract violations and relative URLs remain unfixed.

Past review flagged critical issues that persist in the current code:

  1. Lines 213-214 return nullable values (|| null) violating CSFDSeason contract (requires non-null name and url)
  2. Line 214 returns relative URLs without base domain (e.g., /film/... instead of https://www.csfd.cz/film/...)

Apply the suggested fix:

-export const getSeasonsOrEpisodes = (el: HTMLElement, serie?: { id: number; title: string; }): CSFDSeason[] | null => {
+export const getSeasonsOrEpisodes = (
+  el: HTMLElement,
+  _serie?: { id: number; title: string }
+): CSFDSeason[] | null => {
   const childrenList = el.querySelector('.film-episodes-list');
   if (!childrenList) return null;
 
   const childrenNodes = childrenList.querySelectorAll('.film-title');
   if (!childrenNodes?.length) return [];
 
-  return childrenNodes.map((season) => {
-    const nameContainer = season.querySelector('.film-title-name');
-    const infoContainer = season.querySelector('.info');
-
-    return {
-      id: parseLastIdFromUrl(nameContainer?.getAttribute('href') || ''),
-      name: nameContainer?.textContent?.trim() || null,
-      url: nameContainer?.getAttribute('href') || null,
-      info: infoContainer?.textContent?.replace(/[{()}]/g, '').trim() || null,
-    };
-  });
+  const items: CSFDSeason[] = [];
+  for (const node of childrenNodes) {
+    const nameEl = node.querySelector('.film-title-name');
+    const infoEl = node.querySelector('.info');
+    const href = nameEl?.getAttribute('href') || '';
+    const id = parseLastIdFromUrl(href);
+    const name = nameEl?.textContent?.trim();
+    if (!id || !name || !href) continue;
+    items.push({
+      id,
+      name,
+      url: `https://www.csfd.cz${href}`,
+      info: infoEl?.textContent?.replace(/[{()}]/g, '').trim() || null,
+    });
+  }
+  return items;
 }

239-259: Type safety issue with undefined passing remains unfixed.

Past review correctly identified that parentSeason?.getAttribute('href') returns undefined when parentSeason is null/undefined (e.g., when parents array has only 1 element), but parseIdFromUrl expects a string parameter.

Apply the suggested fix:

   const [parentSeries, parentSeason] = parents;
 
-  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
-  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href'));
+  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href') || '');
+  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href') || '');
   const seriesName = parentSeries?.textContent?.trim() || null;
   const seasonName = parentSeason?.textContent?.trim() || null;
 
   const series = seriesId && seriesName ? { id: seriesId, name: seriesName } : null;
   const season = seasonId && seasonName ? { id: seasonId, name: seasonName } : null;
 
   if (!series && !season) return null;
 
   return { series, season };

Also add test coverage for: 0 parents (uses fallback serie), 1 parent (series only), 2 parents (series + season).

src/services/movie.service.ts (1)

100-105: Multiple critical issues in parent/seasons/episodes population.

Three problems persist:

  1. Lines 100-101: getSeasonsOrEpisodes returns nullable fields and relative URLs, violating CSFDSeason contract (see movie.helper.ts:200-218).

  2. Line 102 parent computation bug: Logic (type === 'seriál') ? null : getSeasonorEpisodeParent(...) incorrectly assigns a parent to regular films. The intent appears inverted—only episodes/seasons should have parents, not top-level series.

  3. Line 102 fallback misuse: Passing { id: movieId, name: title } as the series fallback for non-series items creates bogus self-references.

Fix the parent computation logic:

-      parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }),
+      // Only episodes/seasons have parents; films/series do not
+      parent:
+        (type === 'epizoda' || type === 'série')
+          ? getSeasonorEpisodeParent(el)
+          : null,

And ensure getSeasonsOrEpisodes is fixed per the movie.helper.ts review.

🧹 Nitpick comments (2)
src/dto/movie.ts (1)

20-25: New fields align with TV series support.

The added fields (seasons, episodes, parent, episodeCode, seasonName) appropriately extend the movie model for series/season/episode data. Note: episodes currently reuses CSFDSeason[] type; consider a dedicated CSFDEpisode type in the future for clarity.

src/helpers/movie.helper.ts (1)

231-237: Detection logic is straightforward.

The string matching approach works for the current CSFD markup. Consider adding test coverage for the Czech string patterns to catch future markup changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 288840c and c788bb9.

📒 Files selected for processing (5)
  • demo.ts (1 hunks)
  • src/dto/movie.ts (3 hunks)
  • src/helpers/global.helper.ts (1 hunks)
  • src/helpers/movie.helper.ts (2 hunks)
  • src/services/movie.service.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/services/movie.service.ts (2)
src/helpers/movie.helper.ts (8)
  • getMovieType (271-274)
  • getSerieasAndSeasonTitle (23-39)
  • detectSeasonOrEpisodeListType (231-237)
  • getMovieTitle (41-43)
  • getMovieYear (82-90)
  • getSeasonsOrEpisodes (200-218)
  • getSeasonorEpisodeParent (239-259)
  • getEpisodeCode (220-229)
src/dto/global.ts (1)
  • CSFDFilmTypes (25-36)
demo.ts (2)
src/index.ts (2)
  • csfd (66-72)
  • movie (39-42)
src/services/movie.service.ts (1)
  • movie (36-52)
src/helpers/movie.helper.ts (2)
src/helpers/global.helper.ts (2)
  • parseIdFromUrl (4-12)
  • parseLastIdFromUrl (14-22)
src/dto/movie.ts (2)
  • CSFDSeason (166-171)
  • CSFDParent (27-30)
🔇 Additional comments (3)
demo.ts (1)

7-7: LGTM! Demo updates align with new TV series functionality.

The movie ID change and commented creator call are appropriate for testing the new season/episode features introduced in this PR.

Also applies to: 13-13

src/helpers/movie.helper.ts (1)

220-229: LGTM! Episode code extraction is correctly implemented.

The regex pattern safely extracts content from parentheses, with proper null handling for missing elements.

src/services/movie.service.ts (1)

61-65: Title computation logic depends on unfixed helper issues.

Lines 62 and 65 rely on getSerieasAndSeasonTitle, which has:

  1. A typo in the function name
  2. Type mismatch (returns null but declares string for seriesName)

Once the helper is fixed (see review comment on movie.helper.ts:23-39), update the import on line 29 to use the corrected function name.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (6)
src/dto/movie.ts (1)

20-24: Fix CSFDParent / CSFDSeason type contracts to match helper behavior.

CSFDParent and CSFDSeason currently declare fields as non‑nullable, but the helpers return nulls:

  • getSeasonorEpisodeParent can produce { series: null, season: {…} }, { series: {…}, season: null }, or null, so season and series must allow null.
  • getSeasonsOrEpisodes can emit entries where name and url are null, and id can be null from parseLastIdFromUrl.

This violates the published DTO contract and will hurt TS consumers (especially with strictNullChecks).

Recommend:

  1. Make CSFDParent members nullable:
-export interface CSFDParent {
-  season: { id: number; name: string; };
-  series: { id: number; name: string; };
-}
+export interface CSFDParent {
+  season: { id: number; name: string } | null;
+  series: { id: number; name: string } | null;
+}
  1. Either:
  • Keep CSFDSeason strict and fix getSeasonsOrEpisodes to filter out incomplete nodes and always return non‑null id/name/url (preferred), or
  • Widen DTO fields to nullable (id: number | null; name: string | null; url: string | null;) if you truly want to propagate partial items.

Right now the implementation and typings are inconsistent.

Also applies to: 27-30

src/helpers/movie.helper.ts (3)

23-39: Rename and fix nullability in getSerieasAndSeasonTitle.

Issues:

  • Exported name has a typo (getSerieasAndSeasonTitle).
  • Return type says seriesName: string, but you return seriesName: null when <h1> is missing.

Recommend:

-export const getSerieasAndSeasonTitle = (el: HTMLElement): { seriesName: string; seasonName: string | null } => {
+export const getSeriesAndSeasonTitle = (
+  el: HTMLElement
+): { seriesName: string | null; seasonName: string | null } => {
   const titleElement = el.querySelector('h1');
   if (!titleElement) {
     return { seriesName: null, seasonName: null };
   }

   const fullText = titleElement.innerText.trim();

   if (fullText.includes(' - ')) {
     const [seriesName, seasonName] = fullText.split(' - ').map((part) => part.trim());
     return { seriesName, seasonName };
   }

   return { seriesName: fullText, seasonName: null };
 };

Then update the import and call site in src/services/movie.service.ts to use getSeriesAndSeasonTitle.


200-218: Harden getSeasonsOrEpisodes: avoid nulls, fix .trim() chain, and normalize URLs.

Current implementation has several problems:

  • name, url, and id can be null, but CSFDSeason declares them as non‑null.
  • infoContainer?.textContent?.replace(/…/g, '').trim() will throw if infoContainer is missing, because .trim() is not behind optional chaining.
  • url is returned as a relative path; elsewhere you expose absolute URLs.

Suggested rewrite:

-export const getSeasonsOrEpisodes = (el: HTMLElement, serie?: { id: number; title: string; }): CSFDSeason[] | null => {
+export const getSeasonsOrEpisodes = (el: HTMLElement): CSFDSeason[] | null => {
   const childrenList = el.querySelector('.film-episodes-list');
   if (!childrenList) return null;

   const childrenNodes = childrenList.querySelectorAll('.film-title');
   if (!childrenNodes?.length) return [];

-  return childrenNodes.map((season) => {
-    const nameContainer = season.querySelector('.film-title-name');
-    const infoContainer = season.querySelector('.info');
-
-    return {
-      id: parseLastIdFromUrl(nameContainer?.getAttribute('href') || ''),
-      name: nameContainer?.textContent?.trim() || null,
-      url: nameContainer?.getAttribute('href') || null,
-      info: infoContainer?.textContent?.replace(/[{()}]/g, '').trim() || null,
-    };
-  });
+  const items: CSFDSeason[] = [];
+  for (const node of childrenNodes) {
+    const nameEl = node.querySelector('.film-title-name');
+    const infoEl = node.querySelector('.info');
+    const href = nameEl?.getAttribute('href') || '';
+    const id = parseLastIdFromUrl(href);
+    const name = nameEl?.textContent?.trim();
+
+    if (!id || !name || !href) continue;
+
+    const rawInfo = infoEl?.textContent
+      ?.replace(/[{()}]/g, '')
+      .trim();
+
+    items.push({
+      id,
+      name,
+      url: href.startsWith('http') ? href : `https://www.csfd.cz${href}`,
+      info: rawInfo || null,
+    });
+  }
+  return items;
 }

This keeps CSFDSeason non‑nullable, avoids a potential runtime error on .trim(), and makes URLs consistent with the rest of the helpers.


239-258: Align getSeasonorEpisodeParent with types and avoid passing undefined to parseIdFromUrl.

Issues:

  • parseIdFromUrl(parentSeries?.getAttribute('href')) can receive undefined; the helper expects a string.
  • Function can return { series: null, season: {...} } or { series: {...}, season: null }, but CSFDParent currently requires both fields non‑null.
  • With parents.length === 0, you fabricate a parent { series: serie, season: null } which is then used even for plain films via the service fallback, yielding a bogus “self‑parent”.

At minimum:

-  const [parentSeries, parentSeason] = parents;
-
-  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
-  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href'));
+  const [parentSeries, parentSeason] = parents;
+
+  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href') || '');
+  const seasonId = parseIdFromUrl(parentSeason?.getAttribute('href') || '');
   const seriesName = parentSeries?.textContent?.trim() || null;
   const seasonName = parentSeason?.textContent?.trim() || null;
 
   const series = seriesId && seriesName ? { id: seriesId, name: seriesName } : null;
   const season = seasonId && seasonName ? { id: seasonId, name: seasonName } : null;
 
   if (!series && !season) return null;
 
   return { series, season };

Combined with updating CSFDParent to allow nullable members, this becomes type‑safe and matches real DOM possibilities. Also consider dropping the serie fallback entirely and just returning null when parents.length === 0; the movie service can then decide when to compute a parent.

src/services/movie.service.ts (2)

61-66: Fix helper name usage and clarify title/seasonName derivation.

Once getSerieasAndSeasonTitle is renamed and its types fixed, update this call accordingly:

-    const type = getMovieType(el) as CSFDFilmTypes;
-    const { seriesName = null, seasonName = null } = type === 'série' ? getSerieasAndSeasonTitle(el) : {};
+    const type = getMovieType(el) as CSFDFilmTypes;
+    const { seriesName = null, seasonName = null } =
+      type === 'série' ? getSeriesAndSeasonTitle(el) : {};

The title assignment logic (type === 'série' && seriesName ? seriesName : getMovieTitle(el)) looks reasonable and keeps the DTO’s title focused on the series name for season pages.

Also applies to: 99-105


100-104: Correct parent computation: avoid bogus self‑parent for films and restrict to seasons/episodes.

Current logic:

parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }),

For plain films (non‑series), this calls getSeasonorEpisodeParent with a fallback { id: movieId, name: title }, and the helper returns { series: serie, season: null } when no parents are found. That incorrectly assigns every film a “parent” equal to itself.

Recommend:

-      parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }),
+      // Only derive parent for season/episode detail pages; films and root series have no parent.
+      parent:
+        type === 'epizoda' || type === 'série'
+          ? getSeasonorEpisodeParent(el)
+          : null,

This aligns parent with the real hierarchy (episodes/seasons have parents; films and main series entries do not).

🧹 Nitpick comments (3)
src/dto/movie.ts (1)

20-22: Consider a distinct episode DTO instead of reusing CSFDSeason.

episodes: CSFDSeason[] | null reuses the season type for episodes. If episode items differ even slightly (e.g., include an episode code, air date, or per‑episode info vs per‑season info), a dedicated CSFDEpisode interface will keep the model clearer and safer to evolve.

If the structures are truly identical, adding a short docstring clarifying that CSFDSeason is also used for episode list entries would help future readers.

Also applies to: 166-171

src/helpers/movie.helper.ts (1)

231-237: Make detectSeasonOrEpisodeListType more robust.

Right now it inspects only the first .box-header h3. If the seasons/episodes section isn’t the first box, you’ll silently return null.

Consider scanning all headers instead:

-export const detectSeasonOrEpisodeListType = (el: HTMLElement) => {
-  const headerText = el.querySelector('.box-header h3')?.innerText.trim() ?? '';
-
-  if (headerText.includes('Série')) return 'seasons';
-  if (headerText.startsWith('Epizody')) return 'episodes';
-  return null;
-}
+export const detectSeasonOrEpisodeListType = (el: HTMLElement): 'seasons' | 'episodes' | null => {
+  const headers = el.querySelectorAll('.box-header h3');
+  for (const header of headers) {
+    const text = header.innerText.trim();
+    if (text.includes('Série')) return 'seasons';
+    if (text.startsWith('Epizody')) return 'episodes';
+  }
+  return null;
+}

Not critical, but makes the detection less brittle if CSFD changes box order.

src/services/movie.service.ts (1)

100-103: Ensure seasons/episodes arrays and DTOs stay consistent with helper behavior.

The wiring:

seasons: seasonOrEpisodeListType === 'seasons' ? getSeasonsOrEpisodes(el) : null,
episodes: seasonOrEpisodeListType === 'episodes' ? getSeasonsOrEpisodes(el) : null,
episodeCode: type === 'epizoda' ? getEpisodeCode(el) : null,

is fine conceptually, but depends on getSeasonsOrEpisodes returning items that match CSFDSeason (non‑null id/name/url, absolute URLs). Once you adjust the helper as suggested, this assignment will be consistent and safe for consumers. Until then, the DTO over‑promises non‑nullable fields.

After updating the helper, consider adding a couple of integration tests hitting a known series, season, and episode page to lock in expected shapes of seasons, episodes, parent, and episodeCode.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c788bb9 and fcce368.

📒 Files selected for processing (5)
  • demo.ts (1 hunks)
  • src/dto/movie.ts (3 hunks)
  • src/helpers/global.helper.ts (1 hunks)
  • src/helpers/movie.helper.ts (2 hunks)
  • src/services/movie.service.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/helpers/global.helper.ts
🧰 Additional context used
🧬 Code graph analysis (3)
demo.ts (2)
src/index.ts (2)
  • csfd (70-76)
  • movie (39-42)
src/services/movie.service.ts (1)
  • movie (36-52)
src/helpers/movie.helper.ts (2)
src/helpers/global.helper.ts (2)
  • parseIdFromUrl (4-12)
  • parseLastIdFromUrl (14-22)
src/dto/movie.ts (2)
  • CSFDSeason (166-171)
  • CSFDParent (27-30)
src/services/movie.service.ts (1)
src/helpers/movie.helper.ts (6)
  • getSerieasAndSeasonTitle (23-39)
  • detectSeasonOrEpisodeListType (231-237)
  • getMovieTitle (41-43)
  • getSeasonsOrEpisodes (200-218)
  • getSeasonorEpisodeParent (239-259)
  • getEpisodeCode (220-229)
🔇 Additional comments (1)
demo.ts (1)

7-7: Demo change looks fine.

Switching the demo movie ID and commenting out the creator call is harmless and keeps the example simple.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
tests/mocks/series2-episode.mock.ts (1)

1-1234: Same fixture pattern and trade-offs as other large mocks

This follows the same “full-page HTML in a template string” approach as the other mocks. Functionally fine, but the same comments about noisy diffs and readability apply; see note on series1-season1-episode.mock.ts for possible ways to externalize or slim these fixtures.

src/helpers/movie.helper.ts (3)

109-127: Fix function name typo and return type mismatch.

This issue was flagged in a previous review but remains unresolved:

  1. Typo: getSerieasAndSeasonTitle should be getSeriesAndSeasonTitle
  2. Type mismatch: Return type declares seriesName: string but Line 114 returns null, causing a type error in strict mode.

Apply the previously suggested fix:

-export const getSerieasAndSeasonTitle = (
+export const getSeriesAndSeasonTitle = (
   el: HTMLElement
-): { seriesName: string; seasonName: string | null } => {
+): { seriesName: string | null; seasonName: string | null } => {
   const titleElement = el.querySelector('h1');
   if (!titleElement) {
     return { seriesName: null, seasonName: null };
   }
 
   const fullText = titleElement.innerText.trim();
 
   // Check if there's a series part indicated by ' - '
   if (fullText.includes(' - ')) {
     const [seriesName, seasonName] = fullText.split(' - ').map((part) => part.trim());
     return { seriesName, seasonName };
   }
 
   // If no series part found, return just the name
   return { seriesName: fullText, seasonName: null };
 };

Update the import in src/services/movie.service.ts (line 29 per previous review).


288-309: Ensure non-nullable fields and absolute URLs in getSeasonsOrEpisodes.

A previous review flagged this but the issue persists:

  1. Lines 304-305: name and url can be null, but CSFDSeason likely requires non-null values
  2. Line 305: URLs are relative paths, not absolute
  3. Line 303: If parseLastIdFromUrl returns null for invalid input, id becomes null (coerced from the return value)

Filter out invalid entries and construct absolute URLs:

 export const getSeasonsOrEpisodes = (
   el: HTMLElement,
-  serie?: { id: number; title: string }
+  _serie?: { id: number; title: string }
 ): CSFDSeason[] | null => {
   const childrenList = el.querySelector('.film-episodes-list');
   if (!childrenList) return null;
 
   const childrenNodes = childrenList.querySelectorAll('.film-title');
   if (!childrenNodes?.length) return [];
 
-  return childrenNodes.map((season) => {
-    const nameContainer = season.querySelector('.film-title-name');
-    const infoContainer = season.querySelector('.info');
-
-    return {
-      id: parseLastIdFromUrl(nameContainer?.getAttribute('href') || ''),
-      name: nameContainer?.textContent?.trim() || null,
-      url: nameContainer?.getAttribute('href') || null,
-      info: infoContainer?.textContent?.replace(/[{()}]/g, '').trim() || null
-    };
-  });
+  const items: CSFDSeason[] = [];
+  for (const node of childrenNodes) {
+    const nameEl = node.querySelector('.film-title-name');
+    const infoEl = node.querySelector('.info');
+    const href = nameEl?.getAttribute('href') || '';
+    const id = parseLastIdFromUrl(href);
+    const name = nameEl?.textContent?.trim();
+    if (!id || !name || !href) continue;
+    items.push({
+      id,
+      name,
+      url: `https://www.csfd.cz${href}`,
+      info: infoEl?.textContent?.replace(/[{()}]/g, '').trim() || null
+    });
+  }
+  return items;
 }

330-358: Add string fallback to prevent undefined being passed to parseIdFromUrl.

This issue was flagged in a previous review and remains unresolved:

Line 347 passes parentSeries?.getAttribute('href') (which can be undefined) to parseIdFromUrl, but the function signature in src/helpers/global.helper.ts (lines 3-11) expects a string parameter.

Apply the fix from the previous review:

-  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
-  const seasonId = parseLastIdFromUrl(parentSeason?.getAttribute('href') || '');
+  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href') || '');
+  const seasonId = parseLastIdFromUrl(parentSeason?.getAttribute('href') || '');

This ensures both calls always pass a string. Empty strings will be handled by the if (url) check in parseIdFromUrl, returning null safely.

Also consider adding unit tests for edge cases (0, 1, 2 parents) as suggested in the previous review.

🧹 Nitpick comments (7)
tests/mocks/series1-season1-episode.mock.ts (1)

1-1277: Huge inline HTML fixture: consider externalizing for maintainability

This mock is valid and structurally rich, but inlining the full CSFD page as a template string makes diffs noisy and the test file hard to navigate. Consider moving the raw HTML into a .html fixture file and loading it via fs.readFileSync in tests, or at least documenting which parts are relied on by the parser to reduce future accidental edits.

tests/mocks/series1-season1.mock.ts (1)

1-1357: Season mock is structurally correct; same comments on fixture size

This season mock appears structurally aligned with how the parser operates (episodes list, creators, JSON-LD, aside). As with the other mocks, consider external fixture files or minimal, focused HTML samples in future to reduce inline bulk, but there’s no functional issue here.

tests/movie.test.ts (2)

636-654: getEpisodeCode tests are well‑structured and cover main branches

The three tests here validate:

  • Extraction of a code in parentheses,
  • Absence of code in the title,
  • Missing .film-header-name h1 entirely.

This matches the helper’s behavior (regex on the <h1> text) and gives good coverage of success and null cases. No changes needed.

You could also add one test that uses a full episode mock DOM (like from the HTML fixtures) to guard against future selector changes, but that’s optional given the integration tests in tests/series.test.ts.


656-669: getSeasonorEpisodeParent tests only cover the fallback path

These tests exercise the fallback behavior (no header anchors, with/without serie argument), which is good. Note that the primary parsing path—where anchors exist under .film-header h2 a or .film-header h1 a and IDs are parsed via parseIdFromUrl / parseLastIdFromUrl—is only exercised indirectly in tests/series.test.ts.

Given that, there’s no immediate change needed here, but if getSeasonorEpisodeParent evolves further, consider adding a small unit test with synthetic header links to lock in the selector/ID parsing behavior.

tests/series.test.ts (2)

1-28: parseMovieMock: solid test helper, but relies on a private method

Using parseMovieMock to drive realistic HTML through MovieScraper.buildMovie is a good approach to avoid network I/O and directly validate parsing. The only trade‑off is calling the private buildMovie via (scraper as any), which couples tests to internal structure.

If buildMovie’s signature changes often, consider:

  • Adding a thin public test‑only wrapper in MovieScraper (e.g. fromHtml(...)), or
  • Exporting buildMovie behind a non‑public symbol guarded by /** @internal */.

Not urgent, but it would make these tests less brittle to refactors in movie.service.ts.


394-440: Cross‑pattern validations are sensible sanity checks

The shared validation block nicely consolidates:

  • Basic invariants (positive IDs and /film/ URLs).
  • Mutual exclusivity for seasons vs episodes on main vs season pages.
  • Episode parent references and presence of episodeCode.
  • Behavior for series without seasons (episodes directly, seasons = null).

This is a good high‑level safety net over the more detailed tests above. If test runtime ever becomes an issue, you might cache parsed CSFDMovie instances instead of reparsing the same mocks in each test, but that’s an optimization, not a requirement.

src/helpers/movie.helper.ts (1)

322-328: Consider i18n support for header text detection.

The function hard-codes Czech text patterns ('Série', 'Epizody'). If the codebase supports multiple languages (based on getLocalizedCreatorLabel), this may need localization.

Example enhancement:

export const detectSeasonOrEpisodeListType = (
  el: HTMLElement,
  language?: string
) => {
  const headerText = el.querySelector('.box-header h3')?.innerText.trim() ?? '';
  
  const patterns = {
    cs: { seasons: 'Série', episodes: 'Epizody' },
    sk: { seasons: 'Série', episodes: 'Epizódy' },
    en: { seasons: 'Season', episodes: 'Episode' }
  };
  
  const lang = patterns[language || 'cs'] || patterns.cs;
  
  if (headerText.includes(lang.seasons)) return 'seasons';
  if (headerText.startsWith(lang.episodes)) return 'episodes';
  return null;
};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5169dba and 89dbcad.

📒 Files selected for processing (7)
  • src/helpers/movie.helper.ts (5 hunks)
  • tests/fetchers.test.ts (1 hunks)
  • tests/mocks/series1-season1-episode.mock.ts (1 hunks)
  • tests/mocks/series1-season1.mock.ts (1 hunks)
  • tests/mocks/series2-episode.mock.ts (1 hunks)
  • tests/movie.test.ts (6 hunks)
  • tests/series.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
tests/series.test.ts (2)
src/dto/movie.ts (1)
  • CSFDMovie (3-25)
src/services/movie.service.ts (1)
  • MovieScraper (35-107)
tests/fetchers.test.ts (3)
src/services/movie.service.ts (1)
  • movie (36-51)
src/dto/movie.ts (1)
  • CSFDMovie (3-25)
src/dto/global.ts (1)
  • CSFDFilmTypes (25-36)
tests/movie.test.ts (1)
src/helpers/movie.helper.ts (3)
  • getSerieasAndSeasonTitle (109-127)
  • getEpisodeCode (311-320)
  • getSeasonorEpisodeParent (330-358)
src/helpers/movie.helper.ts (2)
src/dto/movie.ts (6)
  • CSFDCreatorGroups (145-158)
  • CSFDCreatorGroupsEnglish (161-174)
  • CSFDCreatorGroupsSlovak (176-186)
  • CSFDParent (27-30)
  • CSFDMovieCreator (80-91)
  • CSFDBoxContent (195-195)
src/helpers/global.helper.ts (2)
  • parseLastIdFromUrl (14-22)
  • parseIdFromUrl (4-12)
🔇 Additional comments (7)
tests/movie.test.ts (2)

190-209: Updated trivia expectations look consistent with richer site content

The adjusted expected trivia strings for movies and series (lines 194–197 and 205–209) match the style of CSFD “Zajímavosti” entries and keep the parser’s behavior constrained to the textual content. No issues here.


265-275: VOD expectation tweaks align with URL tracking params

The updated expected VOD URLs (e.g., added utm_content=csfd and trailing URL fixes) are fine and keep tests strict about the scraper returning fully qualified links. Nothing else to change in this segment.

tests/series.test.ts (3)

38-223: Pattern 1 (Simpsons): good coverage of series → season → episode behavior

These three describe blocks thoroughly validate the “Series with Seasons” pattern:

  • Main series page: seasons present, episodes absent, rating, creators, genres, no parent/episodeCode/seasonName.
  • Season page: episodes present, seasons absent, seasonName set, parent containing only series.
  • Episode page: correct title, type = 'epizoda', episodeCode, parent with both series and season, year, duration, creators, and descriptions.

The assertions align with the provided mocks and the intended semantics of CSFDMovie. No changes needed here.


225-312: Pattern 2 main series (The Curse): validates direct episodes correctly

This suite checks the “Series with direct episodes” case as intended:

  • type = 'seriál', correct title/year, episodes list present, no seasons.
  • No parent/episodeCode/seasonName on the main series object.
  • Ratings, creators, genres, VOD, and descriptions all asserted.

The additional check that firstEpisode.info matches /E\d+/ is a nice way to ensure the episode code is captured into the list metadata. Looks good.


320-389: Pattern 2 episode (The Curse E01): parent and duration checks look correct

These tests correctly assert:

  • Episode metadata (title, type = 'epizoda', episodeCode = 'E01', year = 2023, duration = 61).
  • Parent has only a series (no season) with expected id/name.
  • No seasons/episodes arrays on the episode object itself.
  • Creators, genres, descriptions, and VOD entries exist.

This matches the provided mock HTML where there is only a series header link and no season context. No issues here.

tests/fetchers.test.ts (1)

155-252: LGTM! Comprehensive test coverage for series patterns.

The test suite effectively covers both series patterns (with and without seasons) across all hierarchy levels: series, season, and episode pages. The assertions validate type, title, hierarchical relationships (parent series/season), and collection fields (seasons/episodes).

src/helpers/movie.helper.ts (1)

311-320: LGTM! Episode code extraction is robust.

The function correctly handles missing elements and uses a simple regex pattern to extract the code from parentheses.

Comment on lines +616 to +634
describe('Get Series and Season Title', () => {
test('With series and season', () => {
const html = parse('<h1>Series Name - Season 1</h1>');
const result = getSerieasAndSeasonTitle(html as any);
expect(result).toEqual({ seriesName: 'Series Name', seasonName: 'Season 1' });
});

test('Only series name', () => {
const html = parse('<h1>Movie Name</h1>');
const result = getSerieasAndSeasonTitle(html as any);
expect(result).toEqual({ seriesName: 'Movie Name', seasonName: null });
});

test('No title element', () => {
const html = parse('<div></div>');
const result = getSerieasAndSeasonTitle(html as any);
expect(result).toEqual({ seriesName: null, seasonName: null });
});
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

getSerieasAndSeasonTitle: tests reveal a type mismatch in the helper

These tests correctly cover:

  • Series + season header ("Series Name - Season 1"),
  • Plain title without season,
  • Missing <h1> element.

However, getSerieasAndSeasonTitle is currently declared to return { seriesName: string; seasonName: string | null }, yet its implementation (and this “No title element” test) return seriesName: null when the <h1> is absent. This is a TypeScript type violation.

Consider changing the helper signature to:

export const getSerieasAndSeasonTitle = (
  el: HTMLElement
): { seriesName: string | null; seasonName: string | null } => { ... }

so the implementation and tests are type-safe and consistent.

🤖 Prompt for AI Agents
In tests/movie.test.ts around lines 616-634, the helper getSerieasAndSeasonTitle
is returning seriesName|null in some cases but its TypeScript signature
currently requires seriesName: string; change the helper's exported signature to
return { seriesName: string | null; seasonName: string | null } so the
implementation and tests are type-safe; update the function declaration/export
to use the nullable seriesName type and fix any call sites or imports
accordingly to satisfy the new return type.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/dto/movie.ts (1)

27-30: Consider documenting CSFDParent invariants (series/season presence)

With both season and series allowed to be null, the type doesn’t express any runtime guarantees (e.g., “for an episode, series is always non‑null”, “at least one of them is non‑null”). A short doc comment explaining expected combinations would make this API easier to consume; a stronger alternative would be a discriminated union if you ever need stricter typing.

🧹 Nitpick comments (1)
src/dto/movie.ts (1)

20-21: Clarify episode vs. season typing (optional alias/type for episodes)

episodes is currently typed as CSFDSeason[] | null, and CSFDSeason is defined as a generic { id, name, url, info } item. That works structurally but can be semantically confusing, especially since the PR description mentions CSFDEpisode[] | null.

If you want clearer intent without changing the underlying shape, you could introduce an alias and retarget episodes:

-  episodes: CSFDSeason[] | null;
+  episodes: CSFDEpisode[] | null;

and elsewhere:

export type CSFDEpisode = CSFDSeason;

This keeps the implementation simple while making the public API more self‑descriptive.

Also applies to: 194-199

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89dbcad and 5eead30.

📒 Files selected for processing (1)
  • src/dto/movie.ts (2 hunks)
🔇 Additional comments (1)
src/dto/movie.ts (1)

20-25: New series/season/episode fields on CSFDMovie are a safe, additive extension

All newly added properties (seasons, episodes, parent, episodeCode, seasonName) are nullable and thus backward‑compatible for consumers of CSFDMovie. The DTO shape here looks consistent with the described feature.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/helpers/movie.helper.ts (3)

109-127: Fix typo in exported name getSerieasAndSeasonTitle.

Logic and null-handling look good, but the exported name still contains a typo (Serieas), which will leak into the public helper surface and callers.

Consider renaming to getSeriesAndSeasonTitle and updating all imports/usages accordingly.

-export const getSerieasAndSeasonTitle = (
+export const getSeriesAndSeasonTitle = (
   el: HTMLElement
 ): { seriesName: string | null; seasonName: string | null } => {

288-309: Harden getSeasonsOrEpisodes output: avoid null id/name/url and normalize URLs.

As written, the mapper can emit entries with:

  • id = null/NaN when href is missing or unparsable,
  • name = null when text is missing,
  • url = null and still relative (no host),

which is likely inconsistent with CSFDSeason (id/name/url typically non-nullable) and with other helpers that always return absolute URLs. The serie parameter is also currently unused.

Filtering invalid nodes and normalizing URLs will make the helper safer and keep the DTO strict.

-export const getSeasonsOrEpisodes = (
-  el: HTMLElement,
-  serie?: { id: number; title: string }
-): CSFDSeason[] | null => {
-  const childrenList = el.querySelector('.film-episodes-list');
-  if (!childrenList) return null;
-
-  const childrenNodes = childrenList.querySelectorAll('.film-title');
-  if (!childrenNodes?.length) return [];
-
-  return childrenNodes.map((season) => {
-    const nameContainer = season.querySelector('.film-title-name');
-    const infoContainer = season.querySelector('.info');
-
-    return {
-      id: parseLastIdFromUrl(nameContainer?.getAttribute('href') || ''),
-      name: nameContainer?.textContent?.trim() || null,
-      url: nameContainer?.getAttribute('href') || null,
-      info: infoContainer?.textContent?.replace(/[{()}]/g, '').trim() || null
-    };
-  });
-};
+export const getSeasonsOrEpisodes = (
+  el: HTMLElement,
+  _serie?: { id: number; title: string }
+): CSFDSeason[] | null => {
+  const childrenList = el.querySelector('.film-episodes-list');
+  if (!childrenList) return null;
+
+  const childrenNodes = childrenList.querySelectorAll('.film-title');
+  if (!childrenNodes?.length) return [];
+
+  const items: CSFDSeason[] = [];
+  for (const node of childrenNodes) {
+    const nameEl = node.querySelector('.film-title-name');
+    const infoEl = node.querySelector('.info');
+    const href = nameEl?.getAttribute('href') || '';
+    const id = parseLastIdFromUrl(href);
+    const name = nameEl?.textContent?.trim();
+    if (!id || !name || !href) continue;
+
+    items.push({
+      id,
+      name,
+      url: `https://www.csfd.cz${href}`,
+      info: infoEl?.textContent?.replace(/[{()}]/g, '').trim() || null
+    });
+  }
+  return items;
+};

330-358: Avoid passing nullable href to parseIdFromUrl in getSeasonorEpisodeParent.

parentSeries?.getAttribute('href') is string | null, but parseIdFromUrl expects a string. If href is missing, you currently end up passing null, which is both a type error under strictNullChecks and potentially fragile at runtime. Earlier review already suggested adding a string fallback here.

Align series and season handling by ensuring both calls always receive a string, and keep the final series/season objects gated on both id and name.

-  const [parentSeries, parentSeason] = parents;
-
-  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
-  const seasonId = parseLastIdFromUrl(parentSeason?.getAttribute('href') || '');
+  const [parentSeries, parentSeason] = parents;
+
+  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href') || '');
+  const seasonId = parseLastIdFromUrl(parentSeason?.getAttribute('href') || '');
🧹 Nitpick comments (2)
src/helpers/movie.helper.ts (2)

311-320: Episode code extraction assumes only one parenthesized segment.

The helper works if the header has a single (...), but with something like Foo (2008) (S01E01) it will return 2008 instead of the episode code. If CSFD headers can contain multiple parentheses, consider matching the last group instead.

-  const text = filmHeaderName.textContent?.trim() || '';
-  const match = text.match(/\(([^)]+)\)/);
+  const text = filmHeaderName.textContent?.trim() || '';
+  // Prefer the last (...) group in case there are multiple.
+  const match = text.match(/\(([^)]+)\)\s*$/);

322-328: detectSeasonOrEpisodeListType is fine; consider explicit return type.

The implementation cleanly distinguishes 'seasons' vs 'episodes' based on the Czech header text. For long‑term API stability, you might want to add an explicit return type : 'seasons' | 'episodes' | null so the contract doesn’t widen if the implementation changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5eead30 and 18f3b44.

📒 Files selected for processing (2)
  • src/dto/movie.ts (2 hunks)
  • src/helpers/movie.helper.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/dto/movie.ts
🔇 Additional comments (4)
src/helpers/movie.helper.ts (4)

11-24: New imports for parent/season and helpers look consistent.

The added DTO imports (CSFDParent, CSFDSeason) and parseLastIdFromUrl integration align with the new helpers’ usage; no issues here.


34-52: Typed creator-group key union is clear and type-safe.

Narrowing key to the explicit union of creator groups and typing labels accordingly makes this API safer and self-documenting; implementation looks sound.


360-363: getMovieGroup signature change matches creator label types.

Updating group to accept the union of CSFDCreatorGroups | CSFDCreatorGroupsEnglish | CSFDCreatorGroupsSlovak matches what getLocalizedCreatorLabel returns and keeps things type-safe; implementation remains unchanged and looks good.


400-403: getMovieBoxMovies signature formatting change only.

The multi-line signature is just a stylistic change; the behavior of the function is unchanged and remains correct.

@bartholomej bartholomej changed the title TV series, seasons and episodes [WIP] TV series, seasons and episodes Dec 10, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (5)
src/helpers/global.helper.ts (1)

14-22: Previously identified type and parsing issues remain unaddressed.

The past review comment (marked as addressed in commits 415e5e5 to 2039636) identified critical issues that still exist in the current code:

  1. Type signature mismatch: Function declares number but returns null on lines 18 and 20
  2. 0-coercion bug: Line 18 uses +id || null which incorrectly converts valid ID 0 to null
  3. Unverified segment index: Line 16 uses index [3] without verification against actual CSFD URL structure

The suggested fix from the past review should be applied:

-export const parseLastIdFromUrl = (url: string): number => {
+export const parseLastIdFromUrl = (url: string): number | null => {
   if (url) {
-    const idSlug = url?.split('/')[3];
-    const id = idSlug?.split('-')[0];
-    return +id || null;
+    const idSlug = url.split('/')[3];
+    const num = Number((idSlug ?? '').split('-')[0]);
+    return Number.isFinite(num) ? num : null;
   } else {
     return null;
   }
 };
src/services/movie.service.ts (1)

102-102: Critical: Parent computation assigns incorrect parent to regular films.

Line 102 uses (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }), which passes the current movie's ID and title as a "series" fallback for all non-series types, including standalone films. This creates bogus parent relationships.

The parent field should only be populated for episodes and seasons, not for series or films:

-      parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }),
+      parent:
+        (type === 'epizoda' || type === 'série')
+          ? getSeasonorEpisodeParent(el)
+          : null,

Only pass a series fallback when you have explicit breadcrumb/header evidence of the series context (not available in current code).

src/helpers/movie.helper.ts (3)

288-309: Critical: getSeasonsOrEpisodes violates CSFDSeason contract with nullable fields and relative URLs.

Past review (marked addressed in commits a56763b to 80336e9) identified these issues, but they remain in the current code:

  1. Lines 304-305 return nullable values (|| null) violating CSFDSeason interface which requires non-nullable name: string and url: string
  2. Line 305 returns relative URLs without base domain (e.g., /film/... instead of https://www.csfd.cz/film/...)

Apply this fix to filter incomplete nodes and normalize URLs:

 export const getSeasonsOrEpisodes = (
   el: HTMLElement,
-  serie?: { id: number; title: string }
+  _serie?: { id: number; title: string }
 ): CSFDSeason[] | null => {
   const childrenList = el.querySelector('.film-episodes-list');
   if (!childrenList) return null;
 
   const childrenNodes = childrenList.querySelectorAll('.film-title');
   if (!childrenNodes?.length) return [];
 
-  return childrenNodes.map((season) => {
-    const nameContainer = season.querySelector('.film-title-name');
-    const infoContainer = season.querySelector('.info');
-
-    return {
-      id: parseLastIdFromUrl(nameContainer?.getAttribute('href') || ''),
-      name: nameContainer?.textContent?.trim() || null,
-      url: nameContainer?.getAttribute('href') || null,
-      info: infoContainer?.textContent?.replace(/[{()}]/g, '').trim() || null
-    };
-  });
+  const items: CSFDSeason[] = [];
+  for (const node of childrenNodes) {
+    const nameEl = node.querySelector('.film-title-name');
+    const infoEl = node.querySelector('.info');
+    const href = nameEl?.getAttribute('href') || '';
+    const id = parseLastIdFromUrl(href);
+    const name = nameEl?.textContent?.trim();
+    if (!id || !name || !href) continue;
+    items.push({
+      id,
+      name,
+      url: `https://www.csfd.cz${href}`,
+      info: infoEl?.textContent?.replace(/[{()}]/g, '').trim() || null
+    });
+  }
+  return items;
 };

109-127: Fix function name typo and verify return type.

The function name has a typo: getSerieasAndSeasonTitle should be getSeriesAndSeasonTitle. The return type correctly allows nullable seriesName as addressed in past reviews, but the name needs correction.

-export const getSerieasAndSeasonTitle = (
+export const getSeriesAndSeasonTitle = (
   el: HTMLElement
 ): { seriesName: string | null; seasonName: string | null } => {

Update the import in src/services/movie.service.ts line 30 accordingly.


347-348: Add string fallback for getAttribute to prevent undefined.

Line 347 calls getAttribute('href') which can return undefined when the attribute is missing. While parseIdFromUrl handles falsy values, TypeScript strict mode requires a string.

-  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
+  const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href') || '');
   const seasonId = parseLastIdFromUrl(parentSeason?.getAttribute('href') || '');

Line 348 already has the correct fallback.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18f3b44 and 2aa67db.

📒 Files selected for processing (11)
  • demo.ts (1 hunks)
  • src/dto/movie.ts (2 hunks)
  • src/helpers/global.helper.ts (1 hunks)
  • src/helpers/movie.helper.ts (5 hunks)
  • src/services/movie.service.ts (4 hunks)
  • tests/fetchers.test.ts (1 hunks)
  • tests/mocks/series1-season1-episode.mock.ts (1 hunks)
  • tests/mocks/series1-season1.mock.ts (1 hunks)
  • tests/mocks/series2-episode.mock.ts (1 hunks)
  • tests/movie.test.ts (6 hunks)
  • tests/series.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/series.test.ts
  • demo.ts
  • tests/mocks/series1-season1.mock.ts
  • tests/mocks/series1-season1-episode.mock.ts
🧰 Additional context used
🧬 Code graph analysis (3)
tests/movie.test.ts (1)
src/helpers/movie.helper.ts (3)
  • getSerieasAndSeasonTitle (109-127)
  • getEpisodeCode (311-320)
  • getSeasonorEpisodeParent (330-358)
tests/fetchers.test.ts (3)
src/index.ts (2)
  • movie (57-60)
  • csfd (89-96)
src/dto/movie.ts (1)
  • CSFDMovie (3-25)
src/dto/global.ts (1)
  • CSFDFilmTypes (25-36)
src/services/movie.service.ts (2)
src/helpers/movie.helper.ts (12)
  • getMovieType (373-376)
  • getSerieasAndSeasonTitle (109-127)
  • detectSeasonOrEpisodeListType (322-328)
  • getMovieTitle (129-131)
  • getMovieYear (170-178)
  • getMovieDuration (180-204)
  • getMovieDescriptions (266-270)
  • getMovieGenres (133-136)
  • getMovieBoxMovies (400-417)
  • getSeasonsOrEpisodes (288-309)
  • getSeasonorEpisodeParent (330-358)
  • getEpisodeCode (311-320)
src/dto/global.ts (1)
  • CSFDFilmTypes (25-36)
🔇 Additional comments (6)
tests/mocks/series2-episode.mock.ts (1)

1-1234: LGTM! Test mock provides necessary HTML fixture.

The HTML mock export is well-structured and provides comprehensive test data for series 2 episode parsing, including metadata, media content, reviews, and navigation elements. This complements existing mocks and supports the new episode-level parsing tests.

src/dto/movie.ts (2)

20-30: LGTM! DTO changes properly implement nullable hierarchical metadata.

The new fields (seasons, episodes, parent, episodeCode, seasonName) and interfaces (CSFDParent, CSFDSeason) correctly support TV series/season/episode hierarchies. The CSFDParent interface properly allows nullable season and series members as addressed in past reviews.


194-199: LGTM! CSFDSeason interface structure is appropriate.

The interface properly defines season/episode metadata with required fields (id, name, url) and optional info. This aligns with the helper functions that populate these fields.

tests/fetchers.test.ts (1)

155-252: LGTM! Comprehensive pattern-based test coverage.

The expanded test suite now covers five distinct series/season/episode patterns, validating:

  • Type and title extraction
  • Seasons/episodes arrays population
  • Parent series/season linkage
  • Episode code parsing
  • Null handling for absent fields

This thorough coverage significantly improves confidence in the new hierarchical parsing functionality.

tests/movie.test.ts (1)

616-669: LGTM! Well-structured test coverage for new helpers.

The new test suites provide good coverage for the three helper functions:

  • getSerieasAndSeasonTitle: validates series/season extraction logic
  • getEpisodeCode: verifies episode code parsing
  • getSeasonorEpisodeParent: confirms parent linkage behavior

Edge cases are appropriately tested (missing elements, null fallbacks).

src/services/movie.service.ts (1)

60-65: LGTM! Type and title computation logic is sound.

The new logic correctly:

  • Computes type once and reuses it
  • Extracts series/season names when type is 'série'
  • Detects whether the page contains seasons or episodes list
  • Falls back to standard title extraction when not a season page

Comment on lines +100 to +101
seasons: seasonOrEpisodeListType === 'seasons' ? getSeasonsOrEpisodes(el) : null,
episodes: seasonOrEpisodeListType === 'episodes' ? getSeasonsOrEpisodes(el) : null,
Copy link

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:

rg -nA15 "export const getSeasonsOrEpisodes" src/helpers/movie.helper.ts

Repository: bartholomej/node-csfd-api

Length of output: 721


🏁 Script executed:

rg -nA40 "export const getSeasonsOrEpisodes" src/helpers/movie.helper.ts

Repository: bartholomej/node-csfd-api

Length of output: 1641


getSeasonsOrEpisodes does not filter incomplete nodes or normalize URLs.

The helper function at lines 288-309 in src/helpers/movie.helper.ts maps all childrenNodes without filtering incomplete entries. Nodes with missing id, name, or url are included in the result, with null values returned for absent properties. Additionally, URLs are returned as raw href attributes without normalization—the https://www.csfd.cz prefix is not prepended, so consumers will receive relative URLs (e.g., /film/...). Add filtering logic to exclude incomplete nodes and normalize URLs to absolute paths.

🤖 Prompt for AI Agents
In src/services/movie.service.ts around lines 100-101 (root cause in
src/helpers/movie.helper.ts lines 288-309): the getSeasonsOrEpisodes mapper
currently returns entries with missing id/name/url and leaves relative hrefs
unnormalized. Update getSeasonsOrEpisodes to first filter out any child nodes
that lack a non-empty id, name, or href; then map remaining nodes to objects
with normalized absolute URLs (if href starts with '/' prepend
"https://www.csfd.cz", otherwise keep as-is). Ensure the function returns only
fully populated entries (omit null/undefined properties) and update the return
type if necessary so callers receive only valid season/episode objects with
absolute urls.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @src/helpers/movie.helper.ts:
- Around line 109-127: The exported function name getSerieasAndSeasonTitle has a
typo; rename it to getSeriesAndSeasonTitle (update the function declaration and
the export) and update any usages/imports across the codebase to reference
getSeriesAndSeasonTitle so callers don't break; ensure TypeScript
types/signature remain unchanged ({ seriesName: string | null; seasonName:
string | null }) and run tests/compile to catch any remaining references.
- Around line 330-358: Rename the function getSeasonorEpisodeParent to
getSeasonOrEpisodeParent (capital O) throughout the module: update the exported
function declaration name, any internal references, and the export list if
present, and update all call sites that import or use getSeasonorEpisodeParent
so they reference getSeasonOrEpisodeParent instead; keep the function signature
and behavior unchanged while ensuring any tests or imports (e.g., named imports
from movie.helper) are updated to the new identifier.

In @src/services/movie.service.ts:
- Line 102: The parent field is being set for all non-'seriál' types; restrict
it so parent is only populated when type is 'epizoda' or 'série'. Update the
expression that assigns parent (currently calling getSeasonorEpisodeParent(el, {
id: movieId, name: title })) to first check if type === 'epizoda' || type ===
'série' and only then call getSeasonorEpisodeParent; otherwise set parent to
null. Keep references to the same variables (type, el, movieId, title) and the
same helper getSeasonorEpisodeParent.
🧹 Nitpick comments (2)
src/dto/movie.ts (1)

20-24: Consider adding a clarifying comment about type reuse.

Both seasons and episodes use the CSFDSeason[] type, which is correct since they share the same structure. However, a brief JSDoc comment explaining this design choice would help future maintainers understand why episodes aren't a separate type.

📝 Suggested documentation enhancement
  related: CSFDMovieListItem[];
  similar: CSFDMovieListItem[];
+  /** List of seasons (for series with season structure). Uses CSFDSeason for consistency. */
  seasons: CSFDSeason[] | null;
+  /** List of episodes (direct or within season). Uses CSFDSeason for consistency. */
  episodes: CSFDSeason[] | null;
  parent: CSFDParent | null;
src/services/movie.service.ts (1)

100-101: Consider clarifying the dual usage of getSeasonsOrEpisodes.

The same helper function getSeasonsOrEpisodes(el) is used to populate both the seasons and episodes fields, differentiated only by the seasonOrEpisodeListType check. While this may be intentional and functional, it could be confusing for maintainability.

Consider either:

  1. Renaming the helper to reflect its polymorphic nature (e.g., getSeasonOrEpisodeList)
  2. Creating separate helper functions (getSeasons, getEpisodes) for clearer semantics
  3. Adding a comment explaining why the same function is used for both
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2aa67db and 20a5f51.

📒 Files selected for processing (13)
  • demo.ts
  • src/dto/movie.ts
  • src/helpers/global.helper.ts
  • src/helpers/movie.helper.ts
  • src/services/movie.service.ts
  • tests/fetchers.test.ts
  • tests/mocks/series1-season1-episode.mock.ts
  • tests/mocks/series1-season1.mock.ts
  • tests/mocks/series1-seasons.mock.ts
  • tests/mocks/series2-episode.mock.ts
  • tests/mocks/series2-episodes.mock.ts
  • tests/movie.test.ts
  • tests/series.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • demo.ts
  • src/helpers/global.helper.ts
  • tests/mocks/series1-season1-episode.mock.ts
  • tests/mocks/series2-episode.mock.ts
  • tests/movie.test.ts
🧰 Additional context used
🧬 Code graph analysis (3)
tests/series.test.ts (2)
src/dto/movie.ts (1)
  • CSFDMovie (3-25)
src/services/movie.service.ts (1)
  • MovieScraper (35-107)
tests/fetchers.test.ts (3)
src/services/movie.service.ts (1)
  • movie (36-51)
src/dto/movie.ts (1)
  • CSFDMovie (3-25)
src/dto/global.ts (1)
  • CSFDFilmTypes (25-36)
src/helpers/movie.helper.ts (2)
src/dto/movie.ts (8)
  • CSFDCreatorGroups (143-156)
  • CSFDCreatorGroupsEnglish (158-171)
  • CSFDCreatorGroupsSlovak (173-183)
  • CSFDSeason (194-199)
  • CSFDParent (27-30)
  • CSFDMovieCreator (80-91)
  • CSFDBoxContent (192-192)
  • CSFDMovieListItem (93-97)
src/helpers/global.helper.ts (2)
  • parseLastIdFromUrl (14-22)
  • parseIdFromUrl (4-12)
🔇 Additional comments (13)
tests/mocks/series1-season1.mock.ts (1)

1-1357: LGTM - Comprehensive test mock for season page.

This mock provides a realistic HTML fixture for testing season page parsing. The large size is expected for a full CSFD page including navigation, ads, and tracking scripts. The structure aligns with the test requirements for extracting season metadata, episode lists, and parent series information.

tests/fetchers.test.ts (1)

156-253: Excellent test restructuring with clear pattern-based organization.

The refactored test suite clearly separates series patterns and page types, making it much easier to understand the different scenarios:

  • Pattern 1 (The Simpsons): Series → Seasons → Episodes hierarchy
  • Pattern 2 (The Curse): Series → Episodes (no seasons)

Each pattern thoroughly validates the new DTO fields (seasons, episodes, parent, episodeCode, seasonName) across different page types.

src/dto/movie.ts (1)

194-199: LGTM - Clean interface definition.

The CSFDSeason interface appropriately captures season/episode metadata with nullable info field for flexibility.

src/helpers/movie.helper.ts (3)

311-320: LGTM - Clean episode code extraction.

The function correctly extracts the episode code from parentheses in the title text using a regex pattern.


322-328: LGTM - Clear season/episode detection.

The function properly distinguishes between season and episode lists based on the header text.


288-309: Remove the unused serie parameter from the function signature.

The serie parameter is never used in the function body and callers do not pass it. Unlike the similar getSeasonorEpisodeParent function which actively uses its serie parameter, this parameter appears to be dead code and should be removed.

tests/series.test.ts (5)

14-28: Consider the trade-offs of using reflection for testing.

The helper uses reflection ((scraper as any).buildMovie) to access the private buildMovie method. While this works for testing, it has downsides:

  • Fragility: Tests will break if the method signature changes, with no compile-time safety
  • Maintenance burden: The method's privacy contract is circumvented

Alternatives to consider:

  1. Make buildMovie protected instead of private
  2. Create a dedicated testing helper method in the service
  3. Test through the public movie() method with mocked fetchPage

What's your preference for testing private methods? If reflection is the team standard, this is fine. Otherwise, consider refactoring for better type safety.


38-223: Excellent comprehensive test coverage for Pattern 1.

The test suite thoroughly validates all aspects of the series-with-seasons pattern:

  • ✅ Main series page with seasons list
  • ✅ Season page with episodes list
  • ✅ Episode page with full metadata
  • ✅ Parent relationships at each level
  • ✅ Mutual exclusivity of seasons/episodes

The three-level hierarchy is well tested with appropriate assertions for each page type.


232-312: LGTM - Thorough validation of direct-episodes pattern.

Correctly validates series without intermediate season pages, ensuring episodes list is present directly on the main page and seasons remain null.


320-389: LGTM - Episode-without-season validation is complete.

Properly tests the case where an episode belongs directly to a series (no season parent), confirming parent.season is null while parent.series is populated.


394-440: LGTM - Valuable cross-pattern validation tests.

These tests ensure consistency across different series patterns and validate important invariants like mutual exclusivity of seasons/episodes.

src/services/movie.service.ts (2)

6-7: LGTM!

The new helper imports are well-organized and appropriately scoped for the series/season/episode feature.

Also applies to: 27-30


61-65: All type string literals ('série', 'seriál', 'epizoda') used in this code are valid values in the CSFDFilmTypes definition. No action needed.

Comment on lines +109 to +127
export const getSerieasAndSeasonTitle = (
el: HTMLElement
): { seriesName: string | null; seasonName: string | null } => {
const titleElement = el.querySelector('h1');
if (!titleElement) {
return { seriesName: null, seasonName: null };
}

const fullText = titleElement.innerText.trim();

// Check if there's a series part indicated by ' - '
if (fullText.includes(' - ')) {
const [seriesName, seasonName] = fullText.split(' - ').map((part) => part.trim());
return { seriesName, seasonName };
}

// If no series part found, return just the name
return { seriesName: fullText, seasonName: null };
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Fix function name typo: "Serieas" → "Series".

The function name contains a typo: getSerieasAndSeasonTitle should be getSeriesAndSeasonTitle.

📝 Proposed fix
-export const getSerieasAndSeasonTitle = (
+export const getSeriesAndSeasonTitle = (
   el: HTMLElement
 ): { seriesName: string | null; seasonName: string | null } => {
📝 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
export const getSerieasAndSeasonTitle = (
el: HTMLElement
): { seriesName: string | null; seasonName: string | null } => {
const titleElement = el.querySelector('h1');
if (!titleElement) {
return { seriesName: null, seasonName: null };
}
const fullText = titleElement.innerText.trim();
// Check if there's a series part indicated by ' - '
if (fullText.includes(' - ')) {
const [seriesName, seasonName] = fullText.split(' - ').map((part) => part.trim());
return { seriesName, seasonName };
}
// If no series part found, return just the name
return { seriesName: fullText, seasonName: null };
};
export const getSeriesAndSeasonTitle = (
el: HTMLElement
): { seriesName: string | null; seasonName: string | null } => {
const titleElement = el.querySelector('h1');
if (!titleElement) {
return { seriesName: null, seasonName: null };
}
const fullText = titleElement.innerText.trim();
// Check if there's a series part indicated by ' - '
if (fullText.includes(' - ')) {
const [seriesName, seasonName] = fullText.split(' - ').map((part) => part.trim());
return { seriesName, seasonName };
}
// If no series part found, return just the name
return { seriesName: fullText, seasonName: null };
};
🤖 Prompt for AI Agents
In @src/helpers/movie.helper.ts around lines 109 - 127, The exported function
name getSerieasAndSeasonTitle has a typo; rename it to getSeriesAndSeasonTitle
(update the function declaration and the export) and update any usages/imports
across the codebase to reference getSeriesAndSeasonTitle so callers don't break;
ensure TypeScript types/signature remain unchanged ({ seriesName: string | null;
seasonName: string | null }) and run tests/compile to catch any remaining
references.

Comment on lines +330 to +358
export const getSeasonorEpisodeParent = (
el: HTMLElement,
serie?: { id: number; name: string }
): CSFDParent | null => {
// Try h2 first (for episodes), then h1 (for seasons)
let parents = el.querySelectorAll('.film-header h2 a');
if (parents.length === 0) {
parents = el.querySelectorAll('.film-header h1 a');
}

if (parents.length === 0) {
if (!serie) return null;
return { series: serie, season: null };
}

const [parentSeries, parentSeason] = parents;

const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
const seasonId = parseLastIdFromUrl(parentSeason?.getAttribute('href') || '');
const seriesName = parentSeries?.textContent?.trim() || null;
const seasonName = parentSeason?.textContent?.trim() || null;

const series = seriesId && seriesName ? { id: seriesId, name: seriesName } : null;
const season = seasonId && seasonName ? { id: seasonId, name: seasonName } : null;

if (!series && !season) return null;

return { series, season };
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Fix function name typo: "Seasonor" → "SeasonOr".

The function name contains a typo: getSeasonorEpisodeParent should be getSeasonOrEpisodeParent (with capital O).

📝 Proposed fix
-export const getSeasonorEpisodeParent = (
+export const getSeasonOrEpisodeParent = (
   el: HTMLElement,
   serie?: { id: number; name: string }
 ): CSFDParent | null => {
📝 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
export const getSeasonorEpisodeParent = (
el: HTMLElement,
serie?: { id: number; name: string }
): CSFDParent | null => {
// Try h2 first (for episodes), then h1 (for seasons)
let parents = el.querySelectorAll('.film-header h2 a');
if (parents.length === 0) {
parents = el.querySelectorAll('.film-header h1 a');
}
if (parents.length === 0) {
if (!serie) return null;
return { series: serie, season: null };
}
const [parentSeries, parentSeason] = parents;
const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
const seasonId = parseLastIdFromUrl(parentSeason?.getAttribute('href') || '');
const seriesName = parentSeries?.textContent?.trim() || null;
const seasonName = parentSeason?.textContent?.trim() || null;
const series = seriesId && seriesName ? { id: seriesId, name: seriesName } : null;
const season = seasonId && seasonName ? { id: seasonId, name: seasonName } : null;
if (!series && !season) return null;
return { series, season };
};
export const getSeasonOrEpisodeParent = (
el: HTMLElement,
serie?: { id: number; name: string }
): CSFDParent | null => {
// Try h2 first (for episodes), then h1 (for seasons)
let parents = el.querySelectorAll('.film-header h2 a');
if (parents.length === 0) {
parents = el.querySelectorAll('.film-header h1 a');
}
if (parents.length === 0) {
if (!serie) return null;
return { series: serie, season: null };
}
const [parentSeries, parentSeason] = parents;
const seriesId = parseIdFromUrl(parentSeries?.getAttribute('href'));
const seasonId = parseLastIdFromUrl(parentSeason?.getAttribute('href') || '');
const seriesName = parentSeries?.textContent?.trim() || null;
const seasonName = parentSeason?.textContent?.trim() || null;
const series = seriesId && seriesName ? { id: seriesId, name: seriesName } : null;
const season = seasonId && seasonName ? { id: seasonId, name: seasonName } : null;
if (!series && !season) return null;
return { series, season };
};
🤖 Prompt for AI Agents
In @src/helpers/movie.helper.ts around lines 330 - 358, Rename the function
getSeasonorEpisodeParent to getSeasonOrEpisodeParent (capital O) throughout the
module: update the exported function declaration name, any internal references,
and the export list if present, and update all call sites that import or use
getSeasonorEpisodeParent so they reference getSeasonOrEpisodeParent instead;
keep the function signature and behavior unchanged while ensuring any tests or
imports (e.g., named imports from movie.helper) are updated to the new
identifier.

similar: getMovieBoxMovies(asideEl, 'Podobné'),
seasons: seasonOrEpisodeListType === 'seasons' ? getSeasonsOrEpisodes(el) : null,
episodes: seasonOrEpisodeListType === 'episodes' ? getSeasonsOrEpisodes(el) : null,
parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Find the getSeasonorEpisodeParent implementation
echo "=== Looking for getSeasonorEpisodeParent implementation ==="
rg -n "getSeasonorEpisodeParent" --type ts -B 2 -A 10

# Check the movie.service.ts file around line 102
echo -e "\n=== Context around line 102 in movie.service.ts ==="
cat -n src/services/movie.service.ts | sed -n '95,110p'

# Look for type definitions and constants
echo -e "\n=== Type definitions and how types are used ==="
rg -n "type.*seriál|type.*epizoda|type.*série" --type ts

# Check for parent field usage patterns
echo -e "\n=== Where parent is set or used ==="
rg -n "parent.*:" --type ts -C 1

Repository: bartholomej/node-csfd-api

Length of output: 35441


🏁 Script executed:

#!/bin/bash

# Get the full implementation of getSeasonorEpisodeParent
echo "=== Full getSeasonorEpisodeParent implementation ==="
cat -n src/helpers/movie.helper.ts | sed -n '330,365p'

# Look for tests that validate parent field behavior
echo -e "\n=== Tests checking parent field by movie type ==="
rg -n "\.parent" tests/ --type ts -B 5 -A 1

# Check if there are specific tests for film vs episode vs season parent behavior
echo -e "\n=== Series/Episode tests and parent expectations ==="
rg -n "parent.*null|parent.*series" tests/ --type ts

Repository: bartholomej/node-csfd-api

Length of output: 50381


Restrict parent field population to episodes and seasons only.

The current logic calls getSeasonorEpisodeParent() for all non-'seriál' types, including regular films. The parent field should only be populated for 'epizoda' (episodes) and 'série' (seasons), not for standalone films.

Suggested fix
-      parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }),
+      parent: (type === 'epizoda' || type === 'série') ? getSeasonorEpisodeParent(el, { id: movieId, name: title }) : null,
📝 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
parent: (type === 'seriál') ? null : getSeasonorEpisodeParent(el, { id: movieId, name: title }),
parent: (type === 'epizoda' || type === 'série') ? getSeasonorEpisodeParent(el, { id: movieId, name: title }) : null,
🤖 Prompt for AI Agents
In @src/services/movie.service.ts at line 102, The parent field is being set for
all non-'seriál' types; restrict it so parent is only populated when type is
'epizoda' or 'série'. Update the expression that assigns parent (currently
calling getSeasonorEpisodeParent(el, { id: movieId, name: title })) to first
check if type === 'epizoda' || type === 'série' and only then call
getSeasonorEpisodeParent; otherwise set parent to null. Keep references to the
same variables (type, el, movieId, title) and the same helper
getSeasonorEpisodeParent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scrape Episodes and Series ?

3 participants