node-csfd-api 4.1.5 → 4.2.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/helpers/creator.helper.js +4 -4
- package/helpers/creator.helper.js.map +1 -1
- package/helpers/creator.helper.mjs +4 -4
- package/helpers/creator.helper.mjs.map +1 -1
- package/helpers/user-ratings.helper.js +2 -2
- package/helpers/user-ratings.helper.js.map +1 -1
- package/helpers/user-ratings.helper.mjs +2 -2
- package/helpers/user-ratings.helper.mjs.map +1 -1
- package/helpers/user-reviews.helper.js +3 -3
- package/helpers/user-reviews.helper.js.map +1 -1
- package/helpers/user-reviews.helper.mjs +3 -3
- package/helpers/user-reviews.helper.mjs.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/mcp-server.js +192 -0
- package/package.json +15 -6
- package/server.js +324 -0
- package/services/user-ratings.service.js +2 -2
- package/services/user-ratings.service.js.map +1 -1
- package/services/user-ratings.service.mjs +2 -2
- package/services/user-ratings.service.mjs.map +1 -1
- package/services/user-reviews.service.js +2 -2
- package/services/user-reviews.service.js.map +1 -1
- package/services/user-reviews.service.mjs +2 -2
- package/services/user-reviews.service.mjs.map +1 -1
|
@@ -13,10 +13,10 @@ const getCreatorName = (el) => {
|
|
|
13
13
|
return (el?.querySelector("h1"))?.innerText?.trim() ?? null;
|
|
14
14
|
};
|
|
15
15
|
const getCreatorBirthdayInfo = (el) => {
|
|
16
|
-
const infoBlock = el.querySelector("
|
|
16
|
+
const infoBlock = el.querySelector(".creator-profile-details p");
|
|
17
17
|
const text = infoBlock?.innerHTML.trim();
|
|
18
|
-
const birthPlaceRow = infoBlock?.querySelector(".info-place")?.
|
|
19
|
-
const ageRow = infoBlock?.querySelector(".info")?.
|
|
18
|
+
const birthPlaceRow = infoBlock?.querySelector(".info-place")?.innerText.trim();
|
|
19
|
+
const ageRow = infoBlock?.querySelector(".info")?.innerText.trim();
|
|
20
20
|
let birthday = "";
|
|
21
21
|
if (text) {
|
|
22
22
|
const birthdayRow = text.split("\n").find((x) => x.includes("nar."));
|
|
@@ -44,7 +44,7 @@ const parseAge = (text) => {
|
|
|
44
44
|
};
|
|
45
45
|
const parseBirthPlace = (text) => text.trim().replace(/<br>/g, "").trim();
|
|
46
46
|
const getCreatorFilms = (el) => {
|
|
47
|
-
const filmNodes = el?.querySelectorAll(".box")?.[0]?.querySelectorAll("table tr") ?? [];
|
|
47
|
+
const filmNodes = el?.querySelectorAll(".updated-box")?.[0]?.querySelectorAll("table tr") ?? [];
|
|
48
48
|
let yearCache = null;
|
|
49
49
|
return filmNodes.map((filmNode) => {
|
|
50
50
|
const id = getCreatorId(filmNode.querySelector("td.name .film-title-name")?.attributes?.href);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"creator.helper.js","names":["parseColor","parseIdFromUrl","addProtocol"],"sources":["../../src/helpers/creator.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDCreatorScreening } from '../dto/creator';\nimport { CSFDColorRating } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { addProtocol, parseColor, parseIdFromUrl } from './global.helper';\n\nconst getCreatorColorRating = (el: HTMLElement | null): CSFDColorRating => {\n const classes: string[] = el?.classNames.split(' ') ?? [];\n const last = classes[classes.length - 1] as CSFDColors | undefined;\n return parseColor(last);\n};\n\nexport const getCreatorId = (url: string | null | undefined): number | null => {\n return url ? parseIdFromUrl(url) : null;\n};\n\nexport const getCreatorName = (el: HTMLElement | null): string | null => {\n const h1 = el?.querySelector('h1');\n return h1?.innerText?.trim() ?? null;\n};\n\nexport const getCreatorBirthdayInfo = (\n el: HTMLElement | null\n): { birthday: string; age: number; birthPlace: string } => {\n const infoBlock = el.querySelector('
|
|
1
|
+
{"version":3,"file":"creator.helper.js","names":["parseColor","parseIdFromUrl","addProtocol"],"sources":["../../src/helpers/creator.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDCreatorScreening } from '../dto/creator';\nimport { CSFDColorRating } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { addProtocol, parseColor, parseIdFromUrl } from './global.helper';\n\nconst getCreatorColorRating = (el: HTMLElement | null): CSFDColorRating => {\n const classes: string[] = el?.classNames.split(' ') ?? [];\n const last = classes[classes.length - 1] as CSFDColors | undefined;\n return parseColor(last);\n};\n\nexport const getCreatorId = (url: string | null | undefined): number | null => {\n return url ? parseIdFromUrl(url) : null;\n};\n\nexport const getCreatorName = (el: HTMLElement | null): string | null => {\n const h1 = el?.querySelector('h1');\n return h1?.innerText?.trim() ?? null;\n};\n\nexport const getCreatorBirthdayInfo = (\n el: HTMLElement | null\n): { birthday: string; age: number; birthPlace: string } => {\n const infoBlock = el.querySelector('.creator-profile-details p');\n const text = infoBlock?.innerHTML.trim();\n const birthPlaceRow = infoBlock?.querySelector('.info-place')?.innerText.trim();\n const ageRow = infoBlock?.querySelector('.info')?.innerText.trim();\n\n let birthday: string = '';\n\n if (text) {\n const parts = text.split('\\n');\n const birthdayRow = parts.find((x) => x.includes('nar.'));\n birthday = birthdayRow ? parseBirthday(birthdayRow) : '';\n }\n\n const age = ageRow ? +parseAge(ageRow) : null;\n const birthPlace = birthPlaceRow ? parseBirthPlace(birthPlaceRow) : '';\n return { birthday, age, birthPlace };\n};\n\nexport const getCreatorBio = (el: HTMLElement | null): string | null => {\n const p = el?.querySelector('.article-content p');\n const first = p?.text?.trim().split('\\n')[0]?.trim();\n return first || null;\n};\n\nexport const getCreatorPhoto = (el: HTMLElement | null): string | null => {\n const src = el?.querySelector('img')?.getAttribute('src');\n return src ? addProtocol(src) : null;\n};\n\nconst parseBirthday = (text: string): string => text.replace(/nar\\./g, '').trim();\n\nconst parseAge = (text: string): number | null => {\n const digits = text.replace(/[^\\d]/g, '');\n return digits ? Number(digits) : null;\n};\n\nconst parseBirthPlace = (text: string): string => text.trim().replace(/<br>/g, '').trim();\n\nexport const getCreatorFilms = (el: HTMLElement | null): CSFDCreatorScreening[] => {\n const filmNodes = el?.querySelectorAll('.updated-box')?.[0]?.querySelectorAll('table tr') ?? [];\n let yearCache: number | null = null;\n\n const films = filmNodes.map((filmNode) => {\n const id = getCreatorId(filmNode.querySelector('td.name .film-title-name')?.attributes?.href);\n const title = filmNode.querySelector('.name')?.text?.trim();\n const yearText = filmNode.querySelector('.year')?.text?.trim();\n const year = yearText ? +yearText : null;\n const colorRating = getCreatorColorRating(filmNode.querySelector('.name .icon'));\n\n // Cache year from previous film because there is a gap between movies with same year\n if (typeof year === 'number' && !isNaN(year)) {\n yearCache = +year;\n }\n\n const finalYear = year ?? yearCache;\n if (id != null && title && finalYear != null) {\n return { id, title, year: finalYear, colorRating };\n }\n return null;\n });\n // Remove empty objects\n const filmsUnique = films.filter(Boolean) as CSFDCreatorScreening[];\n return filmsUnique;\n};\n"],"mappings":";;;AAMA,MAAM,yBAAyB,OAA4C;CACzE,MAAM,UAAoB,IAAI,WAAW,MAAM,IAAI,IAAI,EAAE;CACzD,MAAM,OAAO,QAAQ,QAAQ,SAAS;AACtC,QAAOA,iCAAW,KAAK;;AAGzB,MAAa,gBAAgB,QAAkD;AAC7E,QAAO,MAAMC,qCAAe,IAAI,GAAG;;AAGrC,MAAa,kBAAkB,OAA0C;AAEvE,SADW,IAAI,cAAc,KAAK,GACvB,WAAW,MAAM,IAAI;;AAGlC,MAAa,0BACX,OAC0D;CAC1D,MAAM,YAAY,GAAG,cAAc,6BAA6B;CAChE,MAAM,OAAO,WAAW,UAAU,MAAM;CACxC,MAAM,gBAAgB,WAAW,cAAc,cAAc,EAAE,UAAU,MAAM;CAC/E,MAAM,SAAS,WAAW,cAAc,QAAQ,EAAE,UAAU,MAAM;CAElE,IAAI,WAAmB;AAEvB,KAAI,MAAM;EAER,MAAM,cADQ,KAAK,MAAM,KAAK,CACJ,MAAM,MAAM,EAAE,SAAS,OAAO,CAAC;AACzD,aAAW,cAAc,cAAc,YAAY,GAAG;;CAGxD,MAAM,MAAM,SAAS,CAAC,SAAS,OAAO,GAAG;CACzC,MAAM,aAAa,gBAAgB,gBAAgB,cAAc,GAAG;AACpE,QAAO;EAAE;EAAU;EAAK;EAAY;;AAGtC,MAAa,iBAAiB,OAA0C;AAGtE,SAFU,IAAI,cAAc,qBAAqB,GAChC,MAAM,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,IACpC;;AAGlB,MAAa,mBAAmB,OAA0C;CACxE,MAAM,MAAM,IAAI,cAAc,MAAM,EAAE,aAAa,MAAM;AACzD,QAAO,MAAMC,kCAAY,IAAI,GAAG;;AAGlC,MAAM,iBAAiB,SAAyB,KAAK,QAAQ,UAAU,GAAG,CAAC,MAAM;AAEjF,MAAM,YAAY,SAAgC;CAChD,MAAM,SAAS,KAAK,QAAQ,UAAU,GAAG;AACzC,QAAO,SAAS,OAAO,OAAO,GAAG;;AAGnC,MAAM,mBAAmB,SAAyB,KAAK,MAAM,CAAC,QAAQ,SAAS,GAAG,CAAC,MAAM;AAEzF,MAAa,mBAAmB,OAAmD;CACjF,MAAM,YAAY,IAAI,iBAAiB,eAAe,GAAG,IAAI,iBAAiB,WAAW,IAAI,EAAE;CAC/F,IAAI,YAA2B;AAsB/B,QApBc,UAAU,KAAK,aAAa;EACxC,MAAM,KAAK,aAAa,SAAS,cAAc,2BAA2B,EAAE,YAAY,KAAK;EAC7F,MAAM,QAAQ,SAAS,cAAc,QAAQ,EAAE,MAAM,MAAM;EAC3D,MAAM,WAAW,SAAS,cAAc,QAAQ,EAAE,MAAM,MAAM;EAC9D,MAAM,OAAO,WAAW,CAAC,WAAW;EACpC,MAAM,cAAc,sBAAsB,SAAS,cAAc,cAAc,CAAC;AAGhF,MAAI,OAAO,SAAS,YAAY,CAAC,MAAM,KAAK,CAC1C,aAAY,CAAC;EAGf,MAAM,YAAY,QAAQ;AAC1B,MAAI,MAAM,QAAQ,SAAS,aAAa,KACtC,QAAO;GAAE;GAAI;GAAO,MAAM;GAAW;GAAa;AAEpD,SAAO;GACP,CAEwB,OAAO,QAAQ"}
|
|
@@ -13,10 +13,10 @@ const getCreatorName = (el) => {
|
|
|
13
13
|
return (el?.querySelector("h1"))?.innerText?.trim() ?? null;
|
|
14
14
|
};
|
|
15
15
|
const getCreatorBirthdayInfo = (el) => {
|
|
16
|
-
const infoBlock = el.querySelector("
|
|
16
|
+
const infoBlock = el.querySelector(".creator-profile-details p");
|
|
17
17
|
const text = infoBlock?.innerHTML.trim();
|
|
18
|
-
const birthPlaceRow = infoBlock?.querySelector(".info-place")?.
|
|
19
|
-
const ageRow = infoBlock?.querySelector(".info")?.
|
|
18
|
+
const birthPlaceRow = infoBlock?.querySelector(".info-place")?.innerText.trim();
|
|
19
|
+
const ageRow = infoBlock?.querySelector(".info")?.innerText.trim();
|
|
20
20
|
let birthday = "";
|
|
21
21
|
if (text) {
|
|
22
22
|
const birthdayRow = text.split("\n").find((x) => x.includes("nar."));
|
|
@@ -44,7 +44,7 @@ const parseAge = (text) => {
|
|
|
44
44
|
};
|
|
45
45
|
const parseBirthPlace = (text) => text.trim().replace(/<br>/g, "").trim();
|
|
46
46
|
const getCreatorFilms = (el) => {
|
|
47
|
-
const filmNodes = el?.querySelectorAll(".box")?.[0]?.querySelectorAll("table tr") ?? [];
|
|
47
|
+
const filmNodes = el?.querySelectorAll(".updated-box")?.[0]?.querySelectorAll("table tr") ?? [];
|
|
48
48
|
let yearCache = null;
|
|
49
49
|
return filmNodes.map((filmNode) => {
|
|
50
50
|
const id = getCreatorId(filmNode.querySelector("td.name .film-title-name")?.attributes?.href);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"creator.helper.mjs","names":[],"sources":["../../src/helpers/creator.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDCreatorScreening } from '../dto/creator';\nimport { CSFDColorRating } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { addProtocol, parseColor, parseIdFromUrl } from './global.helper';\n\nconst getCreatorColorRating = (el: HTMLElement | null): CSFDColorRating => {\n const classes: string[] = el?.classNames.split(' ') ?? [];\n const last = classes[classes.length - 1] as CSFDColors | undefined;\n return parseColor(last);\n};\n\nexport const getCreatorId = (url: string | null | undefined): number | null => {\n return url ? parseIdFromUrl(url) : null;\n};\n\nexport const getCreatorName = (el: HTMLElement | null): string | null => {\n const h1 = el?.querySelector('h1');\n return h1?.innerText?.trim() ?? null;\n};\n\nexport const getCreatorBirthdayInfo = (\n el: HTMLElement | null\n): { birthday: string; age: number; birthPlace: string } => {\n const infoBlock = el.querySelector('
|
|
1
|
+
{"version":3,"file":"creator.helper.mjs","names":[],"sources":["../../src/helpers/creator.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDCreatorScreening } from '../dto/creator';\nimport { CSFDColorRating } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { addProtocol, parseColor, parseIdFromUrl } from './global.helper';\n\nconst getCreatorColorRating = (el: HTMLElement | null): CSFDColorRating => {\n const classes: string[] = el?.classNames.split(' ') ?? [];\n const last = classes[classes.length - 1] as CSFDColors | undefined;\n return parseColor(last);\n};\n\nexport const getCreatorId = (url: string | null | undefined): number | null => {\n return url ? parseIdFromUrl(url) : null;\n};\n\nexport const getCreatorName = (el: HTMLElement | null): string | null => {\n const h1 = el?.querySelector('h1');\n return h1?.innerText?.trim() ?? null;\n};\n\nexport const getCreatorBirthdayInfo = (\n el: HTMLElement | null\n): { birthday: string; age: number; birthPlace: string } => {\n const infoBlock = el.querySelector('.creator-profile-details p');\n const text = infoBlock?.innerHTML.trim();\n const birthPlaceRow = infoBlock?.querySelector('.info-place')?.innerText.trim();\n const ageRow = infoBlock?.querySelector('.info')?.innerText.trim();\n\n let birthday: string = '';\n\n if (text) {\n const parts = text.split('\\n');\n const birthdayRow = parts.find((x) => x.includes('nar.'));\n birthday = birthdayRow ? parseBirthday(birthdayRow) : '';\n }\n\n const age = ageRow ? +parseAge(ageRow) : null;\n const birthPlace = birthPlaceRow ? parseBirthPlace(birthPlaceRow) : '';\n return { birthday, age, birthPlace };\n};\n\nexport const getCreatorBio = (el: HTMLElement | null): string | null => {\n const p = el?.querySelector('.article-content p');\n const first = p?.text?.trim().split('\\n')[0]?.trim();\n return first || null;\n};\n\nexport const getCreatorPhoto = (el: HTMLElement | null): string | null => {\n const src = el?.querySelector('img')?.getAttribute('src');\n return src ? addProtocol(src) : null;\n};\n\nconst parseBirthday = (text: string): string => text.replace(/nar\\./g, '').trim();\n\nconst parseAge = (text: string): number | null => {\n const digits = text.replace(/[^\\d]/g, '');\n return digits ? Number(digits) : null;\n};\n\nconst parseBirthPlace = (text: string): string => text.trim().replace(/<br>/g, '').trim();\n\nexport const getCreatorFilms = (el: HTMLElement | null): CSFDCreatorScreening[] => {\n const filmNodes = el?.querySelectorAll('.updated-box')?.[0]?.querySelectorAll('table tr') ?? [];\n let yearCache: number | null = null;\n\n const films = filmNodes.map((filmNode) => {\n const id = getCreatorId(filmNode.querySelector('td.name .film-title-name')?.attributes?.href);\n const title = filmNode.querySelector('.name')?.text?.trim();\n const yearText = filmNode.querySelector('.year')?.text?.trim();\n const year = yearText ? +yearText : null;\n const colorRating = getCreatorColorRating(filmNode.querySelector('.name .icon'));\n\n // Cache year from previous film because there is a gap between movies with same year\n if (typeof year === 'number' && !isNaN(year)) {\n yearCache = +year;\n }\n\n const finalYear = year ?? yearCache;\n if (id != null && title && finalYear != null) {\n return { id, title, year: finalYear, colorRating };\n }\n return null;\n });\n // Remove empty objects\n const filmsUnique = films.filter(Boolean) as CSFDCreatorScreening[];\n return filmsUnique;\n};\n"],"mappings":";;;AAMA,MAAM,yBAAyB,OAA4C;CACzE,MAAM,UAAoB,IAAI,WAAW,MAAM,IAAI,IAAI,EAAE;CACzD,MAAM,OAAO,QAAQ,QAAQ,SAAS;AACtC,QAAO,WAAW,KAAK;;AAGzB,MAAa,gBAAgB,QAAkD;AAC7E,QAAO,MAAM,eAAe,IAAI,GAAG;;AAGrC,MAAa,kBAAkB,OAA0C;AAEvE,SADW,IAAI,cAAc,KAAK,GACvB,WAAW,MAAM,IAAI;;AAGlC,MAAa,0BACX,OAC0D;CAC1D,MAAM,YAAY,GAAG,cAAc,6BAA6B;CAChE,MAAM,OAAO,WAAW,UAAU,MAAM;CACxC,MAAM,gBAAgB,WAAW,cAAc,cAAc,EAAE,UAAU,MAAM;CAC/E,MAAM,SAAS,WAAW,cAAc,QAAQ,EAAE,UAAU,MAAM;CAElE,IAAI,WAAmB;AAEvB,KAAI,MAAM;EAER,MAAM,cADQ,KAAK,MAAM,KAAK,CACJ,MAAM,MAAM,EAAE,SAAS,OAAO,CAAC;AACzD,aAAW,cAAc,cAAc,YAAY,GAAG;;CAGxD,MAAM,MAAM,SAAS,CAAC,SAAS,OAAO,GAAG;CACzC,MAAM,aAAa,gBAAgB,gBAAgB,cAAc,GAAG;AACpE,QAAO;EAAE;EAAU;EAAK;EAAY;;AAGtC,MAAa,iBAAiB,OAA0C;AAGtE,SAFU,IAAI,cAAc,qBAAqB,GAChC,MAAM,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,IACpC;;AAGlB,MAAa,mBAAmB,OAA0C;CACxE,MAAM,MAAM,IAAI,cAAc,MAAM,EAAE,aAAa,MAAM;AACzD,QAAO,MAAM,YAAY,IAAI,GAAG;;AAGlC,MAAM,iBAAiB,SAAyB,KAAK,QAAQ,UAAU,GAAG,CAAC,MAAM;AAEjF,MAAM,YAAY,SAAgC;CAChD,MAAM,SAAS,KAAK,QAAQ,UAAU,GAAG;AACzC,QAAO,SAAS,OAAO,OAAO,GAAG;;AAGnC,MAAM,mBAAmB,SAAyB,KAAK,MAAM,CAAC,QAAQ,SAAS,GAAG,CAAC,MAAM;AAEzF,MAAa,mBAAmB,OAAmD;CACjF,MAAM,YAAY,IAAI,iBAAiB,eAAe,GAAG,IAAI,iBAAiB,WAAW,IAAI,EAAE;CAC/F,IAAI,YAA2B;AAsB/B,QApBc,UAAU,KAAK,aAAa;EACxC,MAAM,KAAK,aAAa,SAAS,cAAc,2BAA2B,EAAE,YAAY,KAAK;EAC7F,MAAM,QAAQ,SAAS,cAAc,QAAQ,EAAE,MAAM,MAAM;EAC3D,MAAM,WAAW,SAAS,cAAc,QAAQ,EAAE,MAAM,MAAM;EAC9D,MAAM,OAAO,WAAW,CAAC,WAAW;EACpC,MAAM,cAAc,sBAAsB,SAAS,cAAc,cAAc,CAAC;AAGhF,MAAI,OAAO,SAAS,YAAY,CAAC,MAAM,KAAK,CAC1C,aAAY,CAAC;EAGf,MAAM,YAAY,QAAQ;AAC1B,MAAI,MAAM,QAAQ,SAAS,aAAa,KACtC,QAAO;GAAE;GAAI;GAAO,MAAM;GAAW;GAAa;AAEpD,SAAO;GACP,CAEwB,OAAO,QAAQ"}
|
|
@@ -11,13 +11,13 @@ const getUserRating = (el) => {
|
|
|
11
11
|
};
|
|
12
12
|
const getUserRatingType = (el) => {
|
|
13
13
|
const typeText = el.querySelectorAll("td.name .film-title-info .info");
|
|
14
|
-
return typeText.length > 1 ? typeText[1].text
|
|
14
|
+
return typeText.length > 1 ? typeText[1].text : "film";
|
|
15
15
|
};
|
|
16
16
|
const getUserRatingTitle = (el) => {
|
|
17
17
|
return el.querySelector("td.name .film-title-name").text;
|
|
18
18
|
};
|
|
19
19
|
const getUserRatingYear = (el) => {
|
|
20
|
-
return +el.querySelectorAll("td.name .film-title-info .info")[0]?.text
|
|
20
|
+
return +el.querySelectorAll("td.name .film-title-info .info")[0]?.text || null;
|
|
21
21
|
};
|
|
22
22
|
const getUserRatingColorRating = (el) => {
|
|
23
23
|
return require_global_helper.parseColor(el.querySelector("td.name .icon").classNames.split(" ").pop());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-ratings.helper.js","names":["parseIdFromUrl","parseColor"],"sources":["../../src/helpers/user-ratings.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes, CSFDStars } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { parseColor, parseIdFromUrl } from './global.helper';\n\nexport const getUserRatingId = (el: HTMLElement): number => {\n const url = el.querySelector('td.name .film-title-name').attributes.href;\n return parseIdFromUrl(url);\n};\n\nexport const getUserRating = (el: HTMLElement): CSFDStars => {\n const ratingText = el.querySelector('td.star-rating-only .stars').classNames.split(' ').pop();\n\n const rating = ratingText.includes('stars-') ? +ratingText.split('-').pop() : 0;\n return rating as CSFDStars;\n};\n\nexport const getUserRatingType = (el: HTMLElement): CSFDFilmTypes => {\n const typeText = el.querySelectorAll('td.name .film-title-info .info');\n\n return (typeText.length > 1 ? typeText[1].text
|
|
1
|
+
{"version":3,"file":"user-ratings.helper.js","names":["parseIdFromUrl","parseColor"],"sources":["../../src/helpers/user-ratings.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes, CSFDStars } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { parseColor, parseIdFromUrl } from './global.helper';\n\nexport const getUserRatingId = (el: HTMLElement): number => {\n const url = el.querySelector('td.name .film-title-name').attributes.href;\n return parseIdFromUrl(url);\n};\n\nexport const getUserRating = (el: HTMLElement): CSFDStars => {\n const ratingText = el.querySelector('td.star-rating-only .stars').classNames.split(' ').pop();\n\n const rating = ratingText.includes('stars-') ? +ratingText.split('-').pop() : 0;\n return rating as CSFDStars;\n};\n\nexport const getUserRatingType = (el: HTMLElement): CSFDFilmTypes => {\n const typeText = el.querySelectorAll('td.name .film-title-info .info');\n\n return (typeText.length > 1 ? typeText[1].text : 'film') as CSFDFilmTypes;\n};\n\nexport const getUserRatingTitle = (el: HTMLElement): string => {\n return el.querySelector('td.name .film-title-name').text;\n};\n\nexport const getUserRatingYear = (el: HTMLElement): number => {\n return +el.querySelectorAll('td.name .film-title-info .info')[0]?.text || null;\n};\n\nexport const getUserRatingColorRating = (el: HTMLElement): CSFDColorRating => {\n const color = parseColor(\n el.querySelector('td.name .icon').classNames.split(' ').pop() as CSFDColors\n );\n return color;\n};\n\nexport const getUserRatingDate = (el: HTMLElement): string => {\n return el.querySelector('td.date-only').text.trim();\n};\n\nexport const getUserRatingUrl = (el: HTMLElement): string => {\n const url = el.querySelector('td.name .film-title-name').attributes.href;\n return `https://www.csfd.cz${url}`;\n};\n"],"mappings":";;;AAKA,MAAa,mBAAmB,OAA4B;CAC1D,MAAM,MAAM,GAAG,cAAc,2BAA2B,CAAC,WAAW;AACpE,QAAOA,qCAAe,IAAI;;AAG5B,MAAa,iBAAiB,OAA+B;CAC3D,MAAM,aAAa,GAAG,cAAc,6BAA6B,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK;AAG7F,QADe,WAAW,SAAS,SAAS,GAAG,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK,GAAG;;AAIhF,MAAa,qBAAqB,OAAmC;CACnE,MAAM,WAAW,GAAG,iBAAiB,iCAAiC;AAEtE,QAAQ,SAAS,SAAS,IAAI,SAAS,GAAG,OAAO;;AAGnD,MAAa,sBAAsB,OAA4B;AAC7D,QAAO,GAAG,cAAc,2BAA2B,CAAC;;AAGtD,MAAa,qBAAqB,OAA4B;AAC5D,QAAO,CAAC,GAAG,iBAAiB,iCAAiC,CAAC,IAAI,QAAQ;;AAG5E,MAAa,4BAA4B,OAAqC;AAI5E,QAHcC,iCACZ,GAAG,cAAc,gBAAgB,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK,CAC9D;;AAIH,MAAa,qBAAqB,OAA4B;AAC5D,QAAO,GAAG,cAAc,eAAe,CAAC,KAAK,MAAM;;AAGrD,MAAa,oBAAoB,OAA4B;AAE3D,QAAO,sBADK,GAAG,cAAc,2BAA2B,CAAC,WAAW"}
|
|
@@ -11,13 +11,13 @@ const getUserRating = (el) => {
|
|
|
11
11
|
};
|
|
12
12
|
const getUserRatingType = (el) => {
|
|
13
13
|
const typeText = el.querySelectorAll("td.name .film-title-info .info");
|
|
14
|
-
return typeText.length > 1 ? typeText[1].text
|
|
14
|
+
return typeText.length > 1 ? typeText[1].text : "film";
|
|
15
15
|
};
|
|
16
16
|
const getUserRatingTitle = (el) => {
|
|
17
17
|
return el.querySelector("td.name .film-title-name").text;
|
|
18
18
|
};
|
|
19
19
|
const getUserRatingYear = (el) => {
|
|
20
|
-
return +el.querySelectorAll("td.name .film-title-info .info")[0]?.text
|
|
20
|
+
return +el.querySelectorAll("td.name .film-title-info .info")[0]?.text || null;
|
|
21
21
|
};
|
|
22
22
|
const getUserRatingColorRating = (el) => {
|
|
23
23
|
return parseColor(el.querySelector("td.name .icon").classNames.split(" ").pop());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-ratings.helper.mjs","names":[],"sources":["../../src/helpers/user-ratings.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes, CSFDStars } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { parseColor, parseIdFromUrl } from './global.helper';\n\nexport const getUserRatingId = (el: HTMLElement): number => {\n const url = el.querySelector('td.name .film-title-name').attributes.href;\n return parseIdFromUrl(url);\n};\n\nexport const getUserRating = (el: HTMLElement): CSFDStars => {\n const ratingText = el.querySelector('td.star-rating-only .stars').classNames.split(' ').pop();\n\n const rating = ratingText.includes('stars-') ? +ratingText.split('-').pop() : 0;\n return rating as CSFDStars;\n};\n\nexport const getUserRatingType = (el: HTMLElement): CSFDFilmTypes => {\n const typeText = el.querySelectorAll('td.name .film-title-info .info');\n\n return (typeText.length > 1 ? typeText[1].text
|
|
1
|
+
{"version":3,"file":"user-ratings.helper.mjs","names":[],"sources":["../../src/helpers/user-ratings.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes, CSFDStars } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { parseColor, parseIdFromUrl } from './global.helper';\n\nexport const getUserRatingId = (el: HTMLElement): number => {\n const url = el.querySelector('td.name .film-title-name').attributes.href;\n return parseIdFromUrl(url);\n};\n\nexport const getUserRating = (el: HTMLElement): CSFDStars => {\n const ratingText = el.querySelector('td.star-rating-only .stars').classNames.split(' ').pop();\n\n const rating = ratingText.includes('stars-') ? +ratingText.split('-').pop() : 0;\n return rating as CSFDStars;\n};\n\nexport const getUserRatingType = (el: HTMLElement): CSFDFilmTypes => {\n const typeText = el.querySelectorAll('td.name .film-title-info .info');\n\n return (typeText.length > 1 ? typeText[1].text : 'film') as CSFDFilmTypes;\n};\n\nexport const getUserRatingTitle = (el: HTMLElement): string => {\n return el.querySelector('td.name .film-title-name').text;\n};\n\nexport const getUserRatingYear = (el: HTMLElement): number => {\n return +el.querySelectorAll('td.name .film-title-info .info')[0]?.text || null;\n};\n\nexport const getUserRatingColorRating = (el: HTMLElement): CSFDColorRating => {\n const color = parseColor(\n el.querySelector('td.name .icon').classNames.split(' ').pop() as CSFDColors\n );\n return color;\n};\n\nexport const getUserRatingDate = (el: HTMLElement): string => {\n return el.querySelector('td.date-only').text.trim();\n};\n\nexport const getUserRatingUrl = (el: HTMLElement): string => {\n const url = el.querySelector('td.name .film-title-name').attributes.href;\n return `https://www.csfd.cz${url}`;\n};\n"],"mappings":";;;AAKA,MAAa,mBAAmB,OAA4B;CAC1D,MAAM,MAAM,GAAG,cAAc,2BAA2B,CAAC,WAAW;AACpE,QAAO,eAAe,IAAI;;AAG5B,MAAa,iBAAiB,OAA+B;CAC3D,MAAM,aAAa,GAAG,cAAc,6BAA6B,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK;AAG7F,QADe,WAAW,SAAS,SAAS,GAAG,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK,GAAG;;AAIhF,MAAa,qBAAqB,OAAmC;CACnE,MAAM,WAAW,GAAG,iBAAiB,iCAAiC;AAEtE,QAAQ,SAAS,SAAS,IAAI,SAAS,GAAG,OAAO;;AAGnD,MAAa,sBAAsB,OAA4B;AAC7D,QAAO,GAAG,cAAc,2BAA2B,CAAC;;AAGtD,MAAa,qBAAqB,OAA4B;AAC5D,QAAO,CAAC,GAAG,iBAAiB,iCAAiC,CAAC,IAAI,QAAQ;;AAG5E,MAAa,4BAA4B,OAAqC;AAI5E,QAHc,WACZ,GAAG,cAAc,gBAAgB,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK,CAC9D;;AAIH,MAAa,qBAAqB,OAA4B;AAC5D,QAAO,GAAG,cAAc,eAAe,CAAC,KAAK,MAAM;;AAGrD,MAAa,oBAAoB,OAA4B;AAE3D,QAAO,sBADK,GAAG,cAAc,2BAA2B,CAAC,WAAW"}
|
|
@@ -21,16 +21,16 @@ const getUserReviewYear = (el) => {
|
|
|
21
21
|
return infoSpan ? +infoSpan.text.replace(/[()]/g, "") : null;
|
|
22
22
|
};
|
|
23
23
|
const getUserReviewColorRating = (el) => {
|
|
24
|
-
return require_global_helper.parseColor(el.querySelector(".film-title-
|
|
24
|
+
return require_global_helper.parseColor(el.querySelector(".film-title-inline i.icon")?.classNames.split(" ").pop());
|
|
25
25
|
};
|
|
26
26
|
const getUserReviewDate = (el) => {
|
|
27
|
-
return el.querySelector(".header-
|
|
27
|
+
return el.querySelector(".article-header-date-content .info time").text.trim();
|
|
28
28
|
};
|
|
29
29
|
const getUserReviewUrl = (el) => {
|
|
30
30
|
return `https://www.csfd.cz${el.querySelector(".film-title-name").attributes.href}`;
|
|
31
31
|
};
|
|
32
32
|
const getUserReviewText = (el) => {
|
|
33
|
-
return el.querySelector(".
|
|
33
|
+
return el.querySelector(".comment").text.trim();
|
|
34
34
|
};
|
|
35
35
|
const getUserReviewPoster = (el) => {
|
|
36
36
|
const img = el.querySelector(".article-img img");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-reviews.helper.js","names":["parseIdFromUrl","parseColor"],"sources":["../../src/helpers/user-reviews.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes, CSFDStars } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { parseColor, parseIdFromUrl } from './global.helper';\n\nexport const getUserReviewId = (el: HTMLElement): number => {\n const url = el.querySelector('.film-title-name').attributes.href;\n return parseIdFromUrl(url);\n};\n\nexport const getUserReviewRating = (el: HTMLElement): CSFDStars => {\n const ratingText = el.querySelector('.star-rating .stars').classNames.split(' ').pop();\n\n const rating = ratingText.includes('stars-') ? +ratingText.split('-').pop() : 0;\n return rating as CSFDStars;\n};\n\nexport const getUserReviewType = (el: HTMLElement): CSFDFilmTypes => {\n // Type can be in the second .info span (e.g., \"(seriál)\") // TODO need more tests\n const typeText = el.querySelectorAll('.film-title-info .info');\n\n return (typeText.length > 1 ? typeText[1].text.slice(1, -1) : 'film') as CSFDFilmTypes;\n};\n\nexport const getUserReviewTitle = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').text;\n};\n\nexport const getUserReviewYear = (el: HTMLElement): number => {\n const infoSpan = el.querySelector('.film-title-info .info');\n return infoSpan ? +infoSpan.text.replace(/[()]/g, '') : null;\n};\n\nexport const getUserReviewColorRating = (el: HTMLElement): CSFDColorRating => {\n const icon = el.querySelector('.film-title-
|
|
1
|
+
{"version":3,"file":"user-reviews.helper.js","names":["parseIdFromUrl","parseColor"],"sources":["../../src/helpers/user-reviews.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes, CSFDStars } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { parseColor, parseIdFromUrl } from './global.helper';\n\nexport const getUserReviewId = (el: HTMLElement): number => {\n const url = el.querySelector('.film-title-name').attributes.href;\n return parseIdFromUrl(url);\n};\n\nexport const getUserReviewRating = (el: HTMLElement): CSFDStars => {\n const ratingText = el.querySelector('.star-rating .stars').classNames.split(' ').pop();\n\n const rating = ratingText.includes('stars-') ? +ratingText.split('-').pop() : 0;\n return rating as CSFDStars;\n};\n\nexport const getUserReviewType = (el: HTMLElement): CSFDFilmTypes => {\n // Type can be in the second .info span (e.g., \"(seriál)\") // TODO need more tests\n const typeText = el.querySelectorAll('.film-title-info .info');\n\n return (typeText.length > 1 ? typeText[1].text.slice(1, -1) : 'film') as CSFDFilmTypes;\n};\n\nexport const getUserReviewTitle = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').text;\n};\n\nexport const getUserReviewYear = (el: HTMLElement): number => {\n const infoSpan = el.querySelector('.film-title-info .info');\n return infoSpan ? +infoSpan.text.replace(/[()]/g, '') : null;\n};\n\nexport const getUserReviewColorRating = (el: HTMLElement): CSFDColorRating => {\n const icon = el.querySelector('.film-title-inline i.icon');\n const color = parseColor(icon?.classNames.split(' ').pop() as CSFDColors);\n return color;\n};\n\nexport const getUserReviewDate = (el: HTMLElement): string => {\n return el.querySelector('.article-header-date-content .info time').text.trim();\n};\n\nexport const getUserReviewUrl = (el: HTMLElement): string => {\n const url = el.querySelector('.film-title-name').attributes.href;\n return `https://www.csfd.cz${url}`;\n};\n\nexport const getUserReviewText = (el: HTMLElement): string => {\n return el.querySelector('.comment').text.trim();\n};\n\nexport const getUserReviewPoster = (el: HTMLElement): string => {\n const img = el.querySelector('.article-img img');\n const srcset = img?.attributes.srcset;\n\n if (srcset) {\n // Extract 3x version from srcset (e.g., \"url 1x, url 2x, url 3x\")\n const srcsetParts = srcset.split(',').map((s) => s.trim());\n const poster3x = srcsetParts.find((s) => s.endsWith('3x'));\n if (poster3x) {\n const url = poster3x.replace(/\\s+3x$/, '').trim();\n return `https:${url}`;\n }\n }\n\n // Fallback to src if srcset not available\n const src = img?.attributes.src;\n return src ? `https:${src}` : null;\n};\n"],"mappings":";;;AAKA,MAAa,mBAAmB,OAA4B;CAC1D,MAAM,MAAM,GAAG,cAAc,mBAAmB,CAAC,WAAW;AAC5D,QAAOA,qCAAe,IAAI;;AAG5B,MAAa,uBAAuB,OAA+B;CACjE,MAAM,aAAa,GAAG,cAAc,sBAAsB,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK;AAGtF,QADe,WAAW,SAAS,SAAS,GAAG,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK,GAAG;;AAIhF,MAAa,qBAAqB,OAAmC;CAEnE,MAAM,WAAW,GAAG,iBAAiB,yBAAyB;AAE9D,QAAQ,SAAS,SAAS,IAAI,SAAS,GAAG,KAAK,MAAM,GAAG,GAAG,GAAG;;AAGhE,MAAa,sBAAsB,OAA4B;AAC7D,QAAO,GAAG,cAAc,mBAAmB,CAAC;;AAG9C,MAAa,qBAAqB,OAA4B;CAC5D,MAAM,WAAW,GAAG,cAAc,yBAAyB;AAC3D,QAAO,WAAW,CAAC,SAAS,KAAK,QAAQ,SAAS,GAAG,GAAG;;AAG1D,MAAa,4BAA4B,OAAqC;AAG5E,QADcC,iCADD,GAAG,cAAc,4BAA4B,EAC3B,WAAW,MAAM,IAAI,CAAC,KAAK,CAAe;;AAI3E,MAAa,qBAAqB,OAA4B;AAC5D,QAAO,GAAG,cAAc,0CAA0C,CAAC,KAAK,MAAM;;AAGhF,MAAa,oBAAoB,OAA4B;AAE3D,QAAO,sBADK,GAAG,cAAc,mBAAmB,CAAC,WAAW;;AAI9D,MAAa,qBAAqB,OAA4B;AAC5D,QAAO,GAAG,cAAc,WAAW,CAAC,KAAK,MAAM;;AAGjD,MAAa,uBAAuB,OAA4B;CAC9D,MAAM,MAAM,GAAG,cAAc,mBAAmB;CAChD,MAAM,SAAS,KAAK,WAAW;AAE/B,KAAI,QAAQ;EAGV,MAAM,WADc,OAAO,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAC7B,MAAM,MAAM,EAAE,SAAS,KAAK,CAAC;AAC1D,MAAI,SAEF,QAAO,SADK,SAAS,QAAQ,UAAU,GAAG,CAAC,MAAM;;CAMrD,MAAM,MAAM,KAAK,WAAW;AAC5B,QAAO,MAAM,SAAS,QAAQ"}
|
|
@@ -21,16 +21,16 @@ const getUserReviewYear = (el) => {
|
|
|
21
21
|
return infoSpan ? +infoSpan.text.replace(/[()]/g, "") : null;
|
|
22
22
|
};
|
|
23
23
|
const getUserReviewColorRating = (el) => {
|
|
24
|
-
return parseColor(el.querySelector(".film-title-
|
|
24
|
+
return parseColor(el.querySelector(".film-title-inline i.icon")?.classNames.split(" ").pop());
|
|
25
25
|
};
|
|
26
26
|
const getUserReviewDate = (el) => {
|
|
27
|
-
return el.querySelector(".header-
|
|
27
|
+
return el.querySelector(".article-header-date-content .info time").text.trim();
|
|
28
28
|
};
|
|
29
29
|
const getUserReviewUrl = (el) => {
|
|
30
30
|
return `https://www.csfd.cz${el.querySelector(".film-title-name").attributes.href}`;
|
|
31
31
|
};
|
|
32
32
|
const getUserReviewText = (el) => {
|
|
33
|
-
return el.querySelector(".
|
|
33
|
+
return el.querySelector(".comment").text.trim();
|
|
34
34
|
};
|
|
35
35
|
const getUserReviewPoster = (el) => {
|
|
36
36
|
const img = el.querySelector(".article-img img");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-reviews.helper.mjs","names":[],"sources":["../../src/helpers/user-reviews.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes, CSFDStars } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { parseColor, parseIdFromUrl } from './global.helper';\n\nexport const getUserReviewId = (el: HTMLElement): number => {\n const url = el.querySelector('.film-title-name').attributes.href;\n return parseIdFromUrl(url);\n};\n\nexport const getUserReviewRating = (el: HTMLElement): CSFDStars => {\n const ratingText = el.querySelector('.star-rating .stars').classNames.split(' ').pop();\n\n const rating = ratingText.includes('stars-') ? +ratingText.split('-').pop() : 0;\n return rating as CSFDStars;\n};\n\nexport const getUserReviewType = (el: HTMLElement): CSFDFilmTypes => {\n // Type can be in the second .info span (e.g., \"(seriál)\") // TODO need more tests\n const typeText = el.querySelectorAll('.film-title-info .info');\n\n return (typeText.length > 1 ? typeText[1].text.slice(1, -1) : 'film') as CSFDFilmTypes;\n};\n\nexport const getUserReviewTitle = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').text;\n};\n\nexport const getUserReviewYear = (el: HTMLElement): number => {\n const infoSpan = el.querySelector('.film-title-info .info');\n return infoSpan ? +infoSpan.text.replace(/[()]/g, '') : null;\n};\n\nexport const getUserReviewColorRating = (el: HTMLElement): CSFDColorRating => {\n const icon = el.querySelector('.film-title-
|
|
1
|
+
{"version":3,"file":"user-reviews.helper.mjs","names":[],"sources":["../../src/helpers/user-reviews.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes, CSFDStars } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { parseColor, parseIdFromUrl } from './global.helper';\n\nexport const getUserReviewId = (el: HTMLElement): number => {\n const url = el.querySelector('.film-title-name').attributes.href;\n return parseIdFromUrl(url);\n};\n\nexport const getUserReviewRating = (el: HTMLElement): CSFDStars => {\n const ratingText = el.querySelector('.star-rating .stars').classNames.split(' ').pop();\n\n const rating = ratingText.includes('stars-') ? +ratingText.split('-').pop() : 0;\n return rating as CSFDStars;\n};\n\nexport const getUserReviewType = (el: HTMLElement): CSFDFilmTypes => {\n // Type can be in the second .info span (e.g., \"(seriál)\") // TODO need more tests\n const typeText = el.querySelectorAll('.film-title-info .info');\n\n return (typeText.length > 1 ? typeText[1].text.slice(1, -1) : 'film') as CSFDFilmTypes;\n};\n\nexport const getUserReviewTitle = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').text;\n};\n\nexport const getUserReviewYear = (el: HTMLElement): number => {\n const infoSpan = el.querySelector('.film-title-info .info');\n return infoSpan ? +infoSpan.text.replace(/[()]/g, '') : null;\n};\n\nexport const getUserReviewColorRating = (el: HTMLElement): CSFDColorRating => {\n const icon = el.querySelector('.film-title-inline i.icon');\n const color = parseColor(icon?.classNames.split(' ').pop() as CSFDColors);\n return color;\n};\n\nexport const getUserReviewDate = (el: HTMLElement): string => {\n return el.querySelector('.article-header-date-content .info time').text.trim();\n};\n\nexport const getUserReviewUrl = (el: HTMLElement): string => {\n const url = el.querySelector('.film-title-name').attributes.href;\n return `https://www.csfd.cz${url}`;\n};\n\nexport const getUserReviewText = (el: HTMLElement): string => {\n return el.querySelector('.comment').text.trim();\n};\n\nexport const getUserReviewPoster = (el: HTMLElement): string => {\n const img = el.querySelector('.article-img img');\n const srcset = img?.attributes.srcset;\n\n if (srcset) {\n // Extract 3x version from srcset (e.g., \"url 1x, url 2x, url 3x\")\n const srcsetParts = srcset.split(',').map((s) => s.trim());\n const poster3x = srcsetParts.find((s) => s.endsWith('3x'));\n if (poster3x) {\n const url = poster3x.replace(/\\s+3x$/, '').trim();\n return `https:${url}`;\n }\n }\n\n // Fallback to src if srcset not available\n const src = img?.attributes.src;\n return src ? `https:${src}` : null;\n};\n"],"mappings":";;;AAKA,MAAa,mBAAmB,OAA4B;CAC1D,MAAM,MAAM,GAAG,cAAc,mBAAmB,CAAC,WAAW;AAC5D,QAAO,eAAe,IAAI;;AAG5B,MAAa,uBAAuB,OAA+B;CACjE,MAAM,aAAa,GAAG,cAAc,sBAAsB,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK;AAGtF,QADe,WAAW,SAAS,SAAS,GAAG,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK,GAAG;;AAIhF,MAAa,qBAAqB,OAAmC;CAEnE,MAAM,WAAW,GAAG,iBAAiB,yBAAyB;AAE9D,QAAQ,SAAS,SAAS,IAAI,SAAS,GAAG,KAAK,MAAM,GAAG,GAAG,GAAG;;AAGhE,MAAa,sBAAsB,OAA4B;AAC7D,QAAO,GAAG,cAAc,mBAAmB,CAAC;;AAG9C,MAAa,qBAAqB,OAA4B;CAC5D,MAAM,WAAW,GAAG,cAAc,yBAAyB;AAC3D,QAAO,WAAW,CAAC,SAAS,KAAK,QAAQ,SAAS,GAAG,GAAG;;AAG1D,MAAa,4BAA4B,OAAqC;AAG5E,QADc,WADD,GAAG,cAAc,4BAA4B,EAC3B,WAAW,MAAM,IAAI,CAAC,KAAK,CAAe;;AAI3E,MAAa,qBAAqB,OAA4B;AAC5D,QAAO,GAAG,cAAc,0CAA0C,CAAC,KAAK,MAAM;;AAGhF,MAAa,oBAAoB,OAA4B;AAE3D,QAAO,sBADK,GAAG,cAAc,mBAAmB,CAAC,WAAW;;AAI9D,MAAa,qBAAqB,OAA4B;AAC5D,QAAO,GAAG,cAAc,WAAW,CAAC,KAAK,MAAM;;AAGjD,MAAa,uBAAuB,OAA4B;CAC9D,MAAM,MAAM,GAAG,cAAc,mBAAmB;CAChD,MAAM,SAAS,KAAK,WAAW;AAE/B,KAAI,QAAQ;EAGV,MAAM,WADc,OAAO,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAC7B,MAAM,MAAM,EAAE,SAAS,KAAK,CAAC;AAC1D,MAAI,SAEF,QAAO,SADK,SAAS,QAAQ,UAAU,GAAG,CAAC,MAAM;;CAMrD,MAAM,MAAM,KAAK,WAAW;AAC5B,QAAO,MAAM,SAAS,QAAQ"}
|
package/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
1
2
|
const require_cinema_service = require('./services/cinema.service.js');
|
|
2
3
|
const require_creator_service = require('./services/creator.service.js');
|
|
3
4
|
const require_movie_service = require('./services/movie.service.js');
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["MovieScraper","UserRatingsScraper","UserReviewsScraper","CinemaScraper","CreatorScraper","SearchScraper"],"sources":["../src/index.ts"],"sourcesContent":["import { CSFDCinema, CSFDCinemaPeriod } from './dto/cinema';\nimport { CSFDCreator } from './dto/creator';\nimport { CSFDMovie } from './dto/movie';\nimport { CSFDSearch } from './dto/search';\nimport { CSFDUserRatingConfig, CSFDUserRatings } from './dto/user-ratings';\nimport { CSFDUserReviews, CSFDUserReviewsConfig } from './dto/user-reviews';\nimport { CinemaScraper } from './services/cinema.service';\nimport { CreatorScraper } from './services/creator.service';\nimport { MovieScraper } from './services/movie.service';\nimport { SearchScraper } from './services/search.service';\nimport { UserRatingsScraper } from './services/user-ratings.service';\nimport { UserReviewsScraper } from './services/user-reviews.service';\nimport { CSFDOptions } from './types';\n\nexport class Csfd {\n private defaultOptions?: CSFDOptions;\n\n constructor(\n private userRatingsService: UserRatingsScraper,\n private userReviewsService: UserReviewsScraper,\n private movieService: MovieScraper,\n private creatorService: CreatorScraper,\n private searchService: SearchScraper,\n private cinemaService: CinemaScraper,\n defaultOptions?: CSFDOptions\n ) {\n this.defaultOptions = defaultOptions;\n }\n\n public setOptions({ request, language }: CSFDOptions): void {\n if (request !== undefined) {\n this.defaultOptions = { ...this.defaultOptions, request };\n }\n if (language !== undefined) {\n this.defaultOptions = { ...this.defaultOptions, language };\n }\n }\n\n public async userRatings(\n user: string | number,\n config?: CSFDUserRatingConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserRatings[]> {\n const opts = options ?? this.defaultOptions;\n return this.userRatingsService.userRatings(user, config, opts);\n }\n\n public async userReviews(\n user: string | number,\n config?: CSFDUserReviewsConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserReviews[]> {\n const opts = options ?? this.defaultOptions;\n return this.userReviewsService.userReviews(user, config, opts);\n }\n\n public async movie(movie: number, options?: CSFDOptions): Promise<CSFDMovie> {\n const opts = options ?? this.defaultOptions;\n return this.movieService.movie(+movie, opts);\n }\n\n public async creator(creator: number, options?: CSFDOptions): Promise<CSFDCreator> {\n const opts = options ?? this.defaultOptions;\n return this.creatorService.creator(+creator, opts);\n }\n\n public async search(text: string, options?: CSFDOptions): Promise<CSFDSearch> {\n const opts = options ?? this.defaultOptions;\n return this.searchService.search(text, opts);\n }\n\n public async cinema(\n district: number | string,\n period: CSFDCinemaPeriod,\n options?: CSFDOptions\n ): Promise<CSFDCinema[]> {\n const opts = options ?? this.defaultOptions;\n return this.cinemaService.cinemas(+district, period, opts);\n }\n}\n\nconst movieScraper = new MovieScraper();\nconst userRatingsScraper = new UserRatingsScraper();\nconst userReviewsScraper = new UserReviewsScraper();\nconst cinemaScraper = new CinemaScraper();\nconst creatorScraper = new CreatorScraper();\nconst searchScraper = new SearchScraper();\n\nexport const csfd = new Csfd(\n userRatingsScraper,\n userReviewsScraper,\n movieScraper,\n creatorScraper,\n searchScraper,\n cinemaScraper\n);\n\nexport type * from './dto';\n\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":["MovieScraper","UserRatingsScraper","UserReviewsScraper","CinemaScraper","CreatorScraper","SearchScraper"],"sources":["../src/index.ts"],"sourcesContent":["import { CSFDCinema, CSFDCinemaPeriod } from './dto/cinema';\nimport { CSFDCreator } from './dto/creator';\nimport { CSFDMovie } from './dto/movie';\nimport { CSFDSearch } from './dto/search';\nimport { CSFDUserRatingConfig, CSFDUserRatings } from './dto/user-ratings';\nimport { CSFDUserReviews, CSFDUserReviewsConfig } from './dto/user-reviews';\nimport { CinemaScraper } from './services/cinema.service';\nimport { CreatorScraper } from './services/creator.service';\nimport { MovieScraper } from './services/movie.service';\nimport { SearchScraper } from './services/search.service';\nimport { UserRatingsScraper } from './services/user-ratings.service';\nimport { UserReviewsScraper } from './services/user-reviews.service';\nimport { CSFDOptions } from './types';\n\nexport class Csfd {\n private defaultOptions?: CSFDOptions;\n\n constructor(\n private userRatingsService: UserRatingsScraper,\n private userReviewsService: UserReviewsScraper,\n private movieService: MovieScraper,\n private creatorService: CreatorScraper,\n private searchService: SearchScraper,\n private cinemaService: CinemaScraper,\n defaultOptions?: CSFDOptions\n ) {\n this.defaultOptions = defaultOptions;\n }\n\n public setOptions({ request, language }: CSFDOptions): void {\n if (request !== undefined) {\n this.defaultOptions = { ...this.defaultOptions, request };\n }\n if (language !== undefined) {\n this.defaultOptions = { ...this.defaultOptions, language };\n }\n }\n\n public async userRatings(\n user: string | number,\n config?: CSFDUserRatingConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserRatings[]> {\n const opts = options ?? this.defaultOptions;\n return this.userRatingsService.userRatings(user, config, opts);\n }\n\n public async userReviews(\n user: string | number,\n config?: CSFDUserReviewsConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserReviews[]> {\n const opts = options ?? this.defaultOptions;\n return this.userReviewsService.userReviews(user, config, opts);\n }\n\n public async movie(movie: number, options?: CSFDOptions): Promise<CSFDMovie> {\n const opts = options ?? this.defaultOptions;\n return this.movieService.movie(+movie, opts);\n }\n\n public async creator(creator: number, options?: CSFDOptions): Promise<CSFDCreator> {\n const opts = options ?? this.defaultOptions;\n return this.creatorService.creator(+creator, opts);\n }\n\n public async search(text: string, options?: CSFDOptions): Promise<CSFDSearch> {\n const opts = options ?? this.defaultOptions;\n return this.searchService.search(text, opts);\n }\n\n public async cinema(\n district: number | string,\n period: CSFDCinemaPeriod,\n options?: CSFDOptions\n ): Promise<CSFDCinema[]> {\n const opts = options ?? this.defaultOptions;\n return this.cinemaService.cinemas(+district, period, opts);\n }\n}\n\nconst movieScraper = new MovieScraper();\nconst userRatingsScraper = new UserRatingsScraper();\nconst userReviewsScraper = new UserReviewsScraper();\nconst cinemaScraper = new CinemaScraper();\nconst creatorScraper = new CreatorScraper();\nconst searchScraper = new SearchScraper();\n\nexport const csfd = new Csfd(\n userRatingsScraper,\n userReviewsScraper,\n movieScraper,\n creatorScraper,\n searchScraper,\n cinemaScraper\n);\n\nexport type * from './dto';\n\n"],"mappings":";;;;;;;;;AAcA,IAAa,OAAb,MAAkB;CAGhB,YACE,AAAQ,oBACR,AAAQ,oBACR,AAAQ,cACR,AAAQ,gBACR,AAAQ,eACR,AAAQ,eACR,gBACA;EAPQ;EACA;EACA;EACA;EACA;EACA;AAGR,OAAK,iBAAiB;;CAGxB,AAAO,WAAW,EAAE,SAAS,YAA+B;AAC1D,MAAI,YAAY,OACd,MAAK,iBAAiB;GAAE,GAAG,KAAK;GAAgB;GAAS;AAE3D,MAAI,aAAa,OACf,MAAK,iBAAiB;GAAE,GAAG,KAAK;GAAgB;GAAU;;CAI9D,MAAa,YACX,MACA,QACA,SAC4B;EAC5B,MAAM,OAAO,WAAW,KAAK;AAC7B,SAAO,KAAK,mBAAmB,YAAY,MAAM,QAAQ,KAAK;;CAGhE,MAAa,YACX,MACA,QACA,SAC4B;EAC5B,MAAM,OAAO,WAAW,KAAK;AAC7B,SAAO,KAAK,mBAAmB,YAAY,MAAM,QAAQ,KAAK;;CAGhE,MAAa,MAAM,OAAe,SAA2C;EAC3E,MAAM,OAAO,WAAW,KAAK;AAC7B,SAAO,KAAK,aAAa,MAAM,CAAC,OAAO,KAAK;;CAG9C,MAAa,QAAQ,SAAiB,SAA6C;EACjF,MAAM,OAAO,WAAW,KAAK;AAC7B,SAAO,KAAK,eAAe,QAAQ,CAAC,SAAS,KAAK;;CAGpD,MAAa,OAAO,MAAc,SAA4C;EAC5E,MAAM,OAAO,WAAW,KAAK;AAC7B,SAAO,KAAK,cAAc,OAAO,MAAM,KAAK;;CAG9C,MAAa,OACX,UACA,QACA,SACuB;EACvB,MAAM,OAAO,WAAW,KAAK;AAC7B,SAAO,KAAK,cAAc,QAAQ,CAAC,UAAU,QAAQ,KAAK;;;AAI9D,MAAM,eAAe,IAAIA,oCAAc;AACvC,MAAM,qBAAqB,IAAIC,iDAAoB;AACnD,MAAM,qBAAqB,IAAIC,iDAAoB;AACnD,MAAM,gBAAgB,IAAIC,sCAAe;AACzC,MAAM,iBAAiB,IAAIC,wCAAgB;AAC3C,MAAM,gBAAgB,IAAIC,sCAAe;AAEzC,MAAa,OAAO,IAAI,KACtB,oBACA,oBACA,cACA,gBACA,eACA,cACD"}
|
package/mcp-server.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import packageJson from "./package.json" with { type: "json" };
|
|
6
|
+
import { csfd } from "./index.mjs";
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: packageJson.name,
|
|
9
|
+
version: packageJson.version
|
|
10
|
+
});
|
|
11
|
+
server.tool(
|
|
12
|
+
"search",
|
|
13
|
+
"Searches for a movie, TV series, or person on CSFD.cz. Returns a list of results with IDs. Use this tool FIRST to find the ID needed for other tools.",
|
|
14
|
+
{
|
|
15
|
+
query: z.string().describe("Search query (movie title, series, or actor name)")
|
|
16
|
+
},
|
|
17
|
+
async ({ query }) => {
|
|
18
|
+
try {
|
|
19
|
+
const results = await csfd.search(query);
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
type: "text",
|
|
24
|
+
text: JSON.stringify(results, null, 2)
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
};
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: `Error during search: ${error}` }],
|
|
31
|
+
isError: true
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
server.tool(
|
|
37
|
+
"get_movie",
|
|
38
|
+
"Retrieves detailed information about a specific movie or series, including rating, plot, genres, and actors. Requires a numeric CSFD ID.",
|
|
39
|
+
{
|
|
40
|
+
id: z.number().describe("CSFD Movie ID (found using the 'search' tool)")
|
|
41
|
+
},
|
|
42
|
+
async ({ id }) => {
|
|
43
|
+
try {
|
|
44
|
+
const movie = await csfd.movie(id);
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: JSON.stringify(movie, null, 2)
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: `Error retrieving movie details: ${error}` }],
|
|
56
|
+
isError: true
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
server.tool(
|
|
62
|
+
"get_creator",
|
|
63
|
+
"Retrieves information about a specific creator (actor, director, etc.), including their biography and filmography. Requires a numeric CSFD ID.",
|
|
64
|
+
{
|
|
65
|
+
id: z.number().describe("CSFD Creator ID (found using the 'search' tool)")
|
|
66
|
+
},
|
|
67
|
+
async ({ id }) => {
|
|
68
|
+
try {
|
|
69
|
+
const creator = await csfd.creator(id);
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: JSON.stringify(creator, null, 2)
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: `Error retrieving creator details: ${error}` }],
|
|
81
|
+
isError: true
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
server.tool(
|
|
87
|
+
"get_user_ratings",
|
|
88
|
+
"Retrieves movie ratings from a specific CSFD user. Returns a list of movies with their user rating (0-5 stars). Supports pagination and filtering by film type.",
|
|
89
|
+
{
|
|
90
|
+
user: z.union([z.string(), z.number()]).describe("CSFD User ID (numeric) or username"),
|
|
91
|
+
page: z.number().optional().describe("Page number to fetch (default: 1)"),
|
|
92
|
+
allPages: z.boolean().optional().describe("Fetch all pages at once (use wisely, may be slow)"),
|
|
93
|
+
allPagesDelay: z.number().optional().describe("Delay in ms between page requests when using allPages"),
|
|
94
|
+
excludes: z.array(z.string()).optional().describe('Film types to exclude (e.g. "seri\xE1l", "TV film")'),
|
|
95
|
+
includesOnly: z.array(z.string()).optional().describe('Only include these film types (e.g. "film")')
|
|
96
|
+
},
|
|
97
|
+
async ({ user, page, allPages, allPagesDelay, excludes, includesOnly }) => {
|
|
98
|
+
try {
|
|
99
|
+
const results = await csfd.userRatings(user, {
|
|
100
|
+
page,
|
|
101
|
+
allPages,
|
|
102
|
+
allPagesDelay,
|
|
103
|
+
excludes,
|
|
104
|
+
includesOnly
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: "text",
|
|
110
|
+
text: JSON.stringify(results, null, 2)
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: "text", text: `Error retrieving user ratings: ${error}` }],
|
|
117
|
+
isError: true
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
server.tool(
|
|
123
|
+
"get_user_reviews",
|
|
124
|
+
"Retrieves movie reviews written by a specific CSFD user. Returns a list of movies with their review text and rating. Supports pagination and filtering by film type.",
|
|
125
|
+
{
|
|
126
|
+
user: z.union([z.string(), z.number()]).describe("CSFD User ID (numeric) or username"),
|
|
127
|
+
page: z.number().optional().describe("Page number to fetch (default: 1)"),
|
|
128
|
+
allPages: z.boolean().optional().describe("Fetch all pages at once (use wisely, may be slow)"),
|
|
129
|
+
allPagesDelay: z.number().optional().describe("Delay in ms between page requests when using allPages"),
|
|
130
|
+
excludes: z.array(z.string()).optional().describe('Film types to exclude (e.g. "seri\xE1l", "TV film")'),
|
|
131
|
+
includesOnly: z.array(z.string()).optional().describe('Only include these film types (e.g. "film")')
|
|
132
|
+
},
|
|
133
|
+
async ({ user, page, allPages, allPagesDelay, excludes, includesOnly }) => {
|
|
134
|
+
try {
|
|
135
|
+
const results = await csfd.userReviews(user, {
|
|
136
|
+
page,
|
|
137
|
+
allPages,
|
|
138
|
+
allPagesDelay,
|
|
139
|
+
excludes,
|
|
140
|
+
includesOnly
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: JSON.stringify(results, null, 2)
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
content: [{ type: "text", text: `Error retrieving user reviews: ${error}` }],
|
|
153
|
+
isError: true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
server.tool(
|
|
159
|
+
"get_cinemas",
|
|
160
|
+
"Retrieves cinema screenings for a given district in Czech Republic. Returns a list of cinemas with their current screenings, showtimes, and movie details.",
|
|
161
|
+
{
|
|
162
|
+
district: z.union([z.number(), z.string()]).describe("District ID (numeric) or name"),
|
|
163
|
+
period: z.enum(["today", "tomorrow", "weekend", "week", "month"]).describe("Time period for screenings")
|
|
164
|
+
},
|
|
165
|
+
async ({ district, period }) => {
|
|
166
|
+
try {
|
|
167
|
+
const results = await csfd.cinema(district, period);
|
|
168
|
+
return {
|
|
169
|
+
content: [
|
|
170
|
+
{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: JSON.stringify(results, null, 2)
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
};
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return {
|
|
178
|
+
content: [{ type: "text", text: `Error retrieving cinema data: ${error}` }],
|
|
179
|
+
isError: true
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
async function main() {
|
|
185
|
+
const transport = new StdioServerTransport();
|
|
186
|
+
await server.connect(transport);
|
|
187
|
+
console.error("CSFD MCP Server running on stdio...");
|
|
188
|
+
}
|
|
189
|
+
main().catch((error) => {
|
|
190
|
+
console.error("Fatal error in MCP server:", error);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-csfd-api",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0-next.0",
|
|
4
4
|
"description": "ČSFD API in JavaScript. Amazing NPM library for scrapping csfd.cz :)",
|
|
5
5
|
"author": "BART! <bart@bartweb.cz>",
|
|
6
6
|
"publishConfig": {
|
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
"registry": "https://registry.npmjs.org"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
11
12
|
"cross-fetch": "^4.1.0",
|
|
12
|
-
"node-html-parser": "^7.0.2"
|
|
13
|
+
"node-html-parser": "^7.0.2",
|
|
14
|
+
"express": "^5.2.1",
|
|
15
|
+
"dotenv": "^17.2.4",
|
|
16
|
+
"zod": "^4.3.6"
|
|
13
17
|
},
|
|
14
18
|
"repository": {
|
|
15
19
|
"url": "git+https://github.com/bartholomej/node-csfd-api.git",
|
|
@@ -41,9 +45,14 @@
|
|
|
41
45
|
"types": "./index.d.ts",
|
|
42
46
|
"exports": {
|
|
43
47
|
".": {
|
|
44
|
-
"
|
|
45
|
-
"
|
|
48
|
+
"import": "./index.mjs",
|
|
49
|
+
"require": "./index.js"
|
|
46
50
|
},
|
|
47
51
|
"./package.json": "./package.json"
|
|
48
|
-
}
|
|
49
|
-
|
|
52
|
+
},
|
|
53
|
+
"bin": {
|
|
54
|
+
"csfd-mcp": "mcp-server.js",
|
|
55
|
+
"csfd-server": "server.js"
|
|
56
|
+
},
|
|
57
|
+
"sideEffects": false
|
|
58
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import express from "express";
|
|
4
|
+
import packageJson from "./package.json" with { type: "json" };
|
|
5
|
+
import { csfd } from "./index.mjs";
|
|
6
|
+
const LOG_COLORS = {
|
|
7
|
+
info: "\x1B[36m",
|
|
8
|
+
// cyan
|
|
9
|
+
warn: "\x1B[33m",
|
|
10
|
+
// yellow
|
|
11
|
+
error: "\x1B[31m",
|
|
12
|
+
// red
|
|
13
|
+
success: "\x1B[32m",
|
|
14
|
+
// green
|
|
15
|
+
reset: "\x1B[0m"
|
|
16
|
+
};
|
|
17
|
+
const LOG_SYMBOLS = {
|
|
18
|
+
info: "\u2139\uFE0F",
|
|
19
|
+
warn: "\u26A0\uFE0F",
|
|
20
|
+
error: "\u274C",
|
|
21
|
+
success: "\u2705"
|
|
22
|
+
};
|
|
23
|
+
const LOG_PADDED_SEVERITY = {
|
|
24
|
+
info: "INFO ",
|
|
25
|
+
warn: "WARN ",
|
|
26
|
+
error: "ERROR ",
|
|
27
|
+
success: "SUCCESS"
|
|
28
|
+
};
|
|
29
|
+
var Errors = /* @__PURE__ */ ((Errors2) => {
|
|
30
|
+
Errors2["API_KEY_MISSING"] = "API_KEY_MISSING";
|
|
31
|
+
Errors2["API_KEY_INVALID"] = "API_KEY_INVALID";
|
|
32
|
+
Errors2["ID_MISSING"] = "ID_MISSING";
|
|
33
|
+
Errors2["MOVIE_FETCH_FAILED"] = "MOVIE_FETCH_FAILED";
|
|
34
|
+
Errors2["CREATOR_FETCH_FAILED"] = "CREATOR_FETCH_FAILED";
|
|
35
|
+
Errors2["SEARCH_FETCH_FAILED"] = "SEARCH_FETCH_FAILED";
|
|
36
|
+
Errors2["USER_RATINGS_FETCH_FAILED"] = "USER_RATINGS_FETCH_FAILED";
|
|
37
|
+
Errors2["USER_REVIEWS_FETCH_FAILED"] = "USER_REVIEWS_FETCH_FAILED";
|
|
38
|
+
Errors2["CINEMAS_FETCH_FAILED"] = "CINEMAS_FETCH_FAILED";
|
|
39
|
+
Errors2["PAGE_NOT_FOUND"] = "PAGE_NOT_FOUND";
|
|
40
|
+
Errors2["TOO_MANY_REQUESTS"] = "TOO_MANY_REQUESTS";
|
|
41
|
+
return Errors2;
|
|
42
|
+
})(Errors || {});
|
|
43
|
+
function logMessage(severity, log, req) {
|
|
44
|
+
const time = (/* @__PURE__ */ new Date()).toISOString();
|
|
45
|
+
const reqInfo = req ? `${req.method}: ${req.originalUrl}` : "";
|
|
46
|
+
const reqIp = req ? req.headers["x-forwarded-for"] || req.socket.remoteAddress || req.ip || req.ips : "";
|
|
47
|
+
const msg = `${LOG_COLORS[severity]}[${LOG_PADDED_SEVERITY[severity]}]${LOG_COLORS.reset} ${time} | IP: ${reqIp} ${LOG_SYMBOLS[severity]} ${log.error ? log.error + ":" : ""} ${log.message} \u{1F517} ${reqInfo}`;
|
|
48
|
+
const logSuccessEnabled = process.env.VERBOSE === "true";
|
|
49
|
+
if (severity === "success") {
|
|
50
|
+
if (logSuccessEnabled) {
|
|
51
|
+
console.log(msg);
|
|
52
|
+
}
|
|
53
|
+
} else if (severity === "error") {
|
|
54
|
+
console.error(msg);
|
|
55
|
+
} else if (severity === "warn") {
|
|
56
|
+
console.warn(msg);
|
|
57
|
+
} else {
|
|
58
|
+
console.log(msg);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
var Endpoint = /* @__PURE__ */ ((Endpoint2) => {
|
|
62
|
+
Endpoint2["MOVIE"] = "/movie/:id";
|
|
63
|
+
Endpoint2["CREATOR"] = "/creator/:id";
|
|
64
|
+
Endpoint2["SEARCH"] = "/search/:query";
|
|
65
|
+
Endpoint2["USER_RATINGS"] = "/user-ratings/:id";
|
|
66
|
+
Endpoint2["USER_REVIEWS"] = "/user-reviews/:id";
|
|
67
|
+
Endpoint2["CINEMAS"] = "/cinemas";
|
|
68
|
+
return Endpoint2;
|
|
69
|
+
})(Endpoint || {});
|
|
70
|
+
const app = express();
|
|
71
|
+
const port = process.env.PORT || 3e3;
|
|
72
|
+
const API_KEY_NAME = process.env.API_KEY_NAME || "x-api-key";
|
|
73
|
+
const API_KEY = process.env.API_KEY;
|
|
74
|
+
const RAW_LANGUAGE = process.env.LANGUAGE;
|
|
75
|
+
const isSupportedLanguage = (value) => value === "cs" || value === "en" || value === "sk";
|
|
76
|
+
const BASE_LANGUAGE = isSupportedLanguage(RAW_LANGUAGE) ? RAW_LANGUAGE : void 0;
|
|
77
|
+
const API_KEYS_LIST = API_KEY ? API_KEY.split(/[,;\s]+/).map((k) => k.trim()).filter(Boolean) : [];
|
|
78
|
+
if (BASE_LANGUAGE) {
|
|
79
|
+
csfd.setOptions({ language: BASE_LANGUAGE });
|
|
80
|
+
}
|
|
81
|
+
app.use((req, res, next) => {
|
|
82
|
+
if (API_KEY) {
|
|
83
|
+
const apiKey = req.get(API_KEY_NAME)?.trim();
|
|
84
|
+
if (!apiKey) {
|
|
85
|
+
const log = {
|
|
86
|
+
error: "API_KEY_MISSING" /* API_KEY_MISSING */,
|
|
87
|
+
message: `Missing API key in request header: ${API_KEY_NAME}`
|
|
88
|
+
};
|
|
89
|
+
logMessage("error", log, req);
|
|
90
|
+
res.status(401).json(log);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!API_KEYS_LIST.includes(apiKey)) {
|
|
94
|
+
const log = {
|
|
95
|
+
error: "API_KEY_INVALID" /* API_KEY_INVALID */,
|
|
96
|
+
message: `Invalid API key in request header: ${API_KEY_NAME}`
|
|
97
|
+
};
|
|
98
|
+
logMessage("error", log, req);
|
|
99
|
+
res.status(401).json(log);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
next();
|
|
104
|
+
});
|
|
105
|
+
app.get("/", (_, res) => {
|
|
106
|
+
logMessage("info", { error: null, message: "/" });
|
|
107
|
+
res.json({
|
|
108
|
+
name: packageJson.name,
|
|
109
|
+
version: packageJson.version,
|
|
110
|
+
docs: packageJson.homepage,
|
|
111
|
+
links: Object.values(Endpoint)
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
app.get(["/movie/", "/creator/", "/search/", "/user-ratings/", "/user-reviews/"], (req, res) => {
|
|
115
|
+
const log = {
|
|
116
|
+
error: "ID_MISSING" /* ID_MISSING */,
|
|
117
|
+
message: `ID is missing. Provide ID like this: ${req.url}${req.url.endsWith("/") ? "" : "/"}1234`
|
|
118
|
+
};
|
|
119
|
+
logMessage("warn", log, req);
|
|
120
|
+
res.status(404).json(log);
|
|
121
|
+
});
|
|
122
|
+
app.get("/movie/:id" /* MOVIE */, async (req, res) => {
|
|
123
|
+
const rawLanguage = req.query.language;
|
|
124
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
125
|
+
try {
|
|
126
|
+
const movie = await csfd.movie(+req.params.id, { language });
|
|
127
|
+
res.json(movie);
|
|
128
|
+
logMessage(
|
|
129
|
+
"success",
|
|
130
|
+
{
|
|
131
|
+
error: null,
|
|
132
|
+
message: `${"/movie/:id" /* MOVIE */}: ${req.params.id}${language ? ` [${language}]` : ""}`
|
|
133
|
+
},
|
|
134
|
+
req
|
|
135
|
+
);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const log = {
|
|
138
|
+
error: "MOVIE_FETCH_FAILED" /* MOVIE_FETCH_FAILED */,
|
|
139
|
+
message: "Failed to fetch movie data: " + error
|
|
140
|
+
};
|
|
141
|
+
logMessage("error", log, req);
|
|
142
|
+
res.status(500).json(log);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
app.get("/creator/:id" /* CREATOR */, async (req, res) => {
|
|
146
|
+
const rawLanguage = req.query.language;
|
|
147
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
148
|
+
try {
|
|
149
|
+
const result = await csfd.creator(+req.params.id, { language });
|
|
150
|
+
res.json(result);
|
|
151
|
+
logMessage(
|
|
152
|
+
"success",
|
|
153
|
+
{
|
|
154
|
+
error: null,
|
|
155
|
+
message: `${"/creator/:id" /* CREATOR */}: ${req.params.id}${language ? ` [${language}]` : ""}`
|
|
156
|
+
},
|
|
157
|
+
req
|
|
158
|
+
);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const log = {
|
|
161
|
+
error: "CREATOR_FETCH_FAILED" /* CREATOR_FETCH_FAILED */,
|
|
162
|
+
message: "Failed to fetch creator data: " + error
|
|
163
|
+
};
|
|
164
|
+
logMessage("error", log, req);
|
|
165
|
+
res.status(500).json(log);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
app.get("/search/:query" /* SEARCH */, async (req, res) => {
|
|
169
|
+
const rawLanguage = req.query.language;
|
|
170
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
171
|
+
try {
|
|
172
|
+
const result = await csfd.search(req.params.query, { language });
|
|
173
|
+
res.json(result);
|
|
174
|
+
logMessage(
|
|
175
|
+
"success",
|
|
176
|
+
{
|
|
177
|
+
error: null,
|
|
178
|
+
message: `${"/search/:query" /* SEARCH */}: ${req.params.query}${language ? ` [${language}]` : ""}`
|
|
179
|
+
},
|
|
180
|
+
req
|
|
181
|
+
);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
const log = {
|
|
184
|
+
error: "SEARCH_FETCH_FAILED" /* SEARCH_FETCH_FAILED */,
|
|
185
|
+
message: "Failed to fetch search data: " + error
|
|
186
|
+
};
|
|
187
|
+
logMessage("error", log, req);
|
|
188
|
+
res.status(500).json(log);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
app.get("/user-ratings/:id" /* USER_RATINGS */, async (req, res) => {
|
|
192
|
+
const { allPages, allPagesDelay, excludes, includesOnly, page } = req.query;
|
|
193
|
+
const rawLanguage = req.query.language;
|
|
194
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
195
|
+
try {
|
|
196
|
+
const result = await csfd.userRatings(
|
|
197
|
+
req.params.id,
|
|
198
|
+
{
|
|
199
|
+
allPages: allPages === "true",
|
|
200
|
+
allPagesDelay: allPagesDelay ? +allPagesDelay : void 0,
|
|
201
|
+
excludes: excludes ? excludes.split(",") : void 0,
|
|
202
|
+
includesOnly: includesOnly ? includesOnly.split(",") : void 0,
|
|
203
|
+
page: page ? +page : void 0
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
language
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
res.json(result);
|
|
210
|
+
logMessage(
|
|
211
|
+
"success",
|
|
212
|
+
{
|
|
213
|
+
error: null,
|
|
214
|
+
message: `${"/user-ratings/:id" /* USER_RATINGS */}: ${req.params.id}${language ? ` [${language}]` : ""}`
|
|
215
|
+
},
|
|
216
|
+
req
|
|
217
|
+
);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const log = {
|
|
220
|
+
error: "USER_RATINGS_FETCH_FAILED" /* USER_RATINGS_FETCH_FAILED */,
|
|
221
|
+
message: "Failed to fetch user-ratings data: " + error
|
|
222
|
+
};
|
|
223
|
+
logMessage("error", log, req);
|
|
224
|
+
res.status(500).json(log);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
app.get("/user-reviews/:id" /* USER_REVIEWS */, async (req, res) => {
|
|
228
|
+
const { allPages, allPagesDelay, excludes, includesOnly, page } = req.query;
|
|
229
|
+
const rawLanguage = req.query.language;
|
|
230
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
231
|
+
try {
|
|
232
|
+
const result = await csfd.userReviews(
|
|
233
|
+
req.params.id,
|
|
234
|
+
{
|
|
235
|
+
allPages: allPages === "true",
|
|
236
|
+
allPagesDelay: allPagesDelay ? +allPagesDelay : void 0,
|
|
237
|
+
excludes: excludes ? excludes.split(",") : void 0,
|
|
238
|
+
includesOnly: includesOnly ? includesOnly.split(",") : void 0,
|
|
239
|
+
page: page ? +page : void 0
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
language
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
res.json(result);
|
|
246
|
+
logMessage(
|
|
247
|
+
"success",
|
|
248
|
+
{
|
|
249
|
+
error: null,
|
|
250
|
+
message: `${"/user-reviews/:id" /* USER_REVIEWS */}: ${req.params.id}${language ? ` [${language}]` : ""}`
|
|
251
|
+
},
|
|
252
|
+
req
|
|
253
|
+
);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const log = {
|
|
256
|
+
error: "USER_REVIEWS_FETCH_FAILED" /* USER_REVIEWS_FETCH_FAILED */,
|
|
257
|
+
message: "Failed to fetch user-reviews data: " + error
|
|
258
|
+
};
|
|
259
|
+
logMessage("error", log, req);
|
|
260
|
+
res.status(500).json(log);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
app.get("/cinemas" /* CINEMAS */, async (req, res) => {
|
|
264
|
+
const rawLanguage = req.query.language;
|
|
265
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
266
|
+
try {
|
|
267
|
+
const result = await csfd.cinema(1, "today", { language });
|
|
268
|
+
logMessage(
|
|
269
|
+
"success",
|
|
270
|
+
{ error: null, message: `${"/cinemas" /* CINEMAS */}${language ? ` [${language}]` : ""}` },
|
|
271
|
+
req
|
|
272
|
+
);
|
|
273
|
+
res.json(result);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
const log = {
|
|
276
|
+
error: "CINEMAS_FETCH_FAILED" /* CINEMAS_FETCH_FAILED */,
|
|
277
|
+
message: "Failed to fetch cinemas data: " + error
|
|
278
|
+
};
|
|
279
|
+
logMessage("error", log, req);
|
|
280
|
+
res.status(500).json(log);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
app.use((req, res) => {
|
|
284
|
+
const log = {
|
|
285
|
+
error: "PAGE_NOT_FOUND" /* PAGE_NOT_FOUND */,
|
|
286
|
+
message: "The requested endpoint could not be found."
|
|
287
|
+
};
|
|
288
|
+
logMessage("warn", log, req);
|
|
289
|
+
res.status(404).json(log);
|
|
290
|
+
});
|
|
291
|
+
app.listen(port, () => {
|
|
292
|
+
console.log(`
|
|
293
|
+
_ __ _ _
|
|
294
|
+
| | / _| | | (_)
|
|
295
|
+
_ __ ___ __| | ___ ___ ___| |_ __| | __ _ _ __ _
|
|
296
|
+
| '_ \\ / _ \\ / _\` |/ _ \\ / __/ __| _/ _\` | / _\` | '_ \\| |
|
|
297
|
+
| | | | (_) | (_| | __/ | (__\\__ \\ || (_| | | (_| | |_) | |
|
|
298
|
+
|_| |_|\\___/ \\__,_|\\___| \\___|___/_| \\__,_| \\__,_| .__/|_|
|
|
299
|
+
| |
|
|
300
|
+
|_|
|
|
301
|
+
`);
|
|
302
|
+
console.log(`node-csfd-api@${packageJson.version}
|
|
303
|
+
`);
|
|
304
|
+
console.log(`Docs: ${packageJson.homepage}`);
|
|
305
|
+
console.log(`Endpoints: ${Object.values(Endpoint).join(", ")}
|
|
306
|
+
`);
|
|
307
|
+
console.log(`API is running on: http://localhost:${port}`);
|
|
308
|
+
if (BASE_LANGUAGE) {
|
|
309
|
+
console.log(`Base language configured: ${BASE_LANGUAGE}
|
|
310
|
+
`);
|
|
311
|
+
}
|
|
312
|
+
if (API_KEYS_LIST.length === 0) {
|
|
313
|
+
console.log(
|
|
314
|
+
"\x1B[31m%s\x1B[0m",
|
|
315
|
+
"\u26A0\uFE0F Server is OPEN!\n- Your server will be open to the world and potentially everyone can use it without any restriction.\n- To enable some basic protection, set API_KEY environment variable (single value or comma-separated list) and provide the same value in request header: " + API_KEY_NAME
|
|
316
|
+
);
|
|
317
|
+
} else {
|
|
318
|
+
console.log(
|
|
319
|
+
"\x1B[32m%s\x1B[0m",
|
|
320
|
+
`\u2714\uFE0F Server is protected (somehow).
|
|
321
|
+
- ${API_KEYS_LIST.length} API key(s) are configured and will be checked for each request header: ${API_KEY_NAME}`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
@@ -11,7 +11,7 @@ var UserRatingsScraper = class {
|
|
|
11
11
|
const pageToFetch = config?.page || 1;
|
|
12
12
|
const url = require_vars.userRatingsUrl(user, pageToFetch > 1 ? pageToFetch : void 0, { language: options?.language });
|
|
13
13
|
const items = (0, node_html_parser.parse)(await require_index.fetchPage(url, { ...options?.request }));
|
|
14
|
-
const movies = items.querySelectorAll("
|
|
14
|
+
const movies = items.querySelectorAll("#snippet--ratings table tr");
|
|
15
15
|
const pagesNode = items.querySelector(".pagination");
|
|
16
16
|
const pages = +pagesNode?.childNodes[pagesNode.childNodes.length - 4].rawText || 1;
|
|
17
17
|
allMovies = this.getPage(config, movies);
|
|
@@ -20,7 +20,7 @@ var UserRatingsScraper = class {
|
|
|
20
20
|
console.log("Fetching all pages", pages);
|
|
21
21
|
for (let i = 2; i <= pages; i++) {
|
|
22
22
|
console.log("Fetching page", i, "out of", pages, "...");
|
|
23
|
-
const movies = (0, node_html_parser.parse)(await require_index.fetchPage(require_vars.userRatingsUrl(user, i, { language: options?.language }), { ...options?.request })).querySelectorAll("
|
|
23
|
+
const movies = (0, node_html_parser.parse)(await require_index.fetchPage(require_vars.userRatingsUrl(user, i, { language: options?.language }), { ...options?.request })).querySelectorAll("#snippet--ratings table tr");
|
|
24
24
|
allMovies = [...allMovies, ...this.getPage(config, movies)];
|
|
25
25
|
if (config.allPagesDelay) await require_global_helper.sleep(config.allPagesDelay);
|
|
26
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-ratings.service.js","names":["userRatingsUrl","fetchPage","sleep","getUserRatingType","getUserRatingId","getUserRatingTitle","getUserRatingYear","getUserRatingUrl","getUserRatingColorRating","getUserRatingDate","getUserRating"],"sources":["../../src/services/user-ratings.service.ts"],"sourcesContent":["import { HTMLElement, parse } from 'node-html-parser';\nimport { CSFDColorRating, CSFDStars } from '../dto/global';\nimport { CSFDUserRatingConfig, CSFDUserRatings } from '../dto/user-ratings';\nimport { fetchPage } from '../fetchers';\nimport { sleep } from '../helpers/global.helper';\nimport {\n getUserRating,\n getUserRatingColorRating,\n getUserRatingDate,\n getUserRatingId,\n getUserRatingTitle,\n getUserRatingType,\n getUserRatingUrl,\n getUserRatingYear\n} from '../helpers/user-ratings.helper';\nimport { CSFDOptions } from '../types';\nimport { userRatingsUrl } from '../vars';\n\nexport class UserRatingsScraper {\n public async userRatings(\n user: string | number,\n config?: CSFDUserRatingConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserRatings[]> {\n let allMovies: CSFDUserRatings[] = [];\n const pageToFetch = config?.page || 1;\n const url = userRatingsUrl(user, pageToFetch > 1 ? pageToFetch : undefined, {
|
|
1
|
+
{"version":3,"file":"user-ratings.service.js","names":["userRatingsUrl","fetchPage","sleep","getUserRatingType","getUserRatingId","getUserRatingTitle","getUserRatingYear","getUserRatingUrl","getUserRatingColorRating","getUserRatingDate","getUserRating"],"sources":["../../src/services/user-ratings.service.ts"],"sourcesContent":["import { HTMLElement, parse } from 'node-html-parser';\nimport { CSFDColorRating, CSFDStars } from '../dto/global';\nimport { CSFDUserRatingConfig, CSFDUserRatings } from '../dto/user-ratings';\nimport { fetchPage } from '../fetchers';\nimport { sleep } from '../helpers/global.helper';\nimport {\n getUserRating,\n getUserRatingColorRating,\n getUserRatingDate,\n getUserRatingId,\n getUserRatingTitle,\n getUserRatingType,\n getUserRatingUrl,\n getUserRatingYear\n} from '../helpers/user-ratings.helper';\nimport { CSFDOptions } from '../types';\nimport { userRatingsUrl } from '../vars';\n\nexport class UserRatingsScraper {\n public async userRatings(\n user: string | number,\n config?: CSFDUserRatingConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserRatings[]> {\n let allMovies: CSFDUserRatings[] = [];\n const pageToFetch = config?.page || 1;\n const url = userRatingsUrl(user, pageToFetch > 1 ? pageToFetch : undefined, {\n language: options?.language\n });\n const response = await fetchPage(url, { ...options?.request });\n const items = parse(response);\n const movies = items.querySelectorAll('#snippet--ratings table tr');\n\n // Get number of pages\n const pagesNode = items.querySelector('.pagination');\n const pages = +pagesNode?.childNodes[pagesNode.childNodes.length - 4].rawText || 1;\n\n allMovies = this.getPage(config, movies);\n\n if (config?.allPages) {\n console.log('User', user, url);\n console.log('Fetching all pages', pages);\n for (let i = 2; i <= pages; i++) {\n console.log('Fetching page', i, 'out of', pages, '...');\n const url = userRatingsUrl(user, i, { language: options?.language });\n const response = await fetchPage(url, { ...options?.request });\n\n const items = parse(response);\n const movies = items.querySelectorAll('#snippet--ratings table tr');\n allMovies = [...allMovies, ...this.getPage(config, movies)];\n\n // Sleep\n if (config.allPagesDelay) {\n await sleep(config.allPagesDelay);\n }\n }\n return allMovies;\n }\n\n return allMovies;\n }\n\n private getPage(config: CSFDUserRatingConfig, movies: HTMLElement[]) {\n const films: CSFDUserRatings[] = [];\n if (config) {\n if (config.includesOnly?.length && config.excludes?.length) {\n console.warn(\n `node-csfd-api:\n You can not use both parameters 'includesOnly' and 'excludes'.\n Parameter 'includesOnly' will be used now:`,\n config.includesOnly\n );\n }\n }\n\n for (const el of movies) {\n const type = getUserRatingType(el);\n\n // Filtering includesOnly\n if (config?.includesOnly?.length) {\n if (config.includesOnly.some((include) => type === include)) {\n films.push(this.buildUserRatings(el));\n }\n // Filter excludes\n } else if (config?.excludes?.length) {\n if (!config.excludes.some((exclude) => type === exclude)) {\n films.push(this.buildUserRatings(el));\n }\n } else {\n // Without filtering\n films.push(this.buildUserRatings(el));\n }\n }\n return films;\n }\n\n private buildUserRatings(el: HTMLElement): CSFDUserRatings {\n return {\n id: getUserRatingId(el),\n title: getUserRatingTitle(el),\n year: getUserRatingYear(el),\n type: getUserRatingType(el),\n url: getUserRatingUrl(el),\n colorRating: getUserRatingColorRating(el) as CSFDColorRating,\n userDate: getUserRatingDate(el),\n userRating: getUserRating(el) as CSFDStars\n };\n }\n}\n"],"mappings":";;;;;;;AAkBA,IAAa,qBAAb,MAAgC;CAC9B,MAAa,YACX,MACA,QACA,SAC4B;EAC5B,IAAI,YAA+B,EAAE;EACrC,MAAM,cAAc,QAAQ,QAAQ;EACpC,MAAM,MAAMA,4BAAe,MAAM,cAAc,IAAI,cAAc,QAAW,EAC1E,UAAU,SAAS,UACpB,CAAC;EAEF,MAAM,oCADW,MAAMC,wBAAU,KAAK,EAAE,GAAG,SAAS,SAAS,CAAC,CACjC;EAC7B,MAAM,SAAS,MAAM,iBAAiB,6BAA6B;EAGnE,MAAM,YAAY,MAAM,cAAc,cAAc;EACpD,MAAM,QAAQ,CAAC,WAAW,WAAW,UAAU,WAAW,SAAS,GAAG,WAAW;AAEjF,cAAY,KAAK,QAAQ,QAAQ,OAAO;AAExC,MAAI,QAAQ,UAAU;AACpB,WAAQ,IAAI,QAAQ,MAAM,IAAI;AAC9B,WAAQ,IAAI,sBAAsB,MAAM;AACxC,QAAK,IAAI,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,YAAQ,IAAI,iBAAiB,GAAG,UAAU,OAAO,MAAM;IAKvD,MAAM,qCAHW,MAAMA,wBADXD,4BAAe,MAAM,GAAG,EAAE,UAAU,SAAS,UAAU,CAAC,EAC9B,EAAE,GAAG,SAAS,SAAS,CAAC,CAEjC,CACR,iBAAiB,6BAA6B;AACnE,gBAAY,CAAC,GAAG,WAAW,GAAG,KAAK,QAAQ,QAAQ,OAAO,CAAC;AAG3D,QAAI,OAAO,cACT,OAAME,4BAAM,OAAO,cAAc;;AAGrC,UAAO;;AAGT,SAAO;;CAGT,AAAQ,QAAQ,QAA8B,QAAuB;EACnE,MAAM,QAA2B,EAAE;AACnC,MAAI,QACF;OAAI,OAAO,cAAc,UAAU,OAAO,UAAU,OAClD,SAAQ,KACN;;qDAGA,OAAO,aACR;;AAIL,OAAK,MAAM,MAAM,QAAQ;GACvB,MAAM,OAAOC,8CAAkB,GAAG;AAGlC,OAAI,QAAQ,cAAc,QACxB;QAAI,OAAO,aAAa,MAAM,YAAY,SAAS,QAAQ,CACzD,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;cAG9B,QAAQ,UAAU,QAC3B;QAAI,CAAC,OAAO,SAAS,MAAM,YAAY,SAAS,QAAQ,CACtD,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;SAIvC,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;;AAGzC,SAAO;;CAGT,AAAQ,iBAAiB,IAAkC;AACzD,SAAO;GACL,IAAIC,4CAAgB,GAAG;GACvB,OAAOC,+CAAmB,GAAG;GAC7B,MAAMC,8CAAkB,GAAG;GAC3B,MAAMH,8CAAkB,GAAG;GAC3B,KAAKI,6CAAiB,GAAG;GACzB,aAAaC,qDAAyB,GAAG;GACzC,UAAUC,8CAAkB,GAAG;GAC/B,YAAYC,0CAAc,GAAG;GAC9B"}
|
|
@@ -11,7 +11,7 @@ var UserRatingsScraper = class {
|
|
|
11
11
|
const pageToFetch = config?.page || 1;
|
|
12
12
|
const url = userRatingsUrl(user, pageToFetch > 1 ? pageToFetch : void 0, { language: options?.language });
|
|
13
13
|
const items = parse(await fetchPage(url, { ...options?.request }));
|
|
14
|
-
const movies = items.querySelectorAll("
|
|
14
|
+
const movies = items.querySelectorAll("#snippet--ratings table tr");
|
|
15
15
|
const pagesNode = items.querySelector(".pagination");
|
|
16
16
|
const pages = +pagesNode?.childNodes[pagesNode.childNodes.length - 4].rawText || 1;
|
|
17
17
|
allMovies = this.getPage(config, movies);
|
|
@@ -20,7 +20,7 @@ var UserRatingsScraper = class {
|
|
|
20
20
|
console.log("Fetching all pages", pages);
|
|
21
21
|
for (let i = 2; i <= pages; i++) {
|
|
22
22
|
console.log("Fetching page", i, "out of", pages, "...");
|
|
23
|
-
const movies = parse(await fetchPage(userRatingsUrl(user, i, { language: options?.language }), { ...options?.request })).querySelectorAll("
|
|
23
|
+
const movies = parse(await fetchPage(userRatingsUrl(user, i, { language: options?.language }), { ...options?.request })).querySelectorAll("#snippet--ratings table tr");
|
|
24
24
|
allMovies = [...allMovies, ...this.getPage(config, movies)];
|
|
25
25
|
if (config.allPagesDelay) await sleep(config.allPagesDelay);
|
|
26
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-ratings.service.mjs","names":[],"sources":["../../src/services/user-ratings.service.ts"],"sourcesContent":["import { HTMLElement, parse } from 'node-html-parser';\nimport { CSFDColorRating, CSFDStars } from '../dto/global';\nimport { CSFDUserRatingConfig, CSFDUserRatings } from '../dto/user-ratings';\nimport { fetchPage } from '../fetchers';\nimport { sleep } from '../helpers/global.helper';\nimport {\n getUserRating,\n getUserRatingColorRating,\n getUserRatingDate,\n getUserRatingId,\n getUserRatingTitle,\n getUserRatingType,\n getUserRatingUrl,\n getUserRatingYear\n} from '../helpers/user-ratings.helper';\nimport { CSFDOptions } from '../types';\nimport { userRatingsUrl } from '../vars';\n\nexport class UserRatingsScraper {\n public async userRatings(\n user: string | number,\n config?: CSFDUserRatingConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserRatings[]> {\n let allMovies: CSFDUserRatings[] = [];\n const pageToFetch = config?.page || 1;\n const url = userRatingsUrl(user, pageToFetch > 1 ? pageToFetch : undefined, {
|
|
1
|
+
{"version":3,"file":"user-ratings.service.mjs","names":[],"sources":["../../src/services/user-ratings.service.ts"],"sourcesContent":["import { HTMLElement, parse } from 'node-html-parser';\nimport { CSFDColorRating, CSFDStars } from '../dto/global';\nimport { CSFDUserRatingConfig, CSFDUserRatings } from '../dto/user-ratings';\nimport { fetchPage } from '../fetchers';\nimport { sleep } from '../helpers/global.helper';\nimport {\n getUserRating,\n getUserRatingColorRating,\n getUserRatingDate,\n getUserRatingId,\n getUserRatingTitle,\n getUserRatingType,\n getUserRatingUrl,\n getUserRatingYear\n} from '../helpers/user-ratings.helper';\nimport { CSFDOptions } from '../types';\nimport { userRatingsUrl } from '../vars';\n\nexport class UserRatingsScraper {\n public async userRatings(\n user: string | number,\n config?: CSFDUserRatingConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserRatings[]> {\n let allMovies: CSFDUserRatings[] = [];\n const pageToFetch = config?.page || 1;\n const url = userRatingsUrl(user, pageToFetch > 1 ? pageToFetch : undefined, {\n language: options?.language\n });\n const response = await fetchPage(url, { ...options?.request });\n const items = parse(response);\n const movies = items.querySelectorAll('#snippet--ratings table tr');\n\n // Get number of pages\n const pagesNode = items.querySelector('.pagination');\n const pages = +pagesNode?.childNodes[pagesNode.childNodes.length - 4].rawText || 1;\n\n allMovies = this.getPage(config, movies);\n\n if (config?.allPages) {\n console.log('User', user, url);\n console.log('Fetching all pages', pages);\n for (let i = 2; i <= pages; i++) {\n console.log('Fetching page', i, 'out of', pages, '...');\n const url = userRatingsUrl(user, i, { language: options?.language });\n const response = await fetchPage(url, { ...options?.request });\n\n const items = parse(response);\n const movies = items.querySelectorAll('#snippet--ratings table tr');\n allMovies = [...allMovies, ...this.getPage(config, movies)];\n\n // Sleep\n if (config.allPagesDelay) {\n await sleep(config.allPagesDelay);\n }\n }\n return allMovies;\n }\n\n return allMovies;\n }\n\n private getPage(config: CSFDUserRatingConfig, movies: HTMLElement[]) {\n const films: CSFDUserRatings[] = [];\n if (config) {\n if (config.includesOnly?.length && config.excludes?.length) {\n console.warn(\n `node-csfd-api:\n You can not use both parameters 'includesOnly' and 'excludes'.\n Parameter 'includesOnly' will be used now:`,\n config.includesOnly\n );\n }\n }\n\n for (const el of movies) {\n const type = getUserRatingType(el);\n\n // Filtering includesOnly\n if (config?.includesOnly?.length) {\n if (config.includesOnly.some((include) => type === include)) {\n films.push(this.buildUserRatings(el));\n }\n // Filter excludes\n } else if (config?.excludes?.length) {\n if (!config.excludes.some((exclude) => type === exclude)) {\n films.push(this.buildUserRatings(el));\n }\n } else {\n // Without filtering\n films.push(this.buildUserRatings(el));\n }\n }\n return films;\n }\n\n private buildUserRatings(el: HTMLElement): CSFDUserRatings {\n return {\n id: getUserRatingId(el),\n title: getUserRatingTitle(el),\n year: getUserRatingYear(el),\n type: getUserRatingType(el),\n url: getUserRatingUrl(el),\n colorRating: getUserRatingColorRating(el) as CSFDColorRating,\n userDate: getUserRatingDate(el),\n userRating: getUserRating(el) as CSFDStars\n };\n }\n}\n"],"mappings":";;;;;;;AAkBA,IAAa,qBAAb,MAAgC;CAC9B,MAAa,YACX,MACA,QACA,SAC4B;EAC5B,IAAI,YAA+B,EAAE;EACrC,MAAM,cAAc,QAAQ,QAAQ;EACpC,MAAM,MAAM,eAAe,MAAM,cAAc,IAAI,cAAc,QAAW,EAC1E,UAAU,SAAS,UACpB,CAAC;EAEF,MAAM,QAAQ,MADG,MAAM,UAAU,KAAK,EAAE,GAAG,SAAS,SAAS,CAAC,CACjC;EAC7B,MAAM,SAAS,MAAM,iBAAiB,6BAA6B;EAGnE,MAAM,YAAY,MAAM,cAAc,cAAc;EACpD,MAAM,QAAQ,CAAC,WAAW,WAAW,UAAU,WAAW,SAAS,GAAG,WAAW;AAEjF,cAAY,KAAK,QAAQ,QAAQ,OAAO;AAExC,MAAI,QAAQ,UAAU;AACpB,WAAQ,IAAI,QAAQ,MAAM,IAAI;AAC9B,WAAQ,IAAI,sBAAsB,MAAM;AACxC,QAAK,IAAI,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,YAAQ,IAAI,iBAAiB,GAAG,UAAU,OAAO,MAAM;IAKvD,MAAM,SADQ,MAFG,MAAM,UADX,eAAe,MAAM,GAAG,EAAE,UAAU,SAAS,UAAU,CAAC,EAC9B,EAAE,GAAG,SAAS,SAAS,CAAC,CAEjC,CACR,iBAAiB,6BAA6B;AACnE,gBAAY,CAAC,GAAG,WAAW,GAAG,KAAK,QAAQ,QAAQ,OAAO,CAAC;AAG3D,QAAI,OAAO,cACT,OAAM,MAAM,OAAO,cAAc;;AAGrC,UAAO;;AAGT,SAAO;;CAGT,AAAQ,QAAQ,QAA8B,QAAuB;EACnE,MAAM,QAA2B,EAAE;AACnC,MAAI,QACF;OAAI,OAAO,cAAc,UAAU,OAAO,UAAU,OAClD,SAAQ,KACN;;qDAGA,OAAO,aACR;;AAIL,OAAK,MAAM,MAAM,QAAQ;GACvB,MAAM,OAAO,kBAAkB,GAAG;AAGlC,OAAI,QAAQ,cAAc,QACxB;QAAI,OAAO,aAAa,MAAM,YAAY,SAAS,QAAQ,CACzD,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;cAG9B,QAAQ,UAAU,QAC3B;QAAI,CAAC,OAAO,SAAS,MAAM,YAAY,SAAS,QAAQ,CACtD,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;SAIvC,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;;AAGzC,SAAO;;CAGT,AAAQ,iBAAiB,IAAkC;AACzD,SAAO;GACL,IAAI,gBAAgB,GAAG;GACvB,OAAO,mBAAmB,GAAG;GAC7B,MAAM,kBAAkB,GAAG;GAC3B,MAAM,kBAAkB,GAAG;GAC3B,KAAK,iBAAiB,GAAG;GACzB,aAAa,yBAAyB,GAAG;GACzC,UAAU,kBAAkB,GAAG;GAC/B,YAAY,cAAc,GAAG;GAC9B"}
|
|
@@ -11,7 +11,7 @@ var UserReviewsScraper = class {
|
|
|
11
11
|
const pageToFetch = config?.page || 1;
|
|
12
12
|
const url = require_vars.userReviewsUrl(user, pageToFetch > 1 ? pageToFetch : void 0, { language: options?.language });
|
|
13
13
|
const items = (0, node_html_parser.parse)(await require_index.fetchPage(url, { ...options?.request }));
|
|
14
|
-
const reviews = items.querySelectorAll(".user-reviews .article");
|
|
14
|
+
const reviews = items.querySelectorAll(".user-tab-reviews .article");
|
|
15
15
|
const pagesNode = items.querySelector(".pagination");
|
|
16
16
|
const pages = +pagesNode?.childNodes[pagesNode.childNodes.length - 4].rawText || 1;
|
|
17
17
|
allReviews = this.getPage(config, reviews);
|
|
@@ -20,7 +20,7 @@ var UserReviewsScraper = class {
|
|
|
20
20
|
console.log("Fetching all pages", pages);
|
|
21
21
|
for (let i = 2; i <= pages; i++) {
|
|
22
22
|
console.log("Fetching page", i, "out of", pages, "...");
|
|
23
|
-
const reviews = (0, node_html_parser.parse)(await require_index.fetchPage(require_vars.userReviewsUrl(user, i, { language: options?.language }), { ...options?.request })).querySelectorAll(".user-reviews .article");
|
|
23
|
+
const reviews = (0, node_html_parser.parse)(await require_index.fetchPage(require_vars.userReviewsUrl(user, i, { language: options?.language }), { ...options?.request })).querySelectorAll(".user-tab-reviews .article");
|
|
24
24
|
allReviews = [...allReviews, ...this.getPage(config, reviews)];
|
|
25
25
|
if (config.allPagesDelay) await require_global_helper.sleep(config.allPagesDelay);
|
|
26
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-reviews.service.js","names":["userReviewsUrl","fetchPage","sleep","getUserReviewType","getUserReviewId","getUserReviewTitle","getUserReviewYear","getUserReviewUrl","getUserReviewColorRating","getUserReviewDate","getUserReviewRating","getUserReviewText","getUserReviewPoster"],"sources":["../../src/services/user-reviews.service.ts"],"sourcesContent":["import { HTMLElement, parse } from 'node-html-parser';\nimport { CSFDColorRating, CSFDStars } from '../dto/global';\nimport { CSFDUserReviews, CSFDUserReviewsConfig } from '../dto/user-reviews';\nimport { fetchPage } from '../fetchers';\nimport { sleep } from '../helpers/global.helper';\nimport {\n getUserReviewColorRating,\n getUserReviewDate,\n getUserReviewId,\n getUserReviewPoster,\n getUserReviewRating,\n getUserReviewText,\n getUserReviewTitle,\n getUserReviewType,\n getUserReviewUrl,\n getUserReviewYear\n} from '../helpers/user-reviews.helper';\nimport { CSFDOptions } from '../types';\nimport { userReviewsUrl } from '../vars';\n\nexport class UserReviewsScraper {\n public async userReviews(\n user: string | number,\n config?: CSFDUserReviewsConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserReviews[]> {\n let allReviews: CSFDUserReviews[] = [];\n const pageToFetch = config?.page || 1;\n const url = userReviewsUrl(user, pageToFetch > 1 ? pageToFetch : undefined, {
|
|
1
|
+
{"version":3,"file":"user-reviews.service.js","names":["userReviewsUrl","fetchPage","sleep","getUserReviewType","getUserReviewId","getUserReviewTitle","getUserReviewYear","getUserReviewUrl","getUserReviewColorRating","getUserReviewDate","getUserReviewRating","getUserReviewText","getUserReviewPoster"],"sources":["../../src/services/user-reviews.service.ts"],"sourcesContent":["import { HTMLElement, parse } from 'node-html-parser';\nimport { CSFDColorRating, CSFDStars } from '../dto/global';\nimport { CSFDUserReviews, CSFDUserReviewsConfig } from '../dto/user-reviews';\nimport { fetchPage } from '../fetchers';\nimport { sleep } from '../helpers/global.helper';\nimport {\n getUserReviewColorRating,\n getUserReviewDate,\n getUserReviewId,\n getUserReviewPoster,\n getUserReviewRating,\n getUserReviewText,\n getUserReviewTitle,\n getUserReviewType,\n getUserReviewUrl,\n getUserReviewYear\n} from '../helpers/user-reviews.helper';\nimport { CSFDOptions } from '../types';\nimport { userReviewsUrl } from '../vars';\n\nexport class UserReviewsScraper {\n public async userReviews(\n user: string | number,\n config?: CSFDUserReviewsConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserReviews[]> {\n let allReviews: CSFDUserReviews[] = [];\n const pageToFetch = config?.page || 1;\n const url = userReviewsUrl(user, pageToFetch > 1 ? pageToFetch : undefined, {\n language: options?.language\n });\n const response = await fetchPage(url, { ...options?.request });\n const items = parse(response);\n const reviews = items.querySelectorAll('.user-tab-reviews .article');\n\n // Get number of pages\n const pagesNode = items.querySelector('.pagination');\n const pages = +pagesNode?.childNodes[pagesNode.childNodes.length - 4].rawText || 1;\n\n allReviews = this.getPage(config, reviews);\n\n if (config?.allPages) {\n console.log('User', user, url);\n console.log('Fetching all pages', pages);\n for (let i = 2; i <= pages; i++) {\n console.log('Fetching page', i, 'out of', pages, '...');\n const url = userReviewsUrl(user, i, { language: options?.language });\n const response = await fetchPage(url, { ...options?.request });\n\n const items = parse(response);\n const reviews = items.querySelectorAll('.user-tab-reviews .article');\n allReviews = [...allReviews, ...this.getPage(config, reviews)];\n\n // Sleep\n if (config.allPagesDelay) {\n await sleep(config.allPagesDelay);\n }\n }\n return allReviews;\n }\n\n return allReviews;\n }\n\n private getPage(config: CSFDUserReviewsConfig, reviews: HTMLElement[]) {\n const films: CSFDUserReviews[] = [];\n if (config) {\n if (config.includesOnly?.length && config.excludes?.length) {\n console.warn(\n `node-csfd-api:\n You can not use both parameters 'includesOnly' and 'excludes'.\n Parameter 'includesOnly' will be used now:`,\n config.includesOnly\n );\n }\n }\n\n for (const el of reviews) {\n const type = getUserReviewType(el);\n\n // Filtering includesOnly\n if (config?.includesOnly?.length) {\n if (config.includesOnly.some((include) => type === include)) {\n films.push(this.buildUserReviews(el));\n }\n // Filter excludes\n } else if (config?.excludes?.length) {\n if (!config.excludes.some((exclude) => type === exclude)) {\n films.push(this.buildUserReviews(el));\n }\n } else {\n // Without filtering\n films.push(this.buildUserReviews(el));\n }\n }\n return films;\n }\n\n private buildUserReviews(el: HTMLElement): CSFDUserReviews {\n return {\n id: getUserReviewId(el),\n title: getUserReviewTitle(el),\n year: getUserReviewYear(el),\n type: getUserReviewType(el),\n url: getUserReviewUrl(el),\n colorRating: getUserReviewColorRating(el) as CSFDColorRating,\n userDate: getUserReviewDate(el),\n userRating: getUserReviewRating(el) as CSFDStars,\n text: getUserReviewText(el),\n poster: getUserReviewPoster(el)\n };\n }\n}\n"],"mappings":";;;;;;;AAoBA,IAAa,qBAAb,MAAgC;CAC9B,MAAa,YACX,MACA,QACA,SAC4B;EAC5B,IAAI,aAAgC,EAAE;EACtC,MAAM,cAAc,QAAQ,QAAQ;EACpC,MAAM,MAAMA,4BAAe,MAAM,cAAc,IAAI,cAAc,QAAW,EAC1E,UAAU,SAAS,UACpB,CAAC;EAEF,MAAM,oCADW,MAAMC,wBAAU,KAAK,EAAE,GAAG,SAAS,SAAS,CAAC,CACjC;EAC7B,MAAM,UAAU,MAAM,iBAAiB,6BAA6B;EAGpE,MAAM,YAAY,MAAM,cAAc,cAAc;EACpD,MAAM,QAAQ,CAAC,WAAW,WAAW,UAAU,WAAW,SAAS,GAAG,WAAW;AAEjF,eAAa,KAAK,QAAQ,QAAQ,QAAQ;AAE1C,MAAI,QAAQ,UAAU;AACpB,WAAQ,IAAI,QAAQ,MAAM,IAAI;AAC9B,WAAQ,IAAI,sBAAsB,MAAM;AACxC,QAAK,IAAI,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,YAAQ,IAAI,iBAAiB,GAAG,UAAU,OAAO,MAAM;IAKvD,MAAM,sCAHW,MAAMA,wBADXD,4BAAe,MAAM,GAAG,EAAE,UAAU,SAAS,UAAU,CAAC,EAC9B,EAAE,GAAG,SAAS,SAAS,CAAC,CAEjC,CACP,iBAAiB,6BAA6B;AACpE,iBAAa,CAAC,GAAG,YAAY,GAAG,KAAK,QAAQ,QAAQ,QAAQ,CAAC;AAG9D,QAAI,OAAO,cACT,OAAME,4BAAM,OAAO,cAAc;;AAGrC,UAAO;;AAGT,SAAO;;CAGT,AAAQ,QAAQ,QAA+B,SAAwB;EACrE,MAAM,QAA2B,EAAE;AACnC,MAAI,QACF;OAAI,OAAO,cAAc,UAAU,OAAO,UAAU,OAClD,SAAQ,KACN;;qDAGA,OAAO,aACR;;AAIL,OAAK,MAAM,MAAM,SAAS;GACxB,MAAM,OAAOC,8CAAkB,GAAG;AAGlC,OAAI,QAAQ,cAAc,QACxB;QAAI,OAAO,aAAa,MAAM,YAAY,SAAS,QAAQ,CACzD,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;cAG9B,QAAQ,UAAU,QAC3B;QAAI,CAAC,OAAO,SAAS,MAAM,YAAY,SAAS,QAAQ,CACtD,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;SAIvC,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;;AAGzC,SAAO;;CAGT,AAAQ,iBAAiB,IAAkC;AACzD,SAAO;GACL,IAAIC,4CAAgB,GAAG;GACvB,OAAOC,+CAAmB,GAAG;GAC7B,MAAMC,8CAAkB,GAAG;GAC3B,MAAMH,8CAAkB,GAAG;GAC3B,KAAKI,6CAAiB,GAAG;GACzB,aAAaC,qDAAyB,GAAG;GACzC,UAAUC,8CAAkB,GAAG;GAC/B,YAAYC,gDAAoB,GAAG;GACnC,MAAMC,8CAAkB,GAAG;GAC3B,QAAQC,gDAAoB,GAAG;GAChC"}
|
|
@@ -11,7 +11,7 @@ var UserReviewsScraper = class {
|
|
|
11
11
|
const pageToFetch = config?.page || 1;
|
|
12
12
|
const url = userReviewsUrl(user, pageToFetch > 1 ? pageToFetch : void 0, { language: options?.language });
|
|
13
13
|
const items = parse(await fetchPage(url, { ...options?.request }));
|
|
14
|
-
const reviews = items.querySelectorAll(".user-reviews .article");
|
|
14
|
+
const reviews = items.querySelectorAll(".user-tab-reviews .article");
|
|
15
15
|
const pagesNode = items.querySelector(".pagination");
|
|
16
16
|
const pages = +pagesNode?.childNodes[pagesNode.childNodes.length - 4].rawText || 1;
|
|
17
17
|
allReviews = this.getPage(config, reviews);
|
|
@@ -20,7 +20,7 @@ var UserReviewsScraper = class {
|
|
|
20
20
|
console.log("Fetching all pages", pages);
|
|
21
21
|
for (let i = 2; i <= pages; i++) {
|
|
22
22
|
console.log("Fetching page", i, "out of", pages, "...");
|
|
23
|
-
const reviews = parse(await fetchPage(userReviewsUrl(user, i, { language: options?.language }), { ...options?.request })).querySelectorAll(".user-reviews .article");
|
|
23
|
+
const reviews = parse(await fetchPage(userReviewsUrl(user, i, { language: options?.language }), { ...options?.request })).querySelectorAll(".user-tab-reviews .article");
|
|
24
24
|
allReviews = [...allReviews, ...this.getPage(config, reviews)];
|
|
25
25
|
if (config.allPagesDelay) await sleep(config.allPagesDelay);
|
|
26
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-reviews.service.mjs","names":[],"sources":["../../src/services/user-reviews.service.ts"],"sourcesContent":["import { HTMLElement, parse } from 'node-html-parser';\nimport { CSFDColorRating, CSFDStars } from '../dto/global';\nimport { CSFDUserReviews, CSFDUserReviewsConfig } from '../dto/user-reviews';\nimport { fetchPage } from '../fetchers';\nimport { sleep } from '../helpers/global.helper';\nimport {\n getUserReviewColorRating,\n getUserReviewDate,\n getUserReviewId,\n getUserReviewPoster,\n getUserReviewRating,\n getUserReviewText,\n getUserReviewTitle,\n getUserReviewType,\n getUserReviewUrl,\n getUserReviewYear\n} from '../helpers/user-reviews.helper';\nimport { CSFDOptions } from '../types';\nimport { userReviewsUrl } from '../vars';\n\nexport class UserReviewsScraper {\n public async userReviews(\n user: string | number,\n config?: CSFDUserReviewsConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserReviews[]> {\n let allReviews: CSFDUserReviews[] = [];\n const pageToFetch = config?.page || 1;\n const url = userReviewsUrl(user, pageToFetch > 1 ? pageToFetch : undefined, {
|
|
1
|
+
{"version":3,"file":"user-reviews.service.mjs","names":[],"sources":["../../src/services/user-reviews.service.ts"],"sourcesContent":["import { HTMLElement, parse } from 'node-html-parser';\nimport { CSFDColorRating, CSFDStars } from '../dto/global';\nimport { CSFDUserReviews, CSFDUserReviewsConfig } from '../dto/user-reviews';\nimport { fetchPage } from '../fetchers';\nimport { sleep } from '../helpers/global.helper';\nimport {\n getUserReviewColorRating,\n getUserReviewDate,\n getUserReviewId,\n getUserReviewPoster,\n getUserReviewRating,\n getUserReviewText,\n getUserReviewTitle,\n getUserReviewType,\n getUserReviewUrl,\n getUserReviewYear\n} from '../helpers/user-reviews.helper';\nimport { CSFDOptions } from '../types';\nimport { userReviewsUrl } from '../vars';\n\nexport class UserReviewsScraper {\n public async userReviews(\n user: string | number,\n config?: CSFDUserReviewsConfig,\n options?: CSFDOptions\n ): Promise<CSFDUserReviews[]> {\n let allReviews: CSFDUserReviews[] = [];\n const pageToFetch = config?.page || 1;\n const url = userReviewsUrl(user, pageToFetch > 1 ? pageToFetch : undefined, {\n language: options?.language\n });\n const response = await fetchPage(url, { ...options?.request });\n const items = parse(response);\n const reviews = items.querySelectorAll('.user-tab-reviews .article');\n\n // Get number of pages\n const pagesNode = items.querySelector('.pagination');\n const pages = +pagesNode?.childNodes[pagesNode.childNodes.length - 4].rawText || 1;\n\n allReviews = this.getPage(config, reviews);\n\n if (config?.allPages) {\n console.log('User', user, url);\n console.log('Fetching all pages', pages);\n for (let i = 2; i <= pages; i++) {\n console.log('Fetching page', i, 'out of', pages, '...');\n const url = userReviewsUrl(user, i, { language: options?.language });\n const response = await fetchPage(url, { ...options?.request });\n\n const items = parse(response);\n const reviews = items.querySelectorAll('.user-tab-reviews .article');\n allReviews = [...allReviews, ...this.getPage(config, reviews)];\n\n // Sleep\n if (config.allPagesDelay) {\n await sleep(config.allPagesDelay);\n }\n }\n return allReviews;\n }\n\n return allReviews;\n }\n\n private getPage(config: CSFDUserReviewsConfig, reviews: HTMLElement[]) {\n const films: CSFDUserReviews[] = [];\n if (config) {\n if (config.includesOnly?.length && config.excludes?.length) {\n console.warn(\n `node-csfd-api:\n You can not use both parameters 'includesOnly' and 'excludes'.\n Parameter 'includesOnly' will be used now:`,\n config.includesOnly\n );\n }\n }\n\n for (const el of reviews) {\n const type = getUserReviewType(el);\n\n // Filtering includesOnly\n if (config?.includesOnly?.length) {\n if (config.includesOnly.some((include) => type === include)) {\n films.push(this.buildUserReviews(el));\n }\n // Filter excludes\n } else if (config?.excludes?.length) {\n if (!config.excludes.some((exclude) => type === exclude)) {\n films.push(this.buildUserReviews(el));\n }\n } else {\n // Without filtering\n films.push(this.buildUserReviews(el));\n }\n }\n return films;\n }\n\n private buildUserReviews(el: HTMLElement): CSFDUserReviews {\n return {\n id: getUserReviewId(el),\n title: getUserReviewTitle(el),\n year: getUserReviewYear(el),\n type: getUserReviewType(el),\n url: getUserReviewUrl(el),\n colorRating: getUserReviewColorRating(el) as CSFDColorRating,\n userDate: getUserReviewDate(el),\n userRating: getUserReviewRating(el) as CSFDStars,\n text: getUserReviewText(el),\n poster: getUserReviewPoster(el)\n };\n }\n}\n"],"mappings":";;;;;;;AAoBA,IAAa,qBAAb,MAAgC;CAC9B,MAAa,YACX,MACA,QACA,SAC4B;EAC5B,IAAI,aAAgC,EAAE;EACtC,MAAM,cAAc,QAAQ,QAAQ;EACpC,MAAM,MAAM,eAAe,MAAM,cAAc,IAAI,cAAc,QAAW,EAC1E,UAAU,SAAS,UACpB,CAAC;EAEF,MAAM,QAAQ,MADG,MAAM,UAAU,KAAK,EAAE,GAAG,SAAS,SAAS,CAAC,CACjC;EAC7B,MAAM,UAAU,MAAM,iBAAiB,6BAA6B;EAGpE,MAAM,YAAY,MAAM,cAAc,cAAc;EACpD,MAAM,QAAQ,CAAC,WAAW,WAAW,UAAU,WAAW,SAAS,GAAG,WAAW;AAEjF,eAAa,KAAK,QAAQ,QAAQ,QAAQ;AAE1C,MAAI,QAAQ,UAAU;AACpB,WAAQ,IAAI,QAAQ,MAAM,IAAI;AAC9B,WAAQ,IAAI,sBAAsB,MAAM;AACxC,QAAK,IAAI,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,YAAQ,IAAI,iBAAiB,GAAG,UAAU,OAAO,MAAM;IAKvD,MAAM,UADQ,MAFG,MAAM,UADX,eAAe,MAAM,GAAG,EAAE,UAAU,SAAS,UAAU,CAAC,EAC9B,EAAE,GAAG,SAAS,SAAS,CAAC,CAEjC,CACP,iBAAiB,6BAA6B;AACpE,iBAAa,CAAC,GAAG,YAAY,GAAG,KAAK,QAAQ,QAAQ,QAAQ,CAAC;AAG9D,QAAI,OAAO,cACT,OAAM,MAAM,OAAO,cAAc;;AAGrC,UAAO;;AAGT,SAAO;;CAGT,AAAQ,QAAQ,QAA+B,SAAwB;EACrE,MAAM,QAA2B,EAAE;AACnC,MAAI,QACF;OAAI,OAAO,cAAc,UAAU,OAAO,UAAU,OAClD,SAAQ,KACN;;qDAGA,OAAO,aACR;;AAIL,OAAK,MAAM,MAAM,SAAS;GACxB,MAAM,OAAO,kBAAkB,GAAG;AAGlC,OAAI,QAAQ,cAAc,QACxB;QAAI,OAAO,aAAa,MAAM,YAAY,SAAS,QAAQ,CACzD,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;cAG9B,QAAQ,UAAU,QAC3B;QAAI,CAAC,OAAO,SAAS,MAAM,YAAY,SAAS,QAAQ,CACtD,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;SAIvC,OAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC;;AAGzC,SAAO;;CAGT,AAAQ,iBAAiB,IAAkC;AACzD,SAAO;GACL,IAAI,gBAAgB,GAAG;GACvB,OAAO,mBAAmB,GAAG;GAC7B,MAAM,kBAAkB,GAAG;GAC3B,MAAM,kBAAkB,GAAG;GAC3B,KAAK,iBAAiB,GAAG;GACzB,aAAa,yBAAyB,GAAG;GACzC,UAAU,kBAAkB,GAAG;GAC/B,YAAY,oBAAoB,GAAG;GACnC,MAAM,kBAAkB,GAAG;GAC3B,QAAQ,oBAAoB,GAAG;GAChC"}
|