Skip to content

Conversation

@bartholomej
Copy link
Owner

@bartholomej bartholomej commented Sep 15, 2025

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

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

    • Support for seasons and episodes listings with parent–child relationships and episode identifiers.
    • Extraction and display of episode codes and season names.
    • Detection and parsing of season/episode lists and titles for improved browsing and labeling.
  • Updates

    • Expanded genre list for finer content classification.
    • Content model extended to store seasons, episodes, parent links, episode codes, and season names.
  • Chore

    • Demo example updated to show a different movie.

✏️ 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

Adds DTO fields for series/season/episode metadata, a URL ID parser, multiple DOM parsing helpers to detect/extract seasons, episodes, episode codes and parent info, integrates these into the movie service to populate new fields, and updates the demo invocation (movie ID changed; creator call commented).

Changes

Cohort / File(s) Change Summary
DTO Type Extensions
src/dto/movie.ts
Added exported interfaces CSFDParent, CSFDSeason; extended CSFDMovie with seasons, episodes, parent, episodeCode, seasonName; expanded CSFDGenres union.
Global Parsing Utilities
src/helpers/global.helper.ts
Added exported `parseLastIdFromUrl(url: string): number
Movie Parsing Helpers
src/helpers/movie.helper.ts
Added exported helpers: getSerieasAndSeasonTitle(), getSeasonsOrEpisodes(), getEpisodeCode(), detectSeasonOrEpisodeListType(), getSeasonorEpisodeParent(); imports CSFDParent, CSFDSeason, and parseLastIdFromUrl.
Service Integration
src/services/movie.service.ts
Integrated new helpers: compute movie type/title with series detection, detect season vs. episode lists, populate seasons/episodes, parent, episodeCode, and seasonName; preserved existing fields and general flow.
Demo/Test
demo.ts
Changed demo movie ID from 10135 to 621073; commented out csfd.creator(2120) call.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant Service as movie.service
    participant Helpers as movie.helper
    participant Global as global.helper
    participant DOM as HTML Element

    Client->>Service: parseMovie(el)
    Service->>Helpers: getMovieType(el)
    Helpers-->>Service: type

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

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

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

    alt type == "epizoda"
        Service->>Helpers: getEpisodeCode(el)
        Helpers-->>Service: episodeCode
        Service->>Service: set episodeCode
    end

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

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review focus:
    • DOM traversal/selectors and robustness in src/helpers/movie.helper.ts.
    • Edge-case handling and return types for parseLastIdFromUrl in src/helpers/global.helper.ts.
    • Null-safety and conditional population of new fields in src/services/movie.service.ts.
    • Consistency between DTO changes in src/dto/movie.ts and usages.

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 I hopped through markup, sniffed each row and code,
Seasons and episodes I gently strode.
IDs I plucked from trailing dashes bright,
Parents, codes, and names tucked into sight.
Demo now hops to a new movie light.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: adding support for TV series, seasons, and episodes parsing, which aligns with the substantial new features across multiple files.
Description check ✅ Passed The description covers the main objective, provides usage examples, lists new properties, and correctly identifies the PR as both a Feature and Refactoring, matching the template requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tv-series

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

❌ Patch coverage is 60.31746% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.95%. Comparing base (bcb8e54) to head (fcce368).

Files with missing lines Patch % Lines
src/helpers/movie.helper.ts 52.08% 13 Missing and 10 partials ⚠️
src/helpers/global.helper.ts 71.42% 2 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #60      +/-   ##
==========================================
- Coverage   98.91%   94.95%   -3.96%     
==========================================
  Files          25       25              
  Lines         551      614      +63     
  Branches      124      154      +30     
==========================================
+ Hits          545      583      +38     
- Misses          6       21      +15     
- Partials        0       10      +10     

☔ 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 +27 to 30
export interface CSFDParent {
season: { id: number; name: string; };
series: { id: number; name: string; };
}
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

CSFDParent must allow nullable members.

getSeasonorEpisodeParent can return a missing season or series; current non-null types will misrepresent reality and can fail compilation.

Apply:

-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;
+}

Optionally add a docstring clarifying that at least one is non-null.

🤖 Prompt for AI Agents
In src/dto/movie.ts around lines 27 to 30, the CSFDParent interface currently
declares season and series as non-null but getSeasonorEpisodeParent may return a
missing season or series; update the interface so both members can be nullable
(e.g., season: { id: number; name: string } | null; series: { id: number; name:
string } | null) and add a brief docstring above the interface indicating that
one of the two will be non-null at runtime.

Comment on lines +23 to +39
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 +259
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.

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.

3 participants