node-csfd-api-racintom 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.editorconfig +13 -0
  2. package/.eslintrc.json +33 -0
  3. package/.gitattributes +2 -0
  4. package/.github/FUNDING.yml +8 -0
  5. package/.github/pull_request_template.md +19 -0
  6. package/.github/workflows/main.yml +40 -0
  7. package/.github/workflows/publish.yml +73 -0
  8. package/.github/workflows/test.yml +43 -0
  9. package/.husky/pre-commit +1 -0
  10. package/.idea/codeStyles/Project.xml +72 -0
  11. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  12. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  13. package/.idea/misc.xml +6 -0
  14. package/.idea/modules.xml +8 -0
  15. package/.idea/node-csfd-api.iml +9 -0
  16. package/.idea/prettier.xml +6 -0
  17. package/.idea/vcs.xml +7 -0
  18. package/.nvmrc +1 -0
  19. package/.prettierignore +8 -0
  20. package/.prettierrc +10 -0
  21. package/.vscode/settings.json +16 -0
  22. package/Dockerfile +19 -0
  23. package/demo.ts +35 -0
  24. package/eslint.config.mjs +55 -0
  25. package/package.json +86 -62
  26. package/server.ts +66 -0
  27. package/src/fetchers/fetch.polyfill.ts +7 -0
  28. package/src/fetchers/index.ts +25 -0
  29. package/src/helpers/creator.helper.ts +95 -0
  30. package/src/helpers/global.helper.ts +70 -0
  31. package/src/helpers/movie.helper.ts +276 -0
  32. package/src/helpers/search-user.helper.ts +19 -0
  33. package/src/helpers/search.helper.ts +66 -0
  34. package/src/helpers/user-ratings.helper.ts +62 -0
  35. package/src/index.ts +50 -0
  36. package/src/interfaces/creator.interface.ts +14 -0
  37. package/src/interfaces/global.ts +36 -0
  38. package/src/interfaces/movie.interface.ts +157 -0
  39. package/src/interfaces/search.interface.ts +32 -0
  40. package/src/interfaces/season.interface.ts +12 -0
  41. package/src/interfaces/user-ratings.interface.ts +21 -0
  42. package/src/services/creator.service.ts +34 -0
  43. package/src/services/movie.service.ts +89 -0
  44. package/src/services/search.service.ts +101 -0
  45. package/src/services/season.service.ts +55 -0
  46. package/src/services/user-ratings.service.ts +106 -0
  47. package/src/vars.ts +16 -0
  48. package/tests/creator.test.ts +182 -0
  49. package/tests/fetchers.test.ts +109 -0
  50. package/tests/global.test.ts +35 -0
  51. package/tests/helpers.test.ts +59 -0
  52. package/tests/mocks/creator-actor.html.ts +2244 -0
  53. package/tests/mocks/creator-composer-empty.html.ts +683 -0
  54. package/tests/mocks/creator-director.html.ts +3407 -0
  55. package/tests/mocks/movie1.html.ts +1430 -0
  56. package/tests/mocks/movie2.html.ts +740 -0
  57. package/tests/mocks/movie3.html.ts +1843 -0
  58. package/tests/mocks/movie4.html.ts +1568 -0
  59. package/tests/mocks/search.html.ts +838 -0
  60. package/tests/mocks/series1.html.ts +1540 -0
  61. package/tests/mocks/userRatings.html.ts +1354 -0
  62. package/tests/movie.test.ts +606 -0
  63. package/tests/search.test.ts +379 -0
  64. package/tests/season.test.ts +115 -0
  65. package/tests/services.test.ts +106 -0
  66. package/tests/user-ratings.test.ts +142 -0
  67. package/tests/vars.test.ts +34 -0
  68. package/tsconfig.json +23 -0
  69. package/vitest.config.mts +10 -0
  70. package/fetchers/fetch.polyfill.d.ts +0 -1
  71. package/fetchers/fetch.polyfill.js +0 -9
  72. package/fetchers/index.d.ts +0 -1
  73. package/fetchers/index.js +0 -27
  74. package/helpers/creator.helper.d.ts +0 -17
  75. package/helpers/creator.helper.js +0 -87
  76. package/helpers/global.helper.d.ts +0 -17
  77. package/helpers/global.helper.js +0 -68
  78. package/helpers/movie.helper.d.ts +0 -26
  79. package/helpers/movie.helper.js +0 -270
  80. package/helpers/search-user.helper.d.ts +0 -5
  81. package/helpers/search-user.helper.js +0 -22
  82. package/helpers/search.helper.d.ts +0 -11
  83. package/helpers/search.helper.js +0 -62
  84. package/helpers/user-ratings.helper.d.ts +0 -13
  85. package/helpers/user-ratings.helper.js +0 -61
  86. package/index.d.ts +0 -24
  87. package/index.js +0 -39
  88. package/interfaces/creator.interface.d.ts +0 -12
  89. package/interfaces/creator.interface.js +0 -2
  90. package/interfaces/global.d.ts +0 -22
  91. package/interfaces/global.js +0 -2
  92. package/interfaces/movie.interface.d.ts +0 -73
  93. package/interfaces/movie.interface.js +0 -2
  94. package/interfaces/search.interface.d.ts +0 -27
  95. package/interfaces/search.interface.js +0 -2
  96. package/interfaces/season.interface.d.ts +0 -11
  97. package/interfaces/season.interface.js +0 -2
  98. package/interfaces/user-ratings.interface.d.ts +0 -18
  99. package/interfaces/user-ratings.interface.js +0 -2
  100. package/services/creator.service.d.ts +0 -6
  101. package/services/creator.service.js +0 -32
  102. package/services/movie.service.d.ts +0 -6
  103. package/services/movie.service.js +0 -59
  104. package/services/search.service.d.ts +0 -5
  105. package/services/search.service.js +0 -80
  106. package/services/season.service.d.ts +0 -9
  107. package/services/season.service.js +0 -42
  108. package/services/user-ratings.service.d.ts +0 -7
  109. package/services/user-ratings.service.js +0 -84
  110. package/vars.d.ts +0 -5
  111. package/vars.js +0 -13
