node-csfd-api 5.5.0 → 5.6.0-next.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.
- package/bin/export-ratings.js +1 -1
- package/bin/mcp-server.js +1 -1
- package/bin/server.js +1 -1
- package/package.js +1 -1
- package/package.json +1 -1
- package/src/fetchers/fetch.polyfill.js +0 -6
- package/src/fetchers/index.js +0 -76
- package/src/helpers/cinema.helper.js +0 -80
- package/src/helpers/creator.helper.js +0 -69
- package/src/helpers/global.helper.js +0 -101
- package/src/helpers/movie.helper.js +0 -302
- package/src/helpers/search-creator.helper.js +0 -17
- package/src/helpers/search-user.helper.js +0 -24
- package/src/helpers/search.helper.js +0 -46
- package/src/helpers/user-ratings.helper.js +0 -35
- package/src/helpers/user-reviews.helper.js +0 -48
- package/src/index.js +0 -65
- package/src/services/cinema.service.js +0 -32
- package/src/services/creator.service.js +0 -33
- package/src/services/movie.service.js +0 -62
- package/src/services/search.service.js +0 -76
- package/src/services/user-ratings.service.js +0 -66
- package/src/services/user-reviews.service.js +0 -68
- package/src/vars.js +0 -22
package/bin/export-ratings.js
CHANGED
package/bin/mcp-server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { csfd } from "../
|
|
2
|
+
import { csfd } from "../index.js";
|
|
3
3
|
import { name, version } from "../package.js";
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
package/bin/server.js
CHANGED
package/package.js
CHANGED
package/package.json
CHANGED
package/src/fetchers/index.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { fetchSafe } from "./fetch.polyfill.js";
|
|
3
|
-
|
|
4
|
-
//#region src/fetchers/index.ts
|
|
5
|
-
const browserProfiles = [
|
|
6
|
-
{
|
|
7
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
8
|
-
"Sec-Ch-Ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
|
|
9
|
-
"Sec-Ch-Ua-Platform": "\"Windows\""
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
|
|
13
|
-
"Sec-Ch-Ua": "\"Google Chrome\";v=\"130\", \"Chromium\";v=\"130\", \"Not_A Brand\";v=\"24\"",
|
|
14
|
-
"Sec-Ch-Ua-Platform": "\"Windows\""
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
18
|
-
"Sec-Ch-Ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
|
|
19
|
-
"Sec-Ch-Ua-Platform": "\"macOS\""
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
|
|
23
|
-
"Sec-Ch-Ua": "\"Google Chrome\";v=\"130\", \"Chromium\";v=\"130\", \"Not_A Brand\";v=\"24\"",
|
|
24
|
-
"Sec-Ch-Ua-Platform": "\"macOS\""
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
|
|
28
|
-
"Sec-Ch-Ua": "\"Microsoft Edge\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
|
|
29
|
-
"Sec-Ch-Ua-Platform": "\"Windows\""
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0",
|
|
33
|
-
"Sec-Ch-Ua": "\"Microsoft Edge\";v=\"130\", \"Chromium\";v=\"130\", \"Not_A Brand\";v=\"24\"",
|
|
34
|
-
"Sec-Ch-Ua-Platform": "\"Windows\""
|
|
35
|
-
}
|
|
36
|
-
];
|
|
37
|
-
const randomProfile = () => browserProfiles[Math.floor(Math.random() * browserProfiles.length)];
|
|
38
|
-
const baseHeaders = {
|
|
39
|
-
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
|
40
|
-
"Accept-Language": "cs-CZ,cs;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
41
|
-
"Accept-Encoding": "gzip, deflate, br",
|
|
42
|
-
"Cache-Control": "max-age=0",
|
|
43
|
-
Connection: "keep-alive",
|
|
44
|
-
"Sec-Ch-Ua-Mobile": "?0",
|
|
45
|
-
"Sec-Fetch-Dest": "document",
|
|
46
|
-
"Sec-Fetch-Mode": "navigate",
|
|
47
|
-
"Sec-Fetch-Site": "none",
|
|
48
|
-
"Sec-Fetch-User": "?1",
|
|
49
|
-
"Upgrade-Insecure-Requests": "1"
|
|
50
|
-
};
|
|
51
|
-
const fetchPage = async (url, optionsRequest) => {
|
|
52
|
-
try {
|
|
53
|
-
const mergedHeaders = new Headers({
|
|
54
|
-
...baseHeaders,
|
|
55
|
-
...randomProfile()
|
|
56
|
-
});
|
|
57
|
-
if (optionsRequest?.headers) new Headers(optionsRequest.headers).forEach((value, key) => mergedHeaders.set(key, value));
|
|
58
|
-
const { headers: _, ...restOptions } = optionsRequest || {};
|
|
59
|
-
const response = await fetchSafe(url, {
|
|
60
|
-
credentials: "omit",
|
|
61
|
-
...restOptions,
|
|
62
|
-
headers: mergedHeaders
|
|
63
|
-
});
|
|
64
|
-
if (!response.ok) throw new Error(`node-csfd-api: Bad response ${response.status} for url: ${url}`);
|
|
65
|
-
const html = await response.text();
|
|
66
|
-
if (html.includes("Making sure you're not a bot!")) console.warn("node-csfd-api: Trap detected.");
|
|
67
|
-
return html;
|
|
68
|
-
} catch (e) {
|
|
69
|
-
if (e instanceof Error) console.error(e.message);
|
|
70
|
-
else console.error(String(e));
|
|
71
|
-
return "Error";
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
//#endregion
|
|
76
|
-
export { fetchPage };
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { parseColor, parseIdFromUrl } from "./global.helper.js";
|
|
3
|
-
|
|
4
|
-
//#region src/helpers/cinema.helper.ts
|
|
5
|
-
const getCinemaColorRating = (el) => {
|
|
6
|
-
const classes = el?.classNames.split(" ") ?? [];
|
|
7
|
-
const last = classes.length ? classes[classes.length - 1] : void 0;
|
|
8
|
-
return last ? parseColor(last) : "unknown";
|
|
9
|
-
};
|
|
10
|
-
const getCinemaId = (el) => {
|
|
11
|
-
return +el?.id?.split("-")[1];
|
|
12
|
-
};
|
|
13
|
-
const getCinemaUrlId = (url) => {
|
|
14
|
-
if (!url) return null;
|
|
15
|
-
return parseIdFromUrl(url);
|
|
16
|
-
};
|
|
17
|
-
const getCinemaCoords = (el) => {
|
|
18
|
-
if (!el) return null;
|
|
19
|
-
const linkMapsEl = el.querySelector("a[href*=\"q=\"]");
|
|
20
|
-
if (!linkMapsEl) return null;
|
|
21
|
-
const [_, latLng] = linkMapsEl.getAttribute("href").split("q=");
|
|
22
|
-
const coords = latLng.split(",");
|
|
23
|
-
if (coords.length !== 2) return null;
|
|
24
|
-
const lat = Number(coords[0]);
|
|
25
|
-
const lng = Number(coords[1]);
|
|
26
|
-
if (Number.isFinite(lat) && Number.isFinite(lng)) return {
|
|
27
|
-
lat,
|
|
28
|
-
lng
|
|
29
|
-
};
|
|
30
|
-
return null;
|
|
31
|
-
};
|
|
32
|
-
const getCinemaUrl = (el) => {
|
|
33
|
-
if (!el) return "";
|
|
34
|
-
return el.querySelector(".cinema-logo a")?.attributes.href ?? "";
|
|
35
|
-
};
|
|
36
|
-
const parseCinema = (el) => {
|
|
37
|
-
const [city, name] = el.querySelector("header h2").innerText.trim().split(" - ");
|
|
38
|
-
return {
|
|
39
|
-
city,
|
|
40
|
-
name
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
const getGroupedFilmsByDate = (el) => {
|
|
44
|
-
const divs = el.querySelectorAll(":scope > div");
|
|
45
|
-
return divs.map((_, index) => index).filter((index) => index % 2 === 0).map((index) => {
|
|
46
|
-
const [date, films] = divs.slice(index, index + 2);
|
|
47
|
-
return {
|
|
48
|
-
date: date?.firstChild?.textContent?.trim() ?? null,
|
|
49
|
-
films: getCinemaFilms("", films)
|
|
50
|
-
};
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
|
-
const getCinemaFilms = (date, el) => {
|
|
54
|
-
return el.querySelectorAll(".cinema-table tr").map((filmNode) => {
|
|
55
|
-
const url = filmNode.querySelector("td.name h3 a")?.attributes.href;
|
|
56
|
-
const id = url ? getCinemaUrlId(url) : null;
|
|
57
|
-
const title = filmNode.querySelector(".name h3")?.text.trim();
|
|
58
|
-
const colorRating = getCinemaColorRating(filmNode.querySelector(".name .icon"));
|
|
59
|
-
const showTimes = filmNode.querySelectorAll(".td-time")?.map((x) => x.textContent.trim());
|
|
60
|
-
const meta = filmNode.querySelectorAll(".td-title span")?.map((x) => x.text.trim());
|
|
61
|
-
return {
|
|
62
|
-
id,
|
|
63
|
-
title,
|
|
64
|
-
url,
|
|
65
|
-
colorRating,
|
|
66
|
-
showTimes,
|
|
67
|
-
meta: parseMeta(meta)
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
};
|
|
71
|
-
const parseMeta = (meta) => {
|
|
72
|
-
const metaConvert = [];
|
|
73
|
-
for (const element of meta) if (element === "T") metaConvert.push("subtitles");
|
|
74
|
-
else if (element === "D") metaConvert.push("dubbing");
|
|
75
|
-
else metaConvert.push(element);
|
|
76
|
-
return metaConvert;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
//#endregion
|
|
80
|
-
export { getCinemaCoords, getCinemaId, getCinemaUrl, getGroupedFilmsByDate, parseCinema };
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { addProtocol, parseColor, parseDate, parseIdFromUrl } from "./global.helper.js";
|
|
3
|
-
|
|
4
|
-
//#region src/helpers/creator.helper.ts
|
|
5
|
-
const getCreatorColorRating = (el) => {
|
|
6
|
-
const classes = el?.classNames.split(" ") ?? [];
|
|
7
|
-
const last = classes[classes.length - 1];
|
|
8
|
-
return parseColor(last);
|
|
9
|
-
};
|
|
10
|
-
const getCreatorId = (url) => {
|
|
11
|
-
return url ? parseIdFromUrl(url) : null;
|
|
12
|
-
};
|
|
13
|
-
const getCreatorName = (el) => {
|
|
14
|
-
return (el?.querySelector("h1"))?.innerText?.trim() ?? null;
|
|
15
|
-
};
|
|
16
|
-
const getCreatorBirthdayInfo = (el) => {
|
|
17
|
-
const infoBlock = el?.querySelector(".creator-profile-details p");
|
|
18
|
-
const text = infoBlock?.innerHTML.trim();
|
|
19
|
-
const birthPlaceRow = infoBlock?.querySelector(".info-place")?.innerText.trim();
|
|
20
|
-
const ageRow = infoBlock?.querySelector(".info")?.innerText.trim();
|
|
21
|
-
let birthday = null;
|
|
22
|
-
if (text) {
|
|
23
|
-
const birthdayRow = text.split("\n").find((x) => x.includes("nar."));
|
|
24
|
-
birthday = birthdayRow ? parseDate(parseBirthday(birthdayRow)) : null;
|
|
25
|
-
}
|
|
26
|
-
const age = ageRow ? +parseAge(ageRow) : null;
|
|
27
|
-
const birthPlace = birthPlaceRow ? parseBirthPlace(birthPlaceRow) : "";
|
|
28
|
-
return {
|
|
29
|
-
birthday,
|
|
30
|
-
age,
|
|
31
|
-
birthPlace
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
const getCreatorBio = (el) => {
|
|
35
|
-
return (el?.querySelector(".article-content p"))?.text?.trim().split("\n")[0]?.trim() || null;
|
|
36
|
-
};
|
|
37
|
-
const getCreatorPhoto = (el) => {
|
|
38
|
-
const src = el?.querySelector("img")?.getAttribute("src");
|
|
39
|
-
return src ? addProtocol(src) : null;
|
|
40
|
-
};
|
|
41
|
-
const parseBirthday = (text) => text.replace(/nar\./g, "").trim();
|
|
42
|
-
const parseAge = (text) => {
|
|
43
|
-
const digits = text.replace(/[^\d]/g, "");
|
|
44
|
-
return digits ? Number(digits) : null;
|
|
45
|
-
};
|
|
46
|
-
const parseBirthPlace = (text) => text.trim().replace(/<br>/g, "").trim();
|
|
47
|
-
const getCreatorFilms = (el) => {
|
|
48
|
-
const filmNodes = el?.querySelector(".updated-box")?.querySelectorAll("table tr") ?? [];
|
|
49
|
-
let yearCache = null;
|
|
50
|
-
return filmNodes.map((filmNode) => {
|
|
51
|
-
const id = getCreatorId(filmNode.querySelector("td.name .film-title-name")?.attributes?.href);
|
|
52
|
-
const title = filmNode.querySelector(".name")?.text?.trim();
|
|
53
|
-
const yearText = filmNode.querySelector(".year")?.text?.trim();
|
|
54
|
-
const year = yearText ? +yearText : null;
|
|
55
|
-
const colorRating = getCreatorColorRating(filmNode.querySelector(".name .icon"));
|
|
56
|
-
if (typeof year === "number" && !isNaN(year)) yearCache = +year;
|
|
57
|
-
const finalYear = year ?? yearCache;
|
|
58
|
-
if (id != null && title && finalYear != null) return {
|
|
59
|
-
id,
|
|
60
|
-
title,
|
|
61
|
-
year: finalYear,
|
|
62
|
-
colorRating
|
|
63
|
-
};
|
|
64
|
-
return null;
|
|
65
|
-
}).filter(Boolean);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
//#endregion
|
|
69
|
-
export { getCreatorBio, getCreatorBirthdayInfo, getCreatorFilms, getCreatorName, getCreatorPhoto };
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
//#region src/helpers/global.helper.ts
|
|
3
|
-
const LANG_PREFIX_REGEX = /^[a-z]{2,3}$/;
|
|
4
|
-
const ISO8601_DURATION_REGEX = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
|
|
5
|
-
const parseIdFromUrl = (url) => {
|
|
6
|
-
if (!url) return null;
|
|
7
|
-
const parts = url.split("/");
|
|
8
|
-
const idParts = parts.filter((p) => /^\d+-/.test(p));
|
|
9
|
-
if (idParts.length > 0) return +idParts[idParts.length - 1].split("-")[0] || null;
|
|
10
|
-
return +parts[LANG_PREFIX_REGEX.test(parts[1]) ? 3 : 2]?.split("-")[0] || null;
|
|
11
|
-
};
|
|
12
|
-
const parseLastIdFromUrl = (url) => {
|
|
13
|
-
if (url) return +(url?.split("/")[3])?.split("-")[0] || null;
|
|
14
|
-
else return null;
|
|
15
|
-
};
|
|
16
|
-
const PAGE_COLORS = {
|
|
17
|
-
"page-lightgrey": "unknown",
|
|
18
|
-
"page-red": "good",
|
|
19
|
-
"page-blue": "average",
|
|
20
|
-
"page-grey": "bad"
|
|
21
|
-
};
|
|
22
|
-
const getColor = (cls) => {
|
|
23
|
-
return PAGE_COLORS[cls] || "unknown";
|
|
24
|
-
};
|
|
25
|
-
const RATING_COLORS = {
|
|
26
|
-
lightgrey: "unknown",
|
|
27
|
-
red: "good",
|
|
28
|
-
blue: "average",
|
|
29
|
-
grey: "bad"
|
|
30
|
-
};
|
|
31
|
-
const parseColor = (quality) => {
|
|
32
|
-
return RATING_COLORS[quality] || "unknown";
|
|
33
|
-
};
|
|
34
|
-
const FILM_TYPES = {
|
|
35
|
-
"TV film": "tv-film",
|
|
36
|
-
pořad: "tv-show",
|
|
37
|
-
seriál: "series",
|
|
38
|
-
"divadelní záznam": "theatrical",
|
|
39
|
-
koncert: "concert",
|
|
40
|
-
série: "season",
|
|
41
|
-
"studentský film": "student-film",
|
|
42
|
-
"amatérský film": "amateur-film",
|
|
43
|
-
"hudební videoklip": "music-video",
|
|
44
|
-
epizoda: "episode",
|
|
45
|
-
"video kompilace": "video-compilation",
|
|
46
|
-
film: "film"
|
|
47
|
-
};
|
|
48
|
-
const parseFilmType = (type) => {
|
|
49
|
-
return FILM_TYPES[type] || "film";
|
|
50
|
-
};
|
|
51
|
-
const addProtocol = (url) => {
|
|
52
|
-
return url.startsWith("//") ? "https:" + url : url;
|
|
53
|
-
};
|
|
54
|
-
const getDuration = (matches) => {
|
|
55
|
-
return {
|
|
56
|
-
sign: matches[1] === void 0 ? "+" : "-",
|
|
57
|
-
years: matches[2] === void 0 ? 0 : matches[2],
|
|
58
|
-
months: matches[3] === void 0 ? 0 : matches[3],
|
|
59
|
-
weeks: matches[4] === void 0 ? 0 : matches[4],
|
|
60
|
-
days: matches[5] === void 0 ? 0 : matches[5],
|
|
61
|
-
hours: matches[6] === void 0 ? 0 : matches[6],
|
|
62
|
-
minutes: matches[7] === void 0 ? 0 : matches[7],
|
|
63
|
-
seconds: matches[8] === void 0 ? 0 : matches[8]
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
const parseISO8601Duration = (iso) => {
|
|
67
|
-
const duration = getDuration(iso.match(ISO8601_DURATION_REGEX));
|
|
68
|
-
return +duration.hours * 60 + +duration.minutes;
|
|
69
|
-
};
|
|
70
|
-
/**
|
|
71
|
-
* Parses a date string into a standardized YYYY-MM-DD format.
|
|
72
|
-
* Supports:
|
|
73
|
-
* - D.M.YYYY
|
|
74
|
-
* - DD.MM.YYYY
|
|
75
|
-
* - D. M. YYYY
|
|
76
|
-
* - MM/DD/YYYY
|
|
77
|
-
* - YYYY
|
|
78
|
-
*/
|
|
79
|
-
const parseDate = (date) => {
|
|
80
|
-
if (!date) return null;
|
|
81
|
-
const cleanDate = date.trim();
|
|
82
|
-
const dateMatch = cleanDate.match(/^(\d{1,2})\.\s*(\d{1,2})\.\s*(\d{4})$/);
|
|
83
|
-
if (dateMatch) {
|
|
84
|
-
const day = dateMatch[1].padStart(2, "0");
|
|
85
|
-
const month = dateMatch[2].padStart(2, "0");
|
|
86
|
-
return `${dateMatch[3]}-${month}-${day}`;
|
|
87
|
-
}
|
|
88
|
-
const slashMatch = cleanDate.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
|
|
89
|
-
if (slashMatch) {
|
|
90
|
-
const month = slashMatch[1].padStart(2, "0");
|
|
91
|
-
const day = slashMatch[2].padStart(2, "0");
|
|
92
|
-
return `${slashMatch[3]}-${month}-${day}`;
|
|
93
|
-
}
|
|
94
|
-
const yearMatch = cleanDate.match(/^(\d{4})$/);
|
|
95
|
-
if (yearMatch) return `${yearMatch[1]}-01-01`;
|
|
96
|
-
return null;
|
|
97
|
-
};
|
|
98
|
-
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
99
|
-
|
|
100
|
-
//#endregion
|
|
101
|
-
export { addProtocol, getColor, parseColor, parseDate, parseFilmType, parseISO8601Duration, parseIdFromUrl, parseLastIdFromUrl, sleep };
|
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { addProtocol, getColor, parseDate, parseFilmType, parseISO8601Duration, parseIdFromUrl, parseLastIdFromUrl } from "./global.helper.js";
|
|
3
|
-
|
|
4
|
-
//#region src/helpers/movie.helper.ts
|
|
5
|
-
const CREATOR_LABELS = {
|
|
6
|
-
en: {
|
|
7
|
-
directors: "Directed by",
|
|
8
|
-
writers: "Screenplay",
|
|
9
|
-
cinematography: "Cinematography",
|
|
10
|
-
music: "Composer",
|
|
11
|
-
actors: "Cast",
|
|
12
|
-
basedOn: "Based on",
|
|
13
|
-
producers: "Produced by",
|
|
14
|
-
filmEditing: "Editing",
|
|
15
|
-
costumeDesign: "Costumes",
|
|
16
|
-
productionDesign: "Production design",
|
|
17
|
-
casting: "Casting",
|
|
18
|
-
sound: "Sound",
|
|
19
|
-
makeup: "Make-up"
|
|
20
|
-
},
|
|
21
|
-
cs: {
|
|
22
|
-
directors: "Režie",
|
|
23
|
-
writers: "Scénář",
|
|
24
|
-
cinematography: "Kamera",
|
|
25
|
-
music: "Hudba",
|
|
26
|
-
actors: "Hrají",
|
|
27
|
-
basedOn: "Předloha",
|
|
28
|
-
producers: "Produkce",
|
|
29
|
-
filmEditing: "Střih",
|
|
30
|
-
costumeDesign: "Kostýmy",
|
|
31
|
-
productionDesign: "Scénografie",
|
|
32
|
-
casting: "Casting",
|
|
33
|
-
sound: "Zvuk",
|
|
34
|
-
makeup: "Masky"
|
|
35
|
-
},
|
|
36
|
-
sk: {
|
|
37
|
-
directors: "Réžia",
|
|
38
|
-
writers: "Scenár",
|
|
39
|
-
cinematography: "Kamera",
|
|
40
|
-
music: "Hudba",
|
|
41
|
-
actors: "Hrajú",
|
|
42
|
-
basedOn: "Predloha",
|
|
43
|
-
producers: "Produkcia",
|
|
44
|
-
filmEditing: "Strih",
|
|
45
|
-
costumeDesign: "Kostýmy",
|
|
46
|
-
productionDesign: "Scénografia",
|
|
47
|
-
casting: "Casting",
|
|
48
|
-
sound: "Zvuk",
|
|
49
|
-
makeup: "Masky"
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
/**
|
|
53
|
-
* Maps language-specific movie creator group labels.
|
|
54
|
-
* @param language - The language code (e.g., 'en', 'cs')
|
|
55
|
-
* @param key - The key of the creator group (e.g., 'directors', 'writers')
|
|
56
|
-
* @returns The localized label for the creator group
|
|
57
|
-
*/
|
|
58
|
-
const getLocalizedCreatorLabel = (language, key) => {
|
|
59
|
-
return (CREATOR_LABELS[language || "cs"] || CREATOR_LABELS["cs"])[key];
|
|
60
|
-
};
|
|
61
|
-
const getSeriesAndSeasonTitle = (el) => {
|
|
62
|
-
const titleElement = el.querySelector("h1");
|
|
63
|
-
if (!titleElement) return {
|
|
64
|
-
seriesName: null,
|
|
65
|
-
seasonName: null
|
|
66
|
-
};
|
|
67
|
-
const fullText = titleElement.innerText.trim();
|
|
68
|
-
if (fullText.includes(" - ")) {
|
|
69
|
-
const [seriesName, seasonName] = fullText.split(" - ").map((part) => part.trim());
|
|
70
|
-
return {
|
|
71
|
-
seriesName,
|
|
72
|
-
seasonName
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
seriesName: fullText,
|
|
77
|
-
seasonName: null
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
const getMovieTitle = (el) => {
|
|
81
|
-
return el.querySelector("h1").innerText.split(`(`)[0].trim();
|
|
82
|
-
};
|
|
83
|
-
const getMovieGenres = (el) => {
|
|
84
|
-
return el.querySelector(".genres").textContent.split(" / ");
|
|
85
|
-
};
|
|
86
|
-
const getMovieOrigins = (el) => {
|
|
87
|
-
return el.querySelector(".origin").textContent.split(",")[0].split(" / ");
|
|
88
|
-
};
|
|
89
|
-
const getMovieColorRating = (bodyClasses) => {
|
|
90
|
-
return getColor(bodyClasses[1]);
|
|
91
|
-
};
|
|
92
|
-
const getMovieRating = (el) => {
|
|
93
|
-
const rating = el.querySelector(".film-rating-average").textContent?.replace(/%/g, "").trim();
|
|
94
|
-
const ratingInt = parseInt(rating);
|
|
95
|
-
if (Number.isInteger(ratingInt)) return ratingInt;
|
|
96
|
-
else return null;
|
|
97
|
-
};
|
|
98
|
-
const getMovieRatingCount = (el) => {
|
|
99
|
-
const ratingCount = +(el.querySelector(".box-rating-container .counter")?.textContent)?.replace(/[(\s)]/g, "");
|
|
100
|
-
if (Number.isInteger(ratingCount)) return ratingCount;
|
|
101
|
-
else return null;
|
|
102
|
-
};
|
|
103
|
-
const getMovieYear = (jsonLd) => {
|
|
104
|
-
if (jsonLd && jsonLd.dateCreated) return +jsonLd.dateCreated;
|
|
105
|
-
return null;
|
|
106
|
-
};
|
|
107
|
-
const getMovieDuration = (jsonLd, el) => {
|
|
108
|
-
if (jsonLd && jsonLd.duration) try {
|
|
109
|
-
return parseISO8601Duration(jsonLd.duration);
|
|
110
|
-
} catch (e) {}
|
|
111
|
-
try {
|
|
112
|
-
const timeString = el.querySelector(".origin").innerText.split(",");
|
|
113
|
-
if (timeString.length > 2) {
|
|
114
|
-
const hoursMins = timeString.pop().trim().split("(")[0].trim().split("min")[0].split("h");
|
|
115
|
-
return hoursMins.length > 1 ? +hoursMins[0] * 60 + +hoursMins[1] : +hoursMins[0];
|
|
116
|
-
} else return null;
|
|
117
|
-
} catch (error) {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
const getMovieTitlesOther = (el) => {
|
|
122
|
-
const namesNode = el.querySelectorAll(".film-names li");
|
|
123
|
-
if (!namesNode.length) return [];
|
|
124
|
-
return namesNode.map((el) => {
|
|
125
|
-
const country = el.querySelector("img.flag").attributes.alt;
|
|
126
|
-
const title = el.textContent.trim().split("\n")[0];
|
|
127
|
-
if (country && title) return {
|
|
128
|
-
country,
|
|
129
|
-
title
|
|
130
|
-
};
|
|
131
|
-
else return null;
|
|
132
|
-
}).filter((x) => x);
|
|
133
|
-
};
|
|
134
|
-
const getMoviePoster = (el) => {
|
|
135
|
-
const poster = el.querySelector(".film-posters img");
|
|
136
|
-
if (poster) if (poster.classNames?.includes("empty-image")) return null;
|
|
137
|
-
else return addProtocol(poster.attributes.src.split("?")[0].replace(/\/w140\//, "/w1080/"));
|
|
138
|
-
else return null;
|
|
139
|
-
};
|
|
140
|
-
const getMovieRandomPhoto = (el) => {
|
|
141
|
-
const image = el.querySelector(".gallery-item picture img")?.attributes?.src;
|
|
142
|
-
if (image) return image.replace(/\/w663\//, "/w1326/");
|
|
143
|
-
else return null;
|
|
144
|
-
};
|
|
145
|
-
const getMovieTrivia = (el) => {
|
|
146
|
-
const triviaNodes = el.querySelectorAll(".article-trivia ul li");
|
|
147
|
-
if (triviaNodes?.length) return triviaNodes.map((node) => node.textContent.trim().replace(/(\r\n|\n|\r|\t)/gm, ""));
|
|
148
|
-
else return null;
|
|
149
|
-
};
|
|
150
|
-
const getMovieDescriptions = (el) => {
|
|
151
|
-
return el.querySelectorAll(".body--plots .plot-full p, .body--plots .plots .plots-item p").map((movie) => movie.textContent?.trim().replace(/(\r\n|\n|\r|\t)/gm, ""));
|
|
152
|
-
};
|
|
153
|
-
const parseMoviePeople = (el) => {
|
|
154
|
-
return el.querySelectorAll("a").filter((x) => x.classNames.length === 0).map((person) => {
|
|
155
|
-
return {
|
|
156
|
-
id: parseIdFromUrl(person.attributes.href),
|
|
157
|
-
name: person.innerText.trim(),
|
|
158
|
-
url: `https://www.csfd.cz${person.attributes.href}`
|
|
159
|
-
};
|
|
160
|
-
});
|
|
161
|
-
};
|
|
162
|
-
const getMovieCreators = (el, options) => {
|
|
163
|
-
const creators = {
|
|
164
|
-
directors: [],
|
|
165
|
-
writers: [],
|
|
166
|
-
cinematography: [],
|
|
167
|
-
music: [],
|
|
168
|
-
actors: [],
|
|
169
|
-
basedOn: [],
|
|
170
|
-
producers: [],
|
|
171
|
-
filmEditing: [],
|
|
172
|
-
costumeDesign: [],
|
|
173
|
-
productionDesign: []
|
|
174
|
-
};
|
|
175
|
-
const groups = el.querySelectorAll(".creators h4");
|
|
176
|
-
const localizedLabels = [
|
|
177
|
-
"directors",
|
|
178
|
-
"writers",
|
|
179
|
-
"cinematography",
|
|
180
|
-
"music",
|
|
181
|
-
"actors",
|
|
182
|
-
"basedOn",
|
|
183
|
-
"producers",
|
|
184
|
-
"filmEditing",
|
|
185
|
-
"costumeDesign",
|
|
186
|
-
"productionDesign"
|
|
187
|
-
].map((key) => ({
|
|
188
|
-
key,
|
|
189
|
-
label: getLocalizedCreatorLabel(options?.language, key)
|
|
190
|
-
}));
|
|
191
|
-
for (const group of groups) {
|
|
192
|
-
const text = group.textContent.trim();
|
|
193
|
-
for (const { key, label } of localizedLabels) if (text.includes(label)) {
|
|
194
|
-
if (group.parentNode) creators[key] = parseMoviePeople(group.parentNode);
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return creators;
|
|
199
|
-
};
|
|
200
|
-
const getSeasonsOrEpisodes = (el) => {
|
|
201
|
-
const childrenList = el.querySelector(".film-episodes-list");
|
|
202
|
-
if (!childrenList) return null;
|
|
203
|
-
const childrenNodes = childrenList.querySelectorAll(".film-title");
|
|
204
|
-
if (!childrenNodes?.length) return [];
|
|
205
|
-
return childrenNodes.map((season) => {
|
|
206
|
-
const nameContainer = season.querySelector(".film-title-name");
|
|
207
|
-
const infoContainer = season.querySelector(".info");
|
|
208
|
-
const href = nameContainer?.getAttribute("href");
|
|
209
|
-
const url = href ? href.startsWith("/") ? `https://www.csfd.cz${href}` : href : null;
|
|
210
|
-
return {
|
|
211
|
-
id: parseLastIdFromUrl(href || ""),
|
|
212
|
-
title: nameContainer?.textContent?.trim() || null,
|
|
213
|
-
url,
|
|
214
|
-
info: infoContainer?.textContent?.replace(/[{()}]/g, "").trim() || null
|
|
215
|
-
};
|
|
216
|
-
});
|
|
217
|
-
};
|
|
218
|
-
const getEpisodeCode = (el) => {
|
|
219
|
-
const filmHeaderName = el.querySelector(".film-header-name h1");
|
|
220
|
-
if (!filmHeaderName) return null;
|
|
221
|
-
const match = (filmHeaderName.textContent?.trim() || "").match(/\(([^)]+)\)/);
|
|
222
|
-
return match ? match[1] : null;
|
|
223
|
-
};
|
|
224
|
-
const detectSeasonOrEpisodeListType = (el) => {
|
|
225
|
-
const headerText = el.querySelector(".box-header h3")?.innerText.trim() ?? "";
|
|
226
|
-
if (headerText.includes("Série")) return "seasons";
|
|
227
|
-
if (headerText.startsWith("Epizody")) return "episodes";
|
|
228
|
-
return null;
|
|
229
|
-
};
|
|
230
|
-
const getSeasonOrEpisodeParent = (el) => {
|
|
231
|
-
let parents = el.querySelectorAll(".film-header h2 a");
|
|
232
|
-
if (parents.length === 0) parents = el.querySelectorAll(".film-header h1 a");
|
|
233
|
-
if (parents.length === 0) return null;
|
|
234
|
-
const [parentSeries, parentSeason] = parents;
|
|
235
|
-
const seriesId = parseIdFromUrl(parentSeries?.getAttribute("href"));
|
|
236
|
-
const seasonId = parseLastIdFromUrl(parentSeason?.getAttribute("href") || "");
|
|
237
|
-
const seriesTitle = parentSeries?.textContent?.trim() || null;
|
|
238
|
-
const seasonTitle = parentSeason?.textContent?.trim() || null;
|
|
239
|
-
const series = seriesId && seriesTitle ? {
|
|
240
|
-
id: seriesId,
|
|
241
|
-
title: seriesTitle
|
|
242
|
-
} : null;
|
|
243
|
-
const season = seasonId && seasonTitle ? {
|
|
244
|
-
id: seasonId,
|
|
245
|
-
title: seasonTitle
|
|
246
|
-
} : null;
|
|
247
|
-
if (!series && !season) return null;
|
|
248
|
-
return {
|
|
249
|
-
series,
|
|
250
|
-
season
|
|
251
|
-
};
|
|
252
|
-
};
|
|
253
|
-
const getMovieType = (el) => {
|
|
254
|
-
return parseFilmType(el.querySelector(".film-header-name .type")?.innerText?.replace(/[{()}]/g, "") || "film");
|
|
255
|
-
};
|
|
256
|
-
const getMovieVods = (el) => {
|
|
257
|
-
let vods = [];
|
|
258
|
-
if (el) vods = el.querySelectorAll(".box-buttons-vod .vod-badge a").map((btn) => {
|
|
259
|
-
return {
|
|
260
|
-
title: btn.textContent.trim(),
|
|
261
|
-
url: btn.attributes.href
|
|
262
|
-
};
|
|
263
|
-
});
|
|
264
|
-
return vods.length ? vods : [];
|
|
265
|
-
};
|
|
266
|
-
const getBoxContent = (el, box) => {
|
|
267
|
-
return el.querySelectorAll("section.box .box-header").find((header) => header.querySelector("h3")?.textContent.trim().includes(box))?.parentNode;
|
|
268
|
-
};
|
|
269
|
-
const getMovieBoxMovies = (el, boxName) => {
|
|
270
|
-
const movieListItem = [];
|
|
271
|
-
const movieTitleNodes = getBoxContent(el, boxName)?.querySelectorAll(".article-header .film-title-name");
|
|
272
|
-
if (movieTitleNodes?.length) for (const item of movieTitleNodes) movieListItem.push({
|
|
273
|
-
id: parseIdFromUrl(item.attributes.href),
|
|
274
|
-
title: item.textContent.trim(),
|
|
275
|
-
url: `https://www.csfd.cz${item.attributes.href}`
|
|
276
|
-
});
|
|
277
|
-
return movieListItem;
|
|
278
|
-
};
|
|
279
|
-
const getMoviePremieres = (el) => {
|
|
280
|
-
const premiereNodes = el.querySelectorAll(".box-premieres li");
|
|
281
|
-
const premiere = [];
|
|
282
|
-
for (const premiereNode of premiereNodes) {
|
|
283
|
-
const title = premiereNode.querySelector("p + span").attributes.title;
|
|
284
|
-
if (title) {
|
|
285
|
-
const [dateRaw, ...company] = title?.split(" ");
|
|
286
|
-
const date = parseDate(dateRaw);
|
|
287
|
-
if (date) premiere.push({
|
|
288
|
-
country: premiereNode.querySelector(".flag")?.attributes.title || null,
|
|
289
|
-
format: premiereNode.querySelector("p").textContent.trim()?.split(" od")[0],
|
|
290
|
-
date,
|
|
291
|
-
company: company.join(" ")
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
return premiere;
|
|
296
|
-
};
|
|
297
|
-
const getMovieTags = (el) => {
|
|
298
|
-
return el.querySelectorAll(".box-content a[href*=\"/tag/\"]").map((tag) => tag.textContent);
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
//#endregion
|
|
302
|
-
export { detectSeasonOrEpisodeListType, getEpisodeCode, getMovieBoxMovies, getMovieColorRating, getMovieCreators, getMovieDescriptions, getMovieDuration, getMovieGenres, getMovieOrigins, getMoviePoster, getMoviePremieres, getMovieRandomPhoto, getMovieRating, getMovieRatingCount, getMovieTags, getMovieTitle, getMovieTitlesOther, getMovieTrivia, getMovieType, getMovieVods, getMovieYear, getSeasonOrEpisodeParent, getSeasonsOrEpisodes, getSeriesAndSeasonTitle };
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { addProtocol } from "./global.helper.js";
|
|
3
|
-
|
|
4
|
-
//#region src/helpers/search-creator.helper.ts
|
|
5
|
-
const getCreatorName = (el) => {
|
|
6
|
-
return el.querySelector(".user-title").text.trim();
|
|
7
|
-
};
|
|
8
|
-
const getCreatorImage = (el) => {
|
|
9
|
-
const image = el.querySelector("img").attributes.src;
|
|
10
|
-
return addProtocol(image);
|
|
11
|
-
};
|
|
12
|
-
const getCreatorUrl = (el) => {
|
|
13
|
-
return el.querySelector(".user-title a").attributes.href;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
//#endregion
|
|
17
|
-
export { getCreatorImage, getCreatorName, getCreatorUrl };
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { addProtocol } from "./global.helper.js";
|
|
3
|
-
import { NodeType } from "node-html-parser";
|
|
4
|
-
|
|
5
|
-
//#region src/helpers/search-user.helper.ts
|
|
6
|
-
const getUser = (el) => {
|
|
7
|
-
return el.querySelector(".user-title-name").text;
|
|
8
|
-
};
|
|
9
|
-
const getUserRealName = (el) => {
|
|
10
|
-
const p = el.querySelector(".article-content p");
|
|
11
|
-
if (!p) return null;
|
|
12
|
-
const textNodes = p.childNodes.filter((n) => n.nodeType === NodeType.TEXT_NODE && n.rawText.trim() !== "");
|
|
13
|
-
return textNodes.length ? textNodes[0].rawText.trim() : null;
|
|
14
|
-
};
|
|
15
|
-
const getAvatar = (el) => {
|
|
16
|
-
const image = el.querySelector(".article-img img").attributes.src;
|
|
17
|
-
return addProtocol(image);
|
|
18
|
-
};
|
|
19
|
-
const getUserUrl = (el) => {
|
|
20
|
-
return el.querySelector(".user-title-name").attributes.href;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
//#endregion
|
|
24
|
-
export { getAvatar, getUser, getUserRealName, getUserUrl };
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { addProtocol, parseColor, parseFilmType, parseIdFromUrl } from "./global.helper.js";
|
|
3
|
-
|
|
4
|
-
//#region src/helpers/search.helper.ts
|
|
5
|
-
const getSearchType = (el) => {
|
|
6
|
-
const type = el.querySelectorAll(".film-title-info .info")[1];
|
|
7
|
-
return parseFilmType(type?.innerText?.replace(/[{()}]/g, "")?.trim() || "film");
|
|
8
|
-
};
|
|
9
|
-
const getSearchTitle = (el) => {
|
|
10
|
-
return el.querySelector(".film-title-name").text;
|
|
11
|
-
};
|
|
12
|
-
const getSearchYear = (el) => {
|
|
13
|
-
return +el.querySelector(".film-title-info .info")?.innerText.replace(/[{()}]/g, "");
|
|
14
|
-
};
|
|
15
|
-
const getSearchUrl = (el) => {
|
|
16
|
-
return el.querySelector(".film-title-name").attributes.href;
|
|
17
|
-
};
|
|
18
|
-
const getSearchColorRating = (el) => {
|
|
19
|
-
return parseColor(el.querySelector(".article-header i.icon").classNames.split(" ").pop());
|
|
20
|
-
};
|
|
21
|
-
const getSearchPoster = (el) => {
|
|
22
|
-
const image = el.querySelector("img").attributes.src;
|
|
23
|
-
return addProtocol(image);
|
|
24
|
-
};
|
|
25
|
-
const getSearchOrigins = (el) => {
|
|
26
|
-
const originsRaw = el.querySelector(".article-content p .info")?.text;
|
|
27
|
-
if (!originsRaw) return [];
|
|
28
|
-
return (originsRaw?.split(", ")?.[0])?.split("/").map((country) => country.trim());
|
|
29
|
-
};
|
|
30
|
-
const parseSearchPeople = (el, type) => {
|
|
31
|
-
let who;
|
|
32
|
-
if (type === "directors") who = "Režie:";
|
|
33
|
-
if (type === "actors") who = "Hrají:";
|
|
34
|
-
const peopleNode = Array.from(el && el.querySelectorAll(".article-content p")).find((el) => el.textContent.includes(who));
|
|
35
|
-
if (peopleNode) return Array.from(peopleNode.querySelectorAll("a")).map((person) => {
|
|
36
|
-
return {
|
|
37
|
-
id: parseIdFromUrl(person.attributes.href),
|
|
38
|
-
name: person.innerText.trim(),
|
|
39
|
-
url: `https://www.csfd.cz${person.attributes.href}`
|
|
40
|
-
};
|
|
41
|
-
});
|
|
42
|
-
else return [];
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
//#endregion
|
|
46
|
-
export { getSearchColorRating, getSearchOrigins, getSearchPoster, getSearchTitle, getSearchType, getSearchUrl, getSearchYear, parseSearchPeople };
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { parseColor, parseDate, parseFilmType, parseIdFromUrl } from "./global.helper.js";
|
|
3
|
-
|
|
4
|
-
//#region src/helpers/user-ratings.helper.ts
|
|
5
|
-
const getUserRatingId = (el) => {
|
|
6
|
-
const url = el.querySelector("td.name .film-title-name").attributes.href;
|
|
7
|
-
return parseIdFromUrl(url);
|
|
8
|
-
};
|
|
9
|
-
const getUserRating = (el) => {
|
|
10
|
-
const ratingText = el.querySelector("td.star-rating-only .stars").classNames.split(" ").pop();
|
|
11
|
-
return ratingText.includes("stars-") ? +ratingText.split("-").pop() : 0;
|
|
12
|
-
};
|
|
13
|
-
const getUserRatingType = (el) => {
|
|
14
|
-
const typeNode = el.querySelector("td.name .film-title-info .info ~ .info");
|
|
15
|
-
return parseFilmType(typeNode ? typeNode.text : "film");
|
|
16
|
-
};
|
|
17
|
-
const getUserRatingTitle = (el) => {
|
|
18
|
-
return el.querySelector("td.name .film-title-name").text;
|
|
19
|
-
};
|
|
20
|
-
const getUserRatingYear = (el) => {
|
|
21
|
-
const yearNode = el.querySelector("td.name .film-title-info .info");
|
|
22
|
-
return yearNode ? +yearNode.text || null : null;
|
|
23
|
-
};
|
|
24
|
-
const getUserRatingColorRating = (el) => {
|
|
25
|
-
return parseColor(el.querySelector("td.name .icon").classNames.split(" ").pop());
|
|
26
|
-
};
|
|
27
|
-
const getUserRatingDate = (el) => {
|
|
28
|
-
return parseDate(el.querySelector("td.date-only").text.trim());
|
|
29
|
-
};
|
|
30
|
-
const getUserRatingUrl = (el) => {
|
|
31
|
-
return `https://www.csfd.cz${el.querySelector("td.name .film-title-name").attributes.href}`;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
//#endregion
|
|
35
|
-
export { getUserRating, getUserRatingColorRating, getUserRatingDate, getUserRatingId, getUserRatingTitle, getUserRatingType, getUserRatingUrl, getUserRatingYear };
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { parseColor, parseDate, parseFilmType, parseIdFromUrl } from "./global.helper.js";
|
|
3
|
-
|
|
4
|
-
//#region src/helpers/user-reviews.helper.ts
|
|
5
|
-
const getUserReviewId = (el) => {
|
|
6
|
-
const url = el.querySelector(".film-title-name").attributes.href;
|
|
7
|
-
return parseIdFromUrl(url);
|
|
8
|
-
};
|
|
9
|
-
const getUserReviewRating = (el) => {
|
|
10
|
-
const ratingText = el.querySelector(".star-rating .stars").classNames.split(" ").pop();
|
|
11
|
-
return ratingText.includes("stars-") ? +ratingText.split("-").pop() : 0;
|
|
12
|
-
};
|
|
13
|
-
const getUserReviewType = (el) => {
|
|
14
|
-
const typeNode = el.querySelector(".film-title-info .info ~ .info");
|
|
15
|
-
return parseFilmType(typeNode ? typeNode.text.slice(1, -1) : "film");
|
|
16
|
-
};
|
|
17
|
-
const getUserReviewTitle = (el) => {
|
|
18
|
-
return el.querySelector(".film-title-name").text;
|
|
19
|
-
};
|
|
20
|
-
const getUserReviewYear = (el) => {
|
|
21
|
-
const infoSpan = el.querySelector(".film-title-info .info");
|
|
22
|
-
return infoSpan ? +infoSpan.text.replace(/[()]/g, "") || null : null;
|
|
23
|
-
};
|
|
24
|
-
const getUserReviewColorRating = (el) => {
|
|
25
|
-
return parseColor(el.querySelector(".film-title-inline i.icon")?.classNames.split(" ").pop());
|
|
26
|
-
};
|
|
27
|
-
const getUserReviewDate = (el) => {
|
|
28
|
-
return parseDate(el.querySelector(".article-header-date-content .info time").text.trim());
|
|
29
|
-
};
|
|
30
|
-
const getUserReviewUrl = (el) => {
|
|
31
|
-
return `https://www.csfd.cz${el.querySelector(".film-title-name").attributes.href}`;
|
|
32
|
-
};
|
|
33
|
-
const getUserReviewText = (el) => {
|
|
34
|
-
return el.querySelector(".comment").text.trim();
|
|
35
|
-
};
|
|
36
|
-
const getUserReviewPoster = (el) => {
|
|
37
|
-
const img = el.querySelector(".article-img img");
|
|
38
|
-
const srcset = img?.attributes.srcset;
|
|
39
|
-
if (srcset) {
|
|
40
|
-
const poster3x = srcset.split(",").map((s) => s.trim()).find((s) => s.endsWith("3x"));
|
|
41
|
-
if (poster3x) return `https:${poster3x.replace(/\s+3x$/, "").trim()}`;
|
|
42
|
-
}
|
|
43
|
-
const src = img?.attributes.src;
|
|
44
|
-
return src ? `https:${src}` : null;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
//#endregion
|
|
48
|
-
export { getUserReviewColorRating, getUserReviewDate, getUserReviewId, getUserReviewPoster, getUserReviewRating, getUserReviewText, getUserReviewTitle, getUserReviewType, getUserReviewUrl, getUserReviewYear };
|
package/src/index.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { CinemaScraper } from "./services/cinema.service.js";
|
|
3
|
-
import { CreatorScraper } from "./services/creator.service.js";
|
|
4
|
-
import { MovieScraper } from "./services/movie.service.js";
|
|
5
|
-
import { SearchScraper } from "./services/search.service.js";
|
|
6
|
-
import { UserRatingsScraper } from "./services/user-ratings.service.js";
|
|
7
|
-
import { UserReviewsScraper } from "./services/user-reviews.service.js";
|
|
8
|
-
|
|
9
|
-
//#region src/index.ts
|
|
10
|
-
var Csfd = class {
|
|
11
|
-
defaultOptions;
|
|
12
|
-
constructor(userRatingsService, userReviewsService, movieService, creatorService, searchService, cinemaService, defaultOptions) {
|
|
13
|
-
this.userRatingsService = userRatingsService;
|
|
14
|
-
this.userReviewsService = userReviewsService;
|
|
15
|
-
this.movieService = movieService;
|
|
16
|
-
this.creatorService = creatorService;
|
|
17
|
-
this.searchService = searchService;
|
|
18
|
-
this.cinemaService = cinemaService;
|
|
19
|
-
this.defaultOptions = defaultOptions;
|
|
20
|
-
}
|
|
21
|
-
setOptions({ request, language }) {
|
|
22
|
-
if (request !== void 0) this.defaultOptions = {
|
|
23
|
-
...this.defaultOptions,
|
|
24
|
-
request
|
|
25
|
-
};
|
|
26
|
-
if (language !== void 0) this.defaultOptions = {
|
|
27
|
-
...this.defaultOptions,
|
|
28
|
-
language
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
async userRatings(user, config, options) {
|
|
32
|
-
const opts = options ?? this.defaultOptions;
|
|
33
|
-
return this.userRatingsService.userRatings(user, config, opts);
|
|
34
|
-
}
|
|
35
|
-
async userReviews(user, config, options) {
|
|
36
|
-
const opts = options ?? this.defaultOptions;
|
|
37
|
-
return this.userReviewsService.userReviews(user, config, opts);
|
|
38
|
-
}
|
|
39
|
-
async movie(movie, options) {
|
|
40
|
-
const opts = options ?? this.defaultOptions;
|
|
41
|
-
return this.movieService.movie(+movie, opts);
|
|
42
|
-
}
|
|
43
|
-
async creator(creator, options) {
|
|
44
|
-
const opts = options ?? this.defaultOptions;
|
|
45
|
-
return this.creatorService.creator(+creator, opts);
|
|
46
|
-
}
|
|
47
|
-
async search(text, options) {
|
|
48
|
-
const opts = options ?? this.defaultOptions;
|
|
49
|
-
return this.searchService.search(text, opts);
|
|
50
|
-
}
|
|
51
|
-
async cinema(district, period, options) {
|
|
52
|
-
const opts = options ?? this.defaultOptions;
|
|
53
|
-
return this.cinemaService.cinemas(+district, period, opts);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
const movieScraper = new MovieScraper();
|
|
57
|
-
const userRatingsScraper = new UserRatingsScraper();
|
|
58
|
-
const userReviewsScraper = new UserReviewsScraper();
|
|
59
|
-
const cinemaScraper = new CinemaScraper();
|
|
60
|
-
const creatorScraper = new CreatorScraper();
|
|
61
|
-
const searchScraper = new SearchScraper();
|
|
62
|
-
const csfd = new Csfd(userRatingsScraper, userReviewsScraper, movieScraper, creatorScraper, searchScraper, cinemaScraper);
|
|
63
|
-
|
|
64
|
-
//#endregion
|
|
65
|
-
export { csfd };
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { fetchPage } from "../fetchers/index.js";
|
|
3
|
-
import { cinemasUrl } from "../vars.js";
|
|
4
|
-
import { getCinemaCoords, getCinemaId, getCinemaUrl, getGroupedFilmsByDate, parseCinema } from "../helpers/cinema.helper.js";
|
|
5
|
-
import { parse } from "node-html-parser";
|
|
6
|
-
|
|
7
|
-
//#region src/services/cinema.service.ts
|
|
8
|
-
var CinemaScraper = class {
|
|
9
|
-
async cinemas(district = 1, period = "today", options) {
|
|
10
|
-
const contentNode = parse(await fetchPage(cinemasUrl(district, period, { language: options?.language }), { ...options?.request })).querySelectorAll("#snippet--cinemas section[id*=\"cinema-\"]");
|
|
11
|
-
return this.buildCinemas(contentNode);
|
|
12
|
-
}
|
|
13
|
-
buildCinemas(contentNode) {
|
|
14
|
-
const cinemas = [];
|
|
15
|
-
contentNode.forEach((x) => {
|
|
16
|
-
const cinemaInfo = parseCinema(x);
|
|
17
|
-
const cinema = {
|
|
18
|
-
id: getCinemaId(x),
|
|
19
|
-
name: cinemaInfo?.name,
|
|
20
|
-
city: cinemaInfo?.city,
|
|
21
|
-
url: getCinemaUrl(x),
|
|
22
|
-
coords: getCinemaCoords(x),
|
|
23
|
-
screenings: getGroupedFilmsByDate(x)
|
|
24
|
-
};
|
|
25
|
-
cinemas.push(cinema);
|
|
26
|
-
});
|
|
27
|
-
return cinemas;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
//#endregion
|
|
32
|
-
export { CinemaScraper };
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { fetchPage } from "../fetchers/index.js";
|
|
3
|
-
import { creatorUrl } from "../vars.js";
|
|
4
|
-
import { getCreatorBio, getCreatorBirthdayInfo, getCreatorFilms, getCreatorName, getCreatorPhoto } from "../helpers/creator.helper.js";
|
|
5
|
-
import { parse } from "node-html-parser";
|
|
6
|
-
|
|
7
|
-
//#region src/services/creator.service.ts
|
|
8
|
-
var CreatorScraper = class {
|
|
9
|
-
async creator(creatorId, options) {
|
|
10
|
-
const id = Number(creatorId);
|
|
11
|
-
if (isNaN(id)) throw new Error("node-csfd-api: creatorId must be a valid number");
|
|
12
|
-
const creatorHtml = parse(await fetchPage(creatorUrl(id, { language: options?.language }), { ...options?.request }));
|
|
13
|
-
const asideNode = creatorHtml.querySelector(".creator-about");
|
|
14
|
-
const filmsNode = creatorHtml.querySelector(".creator-filmography");
|
|
15
|
-
return this.buildCreator(+creatorId, asideNode, filmsNode);
|
|
16
|
-
}
|
|
17
|
-
buildCreator(id, asideEl, filmsNode) {
|
|
18
|
-
const birthdayInfo = getCreatorBirthdayInfo(asideEl);
|
|
19
|
-
return {
|
|
20
|
-
id,
|
|
21
|
-
name: getCreatorName(asideEl),
|
|
22
|
-
birthday: birthdayInfo?.birthday,
|
|
23
|
-
birthplace: birthdayInfo?.birthPlace,
|
|
24
|
-
photo: getCreatorPhoto(asideEl),
|
|
25
|
-
age: birthdayInfo?.age || null,
|
|
26
|
-
bio: getCreatorBio(asideEl),
|
|
27
|
-
films: getCreatorFilms(filmsNode)
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
//#endregion
|
|
33
|
-
export { CreatorScraper };
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { fetchPage } from "../fetchers/index.js";
|
|
3
|
-
import { movieUrl } from "../vars.js";
|
|
4
|
-
import { detectSeasonOrEpisodeListType, getEpisodeCode, getMovieBoxMovies, getMovieColorRating, getMovieCreators, getMovieDescriptions, getMovieDuration, getMovieGenres, getMovieOrigins, getMoviePoster, getMoviePremieres, getMovieRandomPhoto, getMovieRating, getMovieRatingCount, getMovieTags, getMovieTitle, getMovieTitlesOther, getMovieTrivia, getMovieType, getMovieVods, getMovieYear, getSeasonOrEpisodeParent, getSeasonsOrEpisodes, getSeriesAndSeasonTitle } from "../helpers/movie.helper.js";
|
|
5
|
-
import { parse } from "node-html-parser";
|
|
6
|
-
|
|
7
|
-
//#region src/services/movie.service.ts
|
|
8
|
-
var MovieScraper = class {
|
|
9
|
-
async movie(movieId, options) {
|
|
10
|
-
const id = Number(movieId);
|
|
11
|
-
if (isNaN(id)) throw new Error("node-csfd-api: movieId must be a valid number");
|
|
12
|
-
const movieHtml = parse(await fetchPage(movieUrl(id, { language: options?.language }), { ...options?.request }));
|
|
13
|
-
const pageClasses = movieHtml.querySelector(".page-content").classNames.split(" ");
|
|
14
|
-
const asideNode = movieHtml.querySelector(".aside-movie-profile");
|
|
15
|
-
const movieNode = movieHtml.querySelector(".main-movie-profile");
|
|
16
|
-
const jsonLdString = movieHtml.querySelector("script[type=\"application/ld+json\"]").innerText;
|
|
17
|
-
let jsonLd = null;
|
|
18
|
-
try {
|
|
19
|
-
jsonLd = JSON.parse(jsonLdString);
|
|
20
|
-
} catch (e) {
|
|
21
|
-
console.error("node-csfd-api: Error parsing JSON-LD", e);
|
|
22
|
-
}
|
|
23
|
-
return this.buildMovie(+movieId, movieNode, asideNode, pageClasses, jsonLd, options);
|
|
24
|
-
}
|
|
25
|
-
buildMovie(movieId, el, asideEl, pageClasses, jsonLd, options) {
|
|
26
|
-
const type = getMovieType(el);
|
|
27
|
-
const { seriesName = null, seasonName = null } = type === "season" ? getSeriesAndSeasonTitle(el) : {};
|
|
28
|
-
const seasonOrEpisodeListType = detectSeasonOrEpisodeListType(el);
|
|
29
|
-
return {
|
|
30
|
-
id: movieId,
|
|
31
|
-
title: type === "season" && seriesName ? seriesName : getMovieTitle(el),
|
|
32
|
-
year: getMovieYear(jsonLd),
|
|
33
|
-
duration: getMovieDuration(jsonLd, el),
|
|
34
|
-
descriptions: getMovieDescriptions(el),
|
|
35
|
-
genres: getMovieGenres(el),
|
|
36
|
-
type,
|
|
37
|
-
url: movieUrl(movieId, { language: options?.language }),
|
|
38
|
-
origins: getMovieOrigins(el),
|
|
39
|
-
colorRating: getMovieColorRating(pageClasses),
|
|
40
|
-
rating: getMovieRating(asideEl),
|
|
41
|
-
ratingCount: getMovieRatingCount(asideEl),
|
|
42
|
-
titlesOther: getMovieTitlesOther(el),
|
|
43
|
-
poster: getMoviePoster(el),
|
|
44
|
-
photo: getMovieRandomPhoto(el),
|
|
45
|
-
trivia: getMovieTrivia(el),
|
|
46
|
-
creators: getMovieCreators(el, options),
|
|
47
|
-
vod: getMovieVods(asideEl),
|
|
48
|
-
tags: getMovieTags(asideEl),
|
|
49
|
-
premieres: getMoviePremieres(asideEl),
|
|
50
|
-
related: getMovieBoxMovies(asideEl, "Související"),
|
|
51
|
-
similar: getMovieBoxMovies(asideEl, "Podobné"),
|
|
52
|
-
seasons: seasonOrEpisodeListType === "seasons" ? getSeasonsOrEpisodes(el) : null,
|
|
53
|
-
episodes: seasonOrEpisodeListType === "episodes" ? getSeasonsOrEpisodes(el) : null,
|
|
54
|
-
parent: type === "season" || type === "episode" ? getSeasonOrEpisodeParent(el) : null,
|
|
55
|
-
episodeCode: type === "episode" ? getEpisodeCode(el) : null,
|
|
56
|
-
seasonName
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
//#endregion
|
|
62
|
-
export { MovieScraper };
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { fetchPage } from "../fetchers/index.js";
|
|
3
|
-
import { getUrlByLanguage, searchUrl } from "../vars.js";
|
|
4
|
-
import { parseIdFromUrl } from "../helpers/global.helper.js";
|
|
5
|
-
import { getCreatorImage, getCreatorName, getCreatorUrl } from "../helpers/search-creator.helper.js";
|
|
6
|
-
import { getAvatar, getUser, getUserRealName, getUserUrl } from "../helpers/search-user.helper.js";
|
|
7
|
-
import { getSearchColorRating, getSearchOrigins, getSearchPoster, getSearchTitle, getSearchType, getSearchUrl, getSearchYear, parseSearchPeople } from "../helpers/search.helper.js";
|
|
8
|
-
import { parse } from "node-html-parser";
|
|
9
|
-
|
|
10
|
-
//#region src/services/search.service.ts
|
|
11
|
-
var SearchScraper = class {
|
|
12
|
-
async search(text, options) {
|
|
13
|
-
const html = parse(await fetchPage(searchUrl(text, { language: options?.language }), { ...options?.request }));
|
|
14
|
-
const moviesNode = html.querySelectorAll(".main-movies article");
|
|
15
|
-
const usersNode = html.querySelectorAll(".main-users article");
|
|
16
|
-
const tvSeriesNode = html.querySelectorAll(".main-series article");
|
|
17
|
-
const creatorsNode = html.querySelectorAll(".main-authors article");
|
|
18
|
-
return this.parseSearch(moviesNode, usersNode, tvSeriesNode, creatorsNode, options?.language);
|
|
19
|
-
}
|
|
20
|
-
parseSearch(moviesNode, usersNode, tvSeriesNode, creatorsNode, language) {
|
|
21
|
-
const baseUrl = getUrlByLanguage(language);
|
|
22
|
-
const movies = [];
|
|
23
|
-
const users = [];
|
|
24
|
-
const tvSeries = [];
|
|
25
|
-
const creators = [];
|
|
26
|
-
const movieMapper = (m) => {
|
|
27
|
-
const url = getSearchUrl(m);
|
|
28
|
-
return {
|
|
29
|
-
id: parseIdFromUrl(url),
|
|
30
|
-
title: getSearchTitle(m),
|
|
31
|
-
year: getSearchYear(m),
|
|
32
|
-
url: `${baseUrl}${url}`,
|
|
33
|
-
type: getSearchType(m),
|
|
34
|
-
colorRating: getSearchColorRating(m),
|
|
35
|
-
poster: getSearchPoster(m),
|
|
36
|
-
origins: getSearchOrigins(m),
|
|
37
|
-
creators: {
|
|
38
|
-
directors: parseSearchPeople(m, "directors"),
|
|
39
|
-
actors: parseSearchPeople(m, "actors")
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
const userMapper = (m) => {
|
|
44
|
-
const url = getUserUrl(m);
|
|
45
|
-
return {
|
|
46
|
-
id: parseIdFromUrl(url),
|
|
47
|
-
user: getUser(m),
|
|
48
|
-
userRealName: getUserRealName(m),
|
|
49
|
-
avatar: getAvatar(m),
|
|
50
|
-
url: `${baseUrl}${url}`
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
const creatorMapper = (m) => {
|
|
54
|
-
const url = getCreatorUrl(m);
|
|
55
|
-
return {
|
|
56
|
-
id: parseIdFromUrl(url),
|
|
57
|
-
name: getCreatorName(m),
|
|
58
|
-
image: getCreatorImage(m),
|
|
59
|
-
url: `${baseUrl}${url}`
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
movies.push(...moviesNode.map(movieMapper));
|
|
63
|
-
users.push(...usersNode.map(userMapper));
|
|
64
|
-
tvSeries.push(...tvSeriesNode.map(movieMapper));
|
|
65
|
-
creators.push(...creatorsNode.map(creatorMapper));
|
|
66
|
-
return {
|
|
67
|
-
movies,
|
|
68
|
-
users,
|
|
69
|
-
tvSeries,
|
|
70
|
-
creators
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
//#endregion
|
|
76
|
-
export { SearchScraper };
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { fetchPage } from "../fetchers/index.js";
|
|
3
|
-
import { userRatingsUrl } from "../vars.js";
|
|
4
|
-
import { sleep } from "../helpers/global.helper.js";
|
|
5
|
-
import { getUserRating, getUserRatingColorRating, getUserRatingDate, getUserRatingId, getUserRatingTitle, getUserRatingType, getUserRatingUrl, getUserRatingYear } from "../helpers/user-ratings.helper.js";
|
|
6
|
-
import { parse } from "node-html-parser";
|
|
7
|
-
|
|
8
|
-
//#region src/services/user-ratings.service.ts
|
|
9
|
-
var UserRatingsScraper = class {
|
|
10
|
-
async userRatings(user, config, options) {
|
|
11
|
-
let allMovies = [];
|
|
12
|
-
const pageToFetch = config?.page || 1;
|
|
13
|
-
const url = userRatingsUrl(user, pageToFetch > 1 ? pageToFetch : void 0, { language: options?.language });
|
|
14
|
-
const items = parse(await fetchPage(url, { ...options?.request }));
|
|
15
|
-
const movies = items.querySelectorAll("#snippet--ratings table tr");
|
|
16
|
-
const pagesNode = items.querySelector(".pagination");
|
|
17
|
-
const pages = +pagesNode?.childNodes[pagesNode.childNodes.length - 4].rawText || 1;
|
|
18
|
-
allMovies = this.getPage(config, movies);
|
|
19
|
-
if (config?.allPages) {
|
|
20
|
-
console.log("User", user, url);
|
|
21
|
-
console.log("Fetching all pages", pages);
|
|
22
|
-
for (let i = 2; i <= pages; i++) {
|
|
23
|
-
console.log("Fetching page", i, "out of", pages, "...");
|
|
24
|
-
const movies = parse(await fetchPage(userRatingsUrl(user, i, { language: options?.language }), { ...options?.request })).querySelectorAll("#snippet--ratings table tr");
|
|
25
|
-
allMovies = [...allMovies, ...this.getPage(config, movies)];
|
|
26
|
-
if (config.allPagesDelay) await sleep(config.allPagesDelay);
|
|
27
|
-
}
|
|
28
|
-
return allMovies;
|
|
29
|
-
}
|
|
30
|
-
return allMovies;
|
|
31
|
-
}
|
|
32
|
-
getPage(config, movies) {
|
|
33
|
-
const films = [];
|
|
34
|
-
if (config) {
|
|
35
|
-
if (config.includesOnly?.length && config.excludes?.length) console.warn(`node-csfd-api:
|
|
36
|
-
You can not use both parameters 'includesOnly' and 'excludes'.
|
|
37
|
-
Parameter 'includesOnly' will be used now:`, config.includesOnly);
|
|
38
|
-
}
|
|
39
|
-
const includesSet = config?.includesOnly?.length ? new Set(config.includesOnly) : null;
|
|
40
|
-
const excludesSet = config?.excludes?.length ? new Set(config.excludes) : null;
|
|
41
|
-
for (const el of movies) {
|
|
42
|
-
const type = getUserRatingType(el);
|
|
43
|
-
if (includesSet) {
|
|
44
|
-
if (includesSet.has(type)) films.push(this.buildUserRatings(el, type));
|
|
45
|
-
} else if (excludesSet) {
|
|
46
|
-
if (!excludesSet.has(type)) films.push(this.buildUserRatings(el, type));
|
|
47
|
-
} else films.push(this.buildUserRatings(el, type));
|
|
48
|
-
}
|
|
49
|
-
return films;
|
|
50
|
-
}
|
|
51
|
-
buildUserRatings(el, type) {
|
|
52
|
-
return {
|
|
53
|
-
id: getUserRatingId(el),
|
|
54
|
-
title: getUserRatingTitle(el),
|
|
55
|
-
year: getUserRatingYear(el),
|
|
56
|
-
type,
|
|
57
|
-
url: getUserRatingUrl(el),
|
|
58
|
-
colorRating: getUserRatingColorRating(el),
|
|
59
|
-
userDate: getUserRatingDate(el),
|
|
60
|
-
userRating: getUserRating(el)
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
//#endregion
|
|
66
|
-
export { UserRatingsScraper };
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { fetchPage } from "../fetchers/index.js";
|
|
3
|
-
import { userReviewsUrl } from "../vars.js";
|
|
4
|
-
import { sleep } from "../helpers/global.helper.js";
|
|
5
|
-
import { getUserReviewColorRating, getUserReviewDate, getUserReviewId, getUserReviewPoster, getUserReviewRating, getUserReviewText, getUserReviewTitle, getUserReviewType, getUserReviewUrl, getUserReviewYear } from "../helpers/user-reviews.helper.js";
|
|
6
|
-
import { parse } from "node-html-parser";
|
|
7
|
-
|
|
8
|
-
//#region src/services/user-reviews.service.ts
|
|
9
|
-
var UserReviewsScraper = class {
|
|
10
|
-
async userReviews(user, config, options) {
|
|
11
|
-
let allReviews = [];
|
|
12
|
-
const pageToFetch = config?.page || 1;
|
|
13
|
-
const url = userReviewsUrl(user, pageToFetch > 1 ? pageToFetch : void 0, { language: options?.language });
|
|
14
|
-
const items = parse(await fetchPage(url, { ...options?.request }));
|
|
15
|
-
const reviews = items.querySelectorAll(".user-tab-reviews .article");
|
|
16
|
-
const pagesNode = items.querySelector(".pagination");
|
|
17
|
-
const pages = +pagesNode?.childNodes[pagesNode.childNodes.length - 4].rawText || 1;
|
|
18
|
-
allReviews = this.getPage(config, reviews);
|
|
19
|
-
if (config?.allPages) {
|
|
20
|
-
console.log("User", user, url);
|
|
21
|
-
console.log("Fetching all pages", pages);
|
|
22
|
-
for (let i = 2; i <= pages; i++) {
|
|
23
|
-
console.log("Fetching page", i, "out of", pages, "...");
|
|
24
|
-
const reviews = parse(await fetchPage(userReviewsUrl(user, i, { language: options?.language }), { ...options?.request })).querySelectorAll(".user-tab-reviews .article");
|
|
25
|
-
allReviews = [...allReviews, ...this.getPage(config, reviews)];
|
|
26
|
-
if (config.allPagesDelay) await sleep(config.allPagesDelay);
|
|
27
|
-
}
|
|
28
|
-
return allReviews;
|
|
29
|
-
}
|
|
30
|
-
return allReviews;
|
|
31
|
-
}
|
|
32
|
-
getPage(config, reviews) {
|
|
33
|
-
const films = [];
|
|
34
|
-
if (config) {
|
|
35
|
-
if (config.includesOnly?.length && config.excludes?.length) console.warn(`node-csfd-api:
|
|
36
|
-
You can not use both parameters 'includesOnly' and 'excludes'.
|
|
37
|
-
Parameter 'includesOnly' will be used now:`, config.includesOnly);
|
|
38
|
-
}
|
|
39
|
-
const includesSet = config?.includesOnly?.length ? new Set(config.includesOnly) : null;
|
|
40
|
-
const excludesSet = config?.excludes?.length ? new Set(config.excludes) : null;
|
|
41
|
-
for (const el of reviews) {
|
|
42
|
-
const type = getUserReviewType(el);
|
|
43
|
-
if (includesSet) {
|
|
44
|
-
if (includesSet.has(type)) films.push(this.buildUserReviews(el, type));
|
|
45
|
-
} else if (excludesSet) {
|
|
46
|
-
if (!excludesSet.has(type)) films.push(this.buildUserReviews(el, type));
|
|
47
|
-
} else films.push(this.buildUserReviews(el, type));
|
|
48
|
-
}
|
|
49
|
-
return films;
|
|
50
|
-
}
|
|
51
|
-
buildUserReviews(el, type) {
|
|
52
|
-
return {
|
|
53
|
-
id: getUserReviewId(el),
|
|
54
|
-
title: getUserReviewTitle(el),
|
|
55
|
-
year: getUserReviewYear(el),
|
|
56
|
-
type,
|
|
57
|
-
url: getUserReviewUrl(el),
|
|
58
|
-
colorRating: getUserReviewColorRating(el),
|
|
59
|
-
userDate: getUserReviewDate(el),
|
|
60
|
-
userRating: getUserReviewRating(el),
|
|
61
|
-
text: getUserReviewText(el),
|
|
62
|
-
poster: getUserReviewPoster(el)
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
//#endregion
|
|
68
|
-
export { UserReviewsScraper };
|
package/src/vars.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
//#region src/vars.ts
|
|
3
|
-
const LANGUAGE_DOMAIN_MAP = {
|
|
4
|
-
cs: "https://www.csfd.cz",
|
|
5
|
-
en: "https://www.csfd.cz/en",
|
|
6
|
-
sk: "https://www.csfd.cz/sk"
|
|
7
|
-
};
|
|
8
|
-
let BASE_URL = LANGUAGE_DOMAIN_MAP.cs;
|
|
9
|
-
const getUrlByLanguage = (language) => {
|
|
10
|
-
if (language && language in LANGUAGE_DOMAIN_MAP) return LANGUAGE_DOMAIN_MAP[language];
|
|
11
|
-
return BASE_URL;
|
|
12
|
-
};
|
|
13
|
-
const userUrl = (user, options) => `${getUrlByLanguage(options?.language)}/uzivatel/${encodeURIComponent(user)}`;
|
|
14
|
-
const userRatingsUrl = (user, page, options = {}) => `${userUrl(user, options)}/hodnoceni/${page ? "?page=" + page : ""}`;
|
|
15
|
-
const userReviewsUrl = (user, page, options = {}) => `${userUrl(user, options)}/recenze/${page ? "?page=" + page : ""}`;
|
|
16
|
-
const movieUrl = (movie, options) => `${getUrlByLanguage(options?.language)}/film/${encodeURIComponent(movie)}/prehled/`;
|
|
17
|
-
const creatorUrl = (creator, options) => `${getUrlByLanguage(options?.language)}/tvurce/${encodeURIComponent(creator)}`;
|
|
18
|
-
const cinemasUrl = (district, period, options) => `${getUrlByLanguage(options?.language)}/kino/?period=${period}&district=${district}`;
|
|
19
|
-
const searchUrl = (text, options) => `${getUrlByLanguage(options?.language)}/hledat/?q=${encodeURIComponent(text)}`;
|
|
20
|
-
|
|
21
|
-
//#endregion
|
|
22
|
-
export { cinemasUrl, creatorUrl, getUrlByLanguage, movieUrl, searchUrl, userRatingsUrl, userReviewsUrl };
|