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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/helpers/global.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,29 @@ export const parseDate = (date: string): string | null => {

// Sleep in loop
export const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));

export const extractId = (id: number | string): number | null => {
if (typeof id === 'number') {
return id;
}

if (typeof id !== 'string' || !id.trim()) {
return null;
}

const str = id.trim();

if (/^\d+$/.test(str)) {
return +str;
}

if (str.includes('/')) {
return parseIdFromUrl(str);
}

if (/^\d+-/.test(str)) {
return +str.split('-')[0] || null;
}

return null;
};
9 changes: 4 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ export class Csfd {
return this.userReviewsService.userReviews(user, config, opts);
}

public async movie(movie: number, options?: CSFDOptions): Promise<CSFDMovie> {
public async movie(movie: number | string, options?: CSFDOptions): Promise<CSFDMovie> {
const opts = options ?? this.defaultOptions;
return this.movieService.movie(+movie, opts);
return this.movieService.movie(movie, opts);
}

public async creator(creator: number, options?: CSFDOptions): Promise<CSFDCreator> {
public async creator(creator: number | string, options?: CSFDOptions): Promise<CSFDCreator> {
const opts = options ?? this.defaultOptions;
return this.creatorService.creator(+creator, opts);
return this.creatorService.creator(creator, opts);
}

public async search(text: string, options?: CSFDOptions): Promise<CSFDSearch> {
Expand Down Expand Up @@ -96,4 +96,3 @@ export const csfd = new Csfd(
);

export type * from './dto';

11 changes: 6 additions & 5 deletions src/services/creator.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HTMLElement, parse } from 'node-html-parser';
import { CSFDCreator } from '../dto/creator';
import { fetchPage } from '../fetchers';
import { extractId } from '../helpers/global.helper';
import {
getCreatorBio,
getCreatorBirthdayInfo,
Expand All @@ -12,10 +13,10 @@ import { CSFDOptions } from '../types';
import { creatorUrl } from '../vars';

export class CreatorScraper {
public async creator(creatorId: number, options?: CSFDOptions): Promise<CSFDCreator> {
const id = Number(creatorId);
if (isNaN(id)) {
throw new Error('node-csfd-api: creatorId must be a valid number');
public async creator(creatorId: number | string, options?: CSFDOptions): Promise<CSFDCreator> {
const id = extractId(creatorId);
if (!id) {
throw new Error('node-csfd-api: creatorId must be a valid number, string ID, or CSFD URL');
}
const url = creatorUrl(id, { language: options?.language });
const response = await fetchPage(url, { ...options?.request });
Expand All @@ -24,7 +25,7 @@ export class CreatorScraper {

const asideNode = creatorHtml.querySelector('.creator-about');
const filmsNode = creatorHtml.querySelector('.creator-filmography');
return this.buildCreator(+creatorId, asideNode, filmsNode);
return this.buildCreator(id, asideNode, filmsNode);
}

private buildCreator(id: number, asideEl: HTMLElement, filmsNode: HTMLElement): CSFDCreator {
Expand Down
19 changes: 14 additions & 5 deletions src/services/movie.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { HTMLElement, parse } from 'node-html-parser';
import { CSFDFilmTypes } from '../dto/global';
import { CSFDMovie, MovieJsonLd } from '../dto/movie';
import { fetchPage } from '../fetchers';
import { extractId } from '../helpers/global.helper';
import {
detectSeasonOrEpisodeListType,
getEpisodeCode,
Expand Down Expand Up @@ -32,10 +33,10 @@ import { CSFDOptions } from '../types';
import { LIB_PREFIX, movieUrl } from '../vars';

export class MovieScraper {
public async movie(movieId: number, options?: CSFDOptions): Promise<CSFDMovie> {
const id = Number(movieId);
if (isNaN(id)) {
throw new Error('node-csfd-api: movieId must be a valid number');
public async movie(movieId: number | string, options?: CSFDOptions): Promise<CSFDMovie> {
const id = extractId(movieId);
if (!id) {
throw new Error('node-csfd-api: movieId must be a valid number, string ID, or CSFD URL');
}
const url = movieUrl(id, { language: options?.language });
const response = await fetchPage(url, { ...options?.request });
Expand All @@ -52,7 +53,15 @@ export class MovieScraper {
} catch (e) {
console.error(LIB_PREFIX + ' Error parsing JSON-LD', e);
}
return this.buildMovie(+movieId, movieHtml, movieNode as HTMLElement, asideNode as HTMLElement, pageClasses, jsonLd, options);
return this.buildMovie(
id,
movieHtml,
movieNode as HTMLElement,
asideNode as HTMLElement,
pageClasses,
jsonLd,
options
);
}

private buildMovie(
Expand Down
14 changes: 11 additions & 3 deletions src/services/user-ratings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { HTMLElement, parse } from 'node-html-parser';
import { CSFDColorRating, CSFDFilmTypes, CSFDStars } from '../dto/global';
import { CSFDUserRatingConfig, CSFDUserRatings } from '../dto/user-ratings';
import { fetchPage } from '../fetchers';
import { sleep } from '../helpers/global.helper';
import { extractId, sleep } from '../helpers/global.helper';
import {
getUserRating,
getUserRatingColorRating,
Expand All @@ -22,9 +22,14 @@ export class UserRatingsScraper {
config?: CSFDUserRatingConfig,
options?: CSFDOptions
): Promise<CSFDUserRatings[]> {
const userId = extractId(user);
if (!userId) {
throw new Error('node-csfd-api: user must be a valid number, string ID, or CSFD URL');
}

let allMovies: CSFDUserRatings[] = [];
const pageToFetch = config?.page || 1;
const url = userRatingsUrl(user, pageToFetch > 1 ? pageToFetch : undefined, {
const url = userRatingsUrl(userId, pageToFetch > 1 ? pageToFetch : undefined, {
language: options?.language
});
const response = await fetchPage(url, { ...options?.request });
Expand Down Expand Up @@ -62,7 +67,10 @@ export class UserRatingsScraper {
const films: CSFDUserRatings[] = [];
if (config) {
if (config.includesOnly?.length && config.excludes?.length) {
console.warn(`${LIB_PREFIX} Both 'includesOnly' and 'excludes' were provided. 'includesOnly' takes precedence:`, config.includesOnly);
console.warn(
`${LIB_PREFIX} Both 'includesOnly' and 'excludes' were provided. 'includesOnly' takes precedence:`,
config.includesOnly
);
}
}

Expand Down
14 changes: 11 additions & 3 deletions src/services/user-reviews.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { HTMLElement, parse } from 'node-html-parser';
import { CSFDColorRating, CSFDFilmTypes, CSFDStars } from '../dto/global';
import { CSFDUserReviews, CSFDUserReviewsConfig } from '../dto/user-reviews';
import { fetchPage } from '../fetchers';
import { sleep } from '../helpers/global.helper';
import { extractId, sleep } from '../helpers/global.helper';
import {
getUserReviewColorRating,
getUserReviewDate,
Expand All @@ -24,9 +24,14 @@ export class UserReviewsScraper {
config?: CSFDUserReviewsConfig,
options?: CSFDOptions
): Promise<CSFDUserReviews[]> {
const userId = extractId(user);
if (!userId) {
throw new Error('node-csfd-api: user must be a valid number, string ID, or CSFD URL');
}

let allReviews: CSFDUserReviews[] = [];
const pageToFetch = config?.page || 1;
const url = userReviewsUrl(user, pageToFetch > 1 ? pageToFetch : undefined, {
const url = userReviewsUrl(userId, pageToFetch > 1 ? pageToFetch : undefined, {
language: options?.language
});
const response = await fetchPage(url, { ...options?.request });
Expand Down Expand Up @@ -64,7 +69,10 @@ export class UserReviewsScraper {
const films: CSFDUserReviews[] = [];
if (config) {
if (config.includesOnly?.length && config.excludes?.length) {
console.warn(`${LIB_PREFIX} Both 'includesOnly' and 'excludes' were provided. 'includesOnly' takes precedence:`, config.includesOnly);
console.warn(
`${LIB_PREFIX} Both 'includesOnly' and 'excludes' were provided. 'includesOnly' takes precedence:`,
config.includesOnly
);
}
}

Expand Down
37 changes: 36 additions & 1 deletion tests/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from 'vitest';
import { addProtocol, parseColor, parseIdFromUrl } from '../src/helpers/global.helper';
import { addProtocol, parseColor, parseIdFromUrl, extractId } from '../src/helpers/global.helper';

describe('Add protocol', () => {
test('Handle without protocol', () => {
Expand Down Expand Up @@ -49,6 +49,41 @@ describe('Parse Id', () => {
});
});

describe('Extract Id', () => {
test('Handle plain numbers', () => {
const id = extractId(535121);
expect(id).toBe(535121);
});
test('Handle numeric strings', () => {
const id = extractId('535121');
expect(id).toBe(535121);
});
test('Handle URL slugs', () => {
const id = extractId('535121-blade-runner-2049');
expect(id).toBe(535121);
});
test('Handle full CSFD URLs', () => {
const id = extractId('https://www.csfd.cz/film/535121-blade-runner-2049/prehled/');
expect(id).toBe(535121);
});
test('Handle CSFD URLs path only', () => {
const id = extractId('/film/535121-blade-runner-2049/prehled/');
expect(id).toBe(535121);
});
test('Handle non-id slug starting with numbers', () => {
const id = extractId('3d-printers');
expect(id).toBe(null);
});
test('Handle bad strings', () => {
const id = extractId('bad string');
expect(id).toBe(null);
});
test('Handle null/undefined correctly via TS bypass', () => {
const id = extractId(null as any);
expect(id).toBe(null);
});
});

describe('Parse color', () => {
test('Red', () => {
const url = parseColor('red');
Expand Down
Loading