package/package.json CHANGED
@@ -1,62 +1,86 @@
1
- {
2
- "name": "node-csfd-api-racintom",
3
- "version": "1.7.0",
4
- "description": "ČSFD API in JavaScript. Amazing NPM library for scrapping csfd.cz :)",
5
- "main": "./index.js",
6
- "author": "sergeras@seznam.cz",
7
- "scripts": {
8
- "start": "tsc -w",
9
- "prebuild": "rimraf dist",
10
- "build": "tsc",
11
- "postbuild": "npm-prepare-dist -s postinstall -s prepare",
12
- "tsc": "tsc",
13
- "demo": "tsx demo",
14
- "lint": "eslint ./src/**/**/* --fix",
15
- "test": "vitest",
16
- "test:coverage": "yarn test run --coverage",
17
- "publish:next": "yarn && yarn build && yarn test:coverage && cd dist && npm publish",
18
- "postversion": "git push && git push --follow-tags",
19
- "release:beta": "npm version preminor --preid=beta -m \"chore(update): prelease %s β\"",
20
- "prerelease:beta": "npm version prerelease --preid=beta -m \"chore(update): prelease %s β\"",
21
- "release:patch": "git checkout master && npm version patch -m \"chore(update): patch release %s 🐛\"",
22
- "release:minor": "git checkout master && npm version minor -m \"chore(update): release %s 🚀\"",
23
- "release:major": "git checkout master && npm version major -m \"chore(update): major release %s 💥\""
24
- },
25
- "publishConfig": {
26
- "access": "public"
27
- },
28
- "dependencies": {
29
- "cross-fetch": "^4.1.0",
30
- "node-html-parser": "^7.0.1"
31
- },
32
- "repository": {
33
- "url": "https://github.com/racintom/node-csfd-api.git",
34
- "type": "git"
35
- },
36
- "bugs": {
37
- "url": "https://github.com/racintom/node-csfd-api/issues"
38
- },
39
- "homepage": "https://github.com/racintom/node-csfd-api#readme",
40
- "keywords": [
41
- "csfd",
42
- "čsfd",
43
- "ratings",
44
- "movies",
45
- "films",
46
- "nodejs",
47
- "node",
48
- "typescript",
49
- "scraper",
50
- "parser",
51
- "api"
52
- ],
53
- "engines": {
54
- "node": ">= 18"
55
- },
56
- "license": "MIT",
57
- "lint-staged": {
58
- "*.ts": "eslint --cache --fix"
59
- },
60
- "module": "./index.js",
61
- "types": "./index.d.ts"
62
- }
1
+ {
2
+ "name": "node-csfd-api-racintom",
3
+ "version": "1.8.0",
4
+ "description": "ČSFD API in JavaScript. Amazing NPM library for scrapping csfd.cz :)",
5
+ "main": "index.js",
6
+ "author": "sergeras@seznam.cz",
7
+ "scripts": {
8
+ "start": "tsc -w",
9
+ "prebuild": "rimraf dist",
10
+ "build": "tsc",
11
+ "postbuild": "npm-prepare-dist -s postinstall -s prepare",
12
+ "tsc": "tsc",
13
+ "demo": "tsx demo",
14
+ "lint": "eslint ./src/**/**/* --fix",
15
+ "test": "vitest",
16
+ "test:coverage": "yarn test run --coverage",
17
+ "publish:next": "yarn && yarn build && yarn test:coverage && cd dist && npm publish",
18
+ "postversion": "git push && git push --follow-tags",
19
+ "release:beta": "npm version preminor --preid=beta -m \"chore(update): prelease %s β\"",
20
+ "prerelease:beta": "npm version prerelease --preid=beta -m \"chore(update): prelease %s β\"",
21
+ "release:patch": "git checkout master && npm version patch -m \"chore(update): patch release %s 🐛\"",
22
+ "release:minor": "git checkout master && npm version minor -m \"chore(update): release %s 🚀\"",
23
+ "release:major": "git checkout master && npm version major -m \"chore(update): major release %s 💥\"",
24
+ "prepare": "husky"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "cross-fetch": "^4.1.0",
31
+ "node-html-parser": "^7.0.1"
32
+ },
33
+ "devDependencies": {
34
+ "@babel/preset-typescript": "^7.27.1",
35
+ "@eslint/eslintrc": "^3.3.1",
36
+ "@eslint/js": "^9.33.0",
37
+ "@types/express": "^5.0.3",
38
+ "@types/node": "^24.2.1",
39
+ "@typescript-eslint/eslint-plugin": "^8.39.0",
40
+ "@typescript-eslint/parser": "^8.39.0",
41
+ "@vitest/coverage-istanbul": "^3.2.4",
42
+ "@vitest/ui": "3.2.4",
43
+ "eslint": "^9.33.0",
44
+ "eslint-config-google": "^0.14.0",
45
+ "eslint-config-prettier": "^10.1.8",
46
+ "eslint-plugin-prettier": "^5.5.4",
47
+ "express": "^5.1.0",
48
+ "globals": "^16.3.0",
49
+ "husky": "^9.1.7",
50
+ "lint-staged": "^16.1.5",
51
+ "npm-prepare-dist": "^0.5.0",
52
+ "prettier": "^3.6.2",
53
+ "rimraf": "^6.0.1",
54
+ "tsx": "^4.20.3",
55
+ "typescript": "^5.9.2",
56
+ "vitest": "^3.2.4"
57
+ },
58
+ "repository": {
59
+ "url": "https://github.com/racintom/node-csfd-api.git",
60
+ "type": "git"
61
+ },
62
+ "bugs": {
63
+ "url": "https://github.com/racintom/node-csfd-api/issues"
64
+ },
65
+ "homepage": "https://github.com/racintom/node-csfd-api#readme",
66
+ "keywords": [
67
+ "csfd",
68
+ "čsfd",
69
+ "ratings",
70
+ "movies",
71
+ "films",
72
+ "nodejs",
73
+ "node",
74
+ "typescript",
75
+ "scraper",
76
+ "parser",
77
+ "api"
78
+ ],
79
+ "engines": {
80
+ "node": ">= 18"
81
+ },
82
+ "license": "MIT",
83
+ "lint-staged": {
84
+ "*.ts": "eslint --cache --fix"
85
+ }
86
+ }
package/server.ts ADDED
@@ -0,0 +1,66 @@
1
+ import express from 'express';
2
+ import packageJson from './package.json';
3
+ import { csfd } from './src';
4
+ import { CSFDFilmTypes } from './src/interfaces/global';
5
+
6
+ const app = express();
7
+ const port = process.env.PORT || 3000;
8
+
9
+ app.get('/', (_, res) => {
10
+ res.json({
11
+ name: packageJson.name,
12
+ version: packageJson.version,
13
+ docs: packageJson.homepage,
14
+ links: ['/movie/:id', '/creator/:id', '/search/:query', '/user-ratings/:id']
15
+ });
16
+ });
17
+
18
+ app.get(['/movie/', '/creator/', '/search/', '/user-ratings/'], (req, res) => {
19
+ res.json({ error: `ID is missing. Provide ID like this: ${req.url}${req.url.endsWith('/') ? '' : '/'}1234` });
20
+ });
21
+
22
+ app.get('/movie/:id', async (req, res) => {
23
+ try {
24
+ const movie = await csfd.movie(+req.params.id);
25
+ res.json(movie);
26
+ } catch (error) {
27
+ res.status(500).json({ error: 'Failed to fetch movie data' });
28
+ }
29
+ });
30
+
31
+ app.get('/creator/:id', async (req, res) => {
32
+ try {
33
+ const result = await csfd.creator(+req.params.id);
34
+ res.json(result);
35
+ } catch (error) {
36
+ res.status(500).json({ error: 'Failed to fetch creator data: ' + error });
37
+ }
38
+ });
39
+
40
+ app.get('/search/:query', async (req, res) => {
41
+ try {
42
+ const result = await csfd.search(req.params.query);
43
+ res.json(result);
44
+ } catch (error) {
45
+ res.status(500).json({ error: 'Failed to fetch search data: ' + error });
46
+ }
47
+ });
48
+
49
+ app.get('/user-ratings/:id', async (req, res) => {
50
+ const { allPages, allPagesDelay, excludes, includesOnly } = req.query;
51
+ try {
52
+ const result = await csfd.userRatings(req.params.id, {
53
+ allPages: allPages === 'true',
54
+ allPagesDelay: allPagesDelay ? +allPagesDelay : undefined,
55
+ excludes: excludes ? (excludes as string).split(',') as CSFDFilmTypes[] : undefined,
56
+ includesOnly: includesOnly ? (includesOnly as string).split(',') as CSFDFilmTypes[] : undefined
57
+ });
58
+ res.json(result);
59
+ } catch (error) {
60
+ res.status(500).json({ error: 'Failed to fetch user-ratings data: ' + error });
61
+ }
62
+ });
63
+
64
+ app.listen(port, () => {
65
+ console.log(`API is running on http://localhost:${port}`);
66
+ });
@@ -0,0 +1,7 @@
1
+ // Check if `fetch` is available in global scope (nodejs 18+) or in window (browser). If not, use cross-fetch polyfill.
2
+ import { fetch as crossFetch } from 'cross-fetch';
3
+ export const fetchSafe =
4
+ (typeof fetch === 'function' && fetch) || // ServiceWorker fetch (Cloud Functions + Chrome extension)
5
+ (typeof global === 'object' && global.fetch) || // Node.js 18+ fetch
6
+ (typeof window !== 'undefined' && window.fetch) || // Browser fetch
7
+ crossFetch; // Polyfill fetch
@@ -0,0 +1,25 @@
1
+ import { fetchSafe } from './fetch.polyfill';
2
+
3
+ const USER_AGENTS = [
4
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
5
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1',
6
+ 'Mozilla/5.0 (Linux; Android 10; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Mobile Safari/537.36',
7
+ 'Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Mobile Safari/537.36'
8
+ ];
9
+
10
+ const headers = {
11
+ 'User-Agent': USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)]
12
+ };
13
+
14
+ export const fetchPage = async (url: string): Promise<string> => {
15
+ try {
16
+ const response = await fetchSafe(url, { headers });
17
+ if (response.status >= 400 && response.status < 600) {
18
+ throw new Error(`node-csfd-api: Bad response ${response.status} for url: ${url}`);
19
+ }
20
+ return await response.text();
21
+ } catch (e) {
22
+ console.error(e);
23
+ return 'Error';
24
+ }
25
+ };
@@ -0,0 +1,95 @@
1
+ import { HTMLElement } from 'node-html-parser';
2
+ import { CSFDCreatorScreening } from '../interfaces/creator.interface';
3
+ import { CSFDColorRating } from '../interfaces/global';
4
+ import { Colors } from '../interfaces/user-ratings.interface';
5
+ import { addProtocol, parseColor, parseIdFromUrl } from './global.helper';
6
+
7
+ export const getColorRating = (el: HTMLElement): CSFDColorRating => {
8
+ return parseColor(el?.classNames.split(' ').pop() as Colors);
9
+ };
10
+
11
+ export const getId = (url: string): number => {
12
+ if (url) {
13
+ return parseIdFromUrl(url);
14
+ }
15
+ return null;
16
+ };
17
+
18
+ export const getName = (el: HTMLElement | null): string => {
19
+ return el.querySelector('h1').innerText.trim();
20
+ };
21
+
22
+ export const getBirthdayInfo = (
23
+ el: HTMLElement | null
24
+ ): { birthday: string; age: number; birthPlace: string } => {
25
+ const infoBlock = el.querySelector('h1 + p');
26
+ const text = infoBlock?.innerHTML.trim();
27
+
28
+ const birthPlaceRow = infoBlock?.querySelector('.info-place')?.innerHTML.trim();
29
+ const ageRow = infoBlock?.querySelector('.info')?.innerHTML.trim();
30
+
31
+ let birthday: string = '';
32
+
33
+ if (text) {
34
+ const parts = text.split('\n');
35
+ const birthdayRow = parts.find((x) => x.includes('nar.'));
36
+ birthday = birthdayRow ? parseBirthday(birthdayRow) : '';
37
+ }
38
+
39
+ const age = ageRow ? +parseAge(ageRow) : null;
40
+ const birthPlace = birthPlaceRow ? parseBirthPlace(birthPlaceRow) : '';
41
+
42
+ return { birthday, age, birthPlace };
43
+ };
44
+
45
+ export const getBio = (el: HTMLElement | null): string => {
46
+ return el.querySelector('.article-content p')?.text.trim().split('\n')[0].trim() || null;
47
+ };
48
+
49
+ export const getPhoto = (el: HTMLElement | null): string => {
50
+ const image = el.querySelector('img').attributes.src;
51
+ return addProtocol(image);
52
+ };
53
+
54
+ export const parseBirthday = (text: string): any => {
55
+ return text.replace(/nar./g, '').trim();
56
+ };
57
+
58
+ export const parseAge = (text: string): any => {
59
+ return text.trim().replace(/\(/g, '').replace(/let\)/g, '').trim();
60
+ };
61
+
62
+ export const parseBirthPlace = (text: string): any => {
63
+ return text.trim().replace(/<br>/g, '').trim();
64
+ };
65
+
66
+ export const getFilms = (el: HTMLElement | null): CSFDCreatorScreening[] => {
67
+ const filmNodes = el.querySelectorAll('.box')[0]?.querySelectorAll('table tr');
68
+ let yearCache: number;
69
+ const films = filmNodes.map((filmNode) => {
70
+ const id = getId(filmNode.querySelector('td.name .film-title-name')?.attributes.href);
71
+ const title = filmNode.querySelector('.name')?.text.trim();
72
+ const year = +filmNode.querySelector('.year')?.text.trim();
73
+ const colorRating = getColorRating(filmNode.querySelector('.name .icon'));
74
+
75
+ // Cache year from previous film because there is a gap between movies with same year
76
+ if (year) {
77
+ yearCache = +year;
78
+ }
79
+
80
+ if (id && title) {
81
+ return {
82
+ id,
83
+ title,
84
+ year: year || yearCache,
85
+ colorRating
86
+ };
87
+ }
88
+ return {};
89
+ });
90
+ // Remove empty objects
91
+ const filmsUnique = films.filter(
92
+ (value) => Object.keys(value).length !== 0
93
+ ) as CSFDCreatorScreening[];
94
+ return filmsUnique;
95
+ };
@@ -0,0 +1,70 @@
1
+ import { CSFDColorRating } from '../interfaces/global';
2
+ import { Colors } from '../interfaces/user-ratings.interface';
3
+
4
+ export const parseIdFromUrl = (url: string): number => {
5
+ if (url) {
6
+ const idSlug = url?.split('/')[2];
7
+ const id = idSlug?.split('-')[0];
8
+ return +id || null;
9
+ } else {
10
+ return null;
11
+ }
12
+ };
13
+
14
+ export const getColor = (cls: string): CSFDColorRating => {
15
+ switch (cls) {
16
+ case 'page-lightgrey':
17
+ return 'unknown';
18
+ case 'page-red':
19
+ return 'good';
20
+ case 'page-blue':
21
+ return 'average';
22
+ case 'page-grey':
23
+ return 'bad';
24
+ default:
25
+ return 'unknown';
26
+ }
27
+ };
28
+
29
+ export const parseColor = (quality: Colors): CSFDColorRating => {
30
+ switch (quality) {
31
+ case 'lightgrey':
32
+ return 'unknown';
33
+ case 'red':
34
+ return 'good';
35
+ case 'blue':
36
+ return 'average';
37
+ case 'grey':
38
+ return 'bad';
39
+ default:
40
+ return 'unknown';
41
+ }
42
+ };
43
+
44
+ export const addProtocol = (url: string): string => {
45
+ return url.startsWith('//') ? 'https:' + url : url;
46
+ };
47
+
48
+ export const getDuration = (matches: any[]) => {
49
+ return {
50
+ sign: matches[1] === undefined ? '+' : '-',
51
+ years: matches[2] === undefined ? 0 : matches[2],
52
+ months: matches[3] === undefined ? 0 : matches[3],
53
+ weeks: matches[4] === undefined ? 0 : matches[4],
54
+ days: matches[5] === undefined ? 0 : matches[5],
55
+ hours: matches[6] === undefined ? 0 : matches[6],
56
+ minutes: matches[7] === undefined ? 0 : matches[7],
57
+ seconds: matches[8] === undefined ? 0 : matches[8]
58
+ };
59
+ };
60
+
61
+ export const parseISO8601Duration = (iso: string): number => {
62
+ const iso8601DurationRegex =
63
+ /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
64
+
65
+ const matches = iso.match(iso8601DurationRegex);
66
+
67
+ const duration = getDuration(matches);
68
+
69
+ return +duration.minutes;
70
+ };