node-csfd-api 5.5.0-next.0 → 5.6.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { csfd } from "../src/index.js";
2
+ import { csfd } from "../index.js";
3
3
  import { writeFile } from "node:fs/promises";
4
4
 
5
5
  //#region src/bin/export-ratings.ts
package/bin/mcp-server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { csfd } from "../src/index.js";
2
+ import { csfd } from "../index.js";
3
3
  import { name, version } from "../package.js";
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/bin/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { csfd } from "../src/index.js";
2
+ import { csfd } from "../index.js";
3
3
  import { homepage, name, version } from "../package.js";
4
4
  import "dotenv/config";
5
5
  import express from "express";
@@ -43,7 +43,7 @@ const parseAge = (text) => {
43
43
  };
44
44
  const parseBirthPlace = (text) => text.trim().replace(/<br>/g, "").trim();
45
45
  const getCreatorFilms = (el) => {
46
- const filmNodes = el?.querySelectorAll(".updated-box")?.[0]?.querySelectorAll("table tr") ?? [];
46
+ const filmNodes = el?.querySelector(".updated-box")?.querySelectorAll("table tr") ?? [];
47
47
  let yearCache = null;
48
48
  return filmNodes.map((filmNode) => {
49
49
  const id = getCreatorId(filmNode.querySelector("td.name .film-title-name")?.attributes?.href);
@@ -1 +1 @@
1
- {"version":3,"file":"creator.helper.cjs","names":["parseColor","parseIdFromUrl","parseDate","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, parseDate, 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 | null; 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 | null = null;\n\n if (text) {\n const parts = text.split('\\n');\n const birthdayRow = parts.find((x) => x.includes('nar.'));\n birthday = birthdayRow ? parseDate(parseBirthday(birthdayRow)) : null;\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,sBAAAA,WAAW,KAAK;;AAGzB,MAAa,gBAAgB,QAAkD;AAC7E,QAAO,MAAMC,sBAAAA,eAAe,IAAI,GAAG;;AAGrC,MAAa,kBAAkB,OAA0C;AAEvE,SADW,IAAI,cAAc,KAAK,GACvB,WAAW,MAAM,IAAI;;AAGlC,MAAa,0BACX,OACiE;CACjE,MAAM,YAAY,IAAI,cAAc,6BAA6B;CACjE,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,WAA0B;AAE9B,KAAI,MAAM;EAER,MAAM,cADQ,KAAK,MAAM,KAAK,CACJ,MAAM,MAAM,EAAE,SAAS,OAAO,CAAC;AACzD,aAAW,cAAcC,sBAAAA,UAAU,cAAc,YAAY,CAAC,GAAG;;CAGnE,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,sBAAAA,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"}
1
+ {"version":3,"file":"creator.helper.cjs","names":["parseColor","parseIdFromUrl","parseDate","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, parseDate, 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 | null; 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 | null = null;\n\n if (text) {\n const parts = text.split('\\n');\n const birthdayRow = parts.find((x) => x.includes('nar.'));\n birthday = birthdayRow ? parseDate(parseBirthday(birthdayRow)) : null;\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 // Optimization: Use querySelector instead of querySelectorAll(...)[0]\n const filmNodes = el?.querySelector('.updated-box')?.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,sBAAAA,WAAW,KAAK;;AAGzB,MAAa,gBAAgB,QAAkD;AAC7E,QAAO,MAAMC,sBAAAA,eAAe,IAAI,GAAG;;AAGrC,MAAa,kBAAkB,OAA0C;AAEvE,SADW,IAAI,cAAc,KAAK,GACvB,WAAW,MAAM,IAAI;;AAGlC,MAAa,0BACX,OACiE;CACjE,MAAM,YAAY,IAAI,cAAc,6BAA6B;CACjE,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,WAA0B;AAE9B,KAAI,MAAM;EAER,MAAM,cADQ,KAAK,MAAM,KAAK,CACJ,MAAM,MAAM,EAAE,SAAS,OAAO,CAAC;AACzD,aAAW,cAAcC,sBAAAA,UAAU,cAAc,YAAY,CAAC,GAAG;;CAGnE,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,sBAAAA,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;CAEjF,MAAM,YAAY,IAAI,cAAc,eAAe,EAAE,iBAAiB,WAAW,IAAI,EAAE;CACvF,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"}
@@ -43,7 +43,7 @@ const parseAge = (text) => {
43
43
  };
44
44
  const parseBirthPlace = (text) => text.trim().replace(/<br>/g, "").trim();
45
45
  const getCreatorFilms = (el) => {
46
- const filmNodes = el?.querySelectorAll(".updated-box")?.[0]?.querySelectorAll("table tr") ?? [];
46
+ const filmNodes = el?.querySelector(".updated-box")?.querySelectorAll("table tr") ?? [];
47
47
  let yearCache = null;
48
48
  return filmNodes.map((filmNode) => {
49
49
  const id = getCreatorId(filmNode.querySelector("td.name .film-title-name")?.attributes?.href);
@@ -1 +1 @@
1
- {"version":3,"file":"creator.helper.js","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, parseDate, 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 | null; 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 | null = null;\n\n if (text) {\n const parts = text.split('\\n');\n const birthdayRow = parts.find((x) => x.includes('nar.'));\n birthday = birthdayRow ? parseDate(parseBirthday(birthdayRow)) : null;\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,OACiE;CACjE,MAAM,YAAY,IAAI,cAAc,6BAA6B;CACjE,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,WAA0B;AAE9B,KAAI,MAAM;EAER,MAAM,cADQ,KAAK,MAAM,KAAK,CACJ,MAAM,MAAM,EAAE,SAAS,OAAO,CAAC;AACzD,aAAW,cAAc,UAAU,cAAc,YAAY,CAAC,GAAG;;CAGnE,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"}
1
+ {"version":3,"file":"creator.helper.js","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, parseDate, 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 | null; 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 | null = null;\n\n if (text) {\n const parts = text.split('\\n');\n const birthdayRow = parts.find((x) => x.includes('nar.'));\n birthday = birthdayRow ? parseDate(parseBirthday(birthdayRow)) : null;\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 // Optimization: Use querySelector instead of querySelectorAll(...)[0]\n const filmNodes = el?.querySelector('.updated-box')?.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,OACiE;CACjE,MAAM,YAAY,IAAI,cAAc,6BAA6B;CACjE,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,WAA0B;AAE9B,KAAI,MAAM;EAER,MAAM,cADQ,KAAK,MAAM,KAAK,CACJ,MAAM,MAAM,EAAE,SAAS,OAAO,CAAC;AACzD,aAAW,cAAc,UAAU,cAAc,YAAY,CAAC,GAAG;;CAGnE,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;CAEjF,MAAM,YAAY,IAAI,cAAc,eAAe,EAAE,iBAAiB,WAAW,IAAI,EAAE;CACvF,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"}
@@ -8,7 +8,7 @@ const getSearchTitle = (el) => {
8
8
  return el.querySelector(".film-title-name").text;
9
9
  };
10
10
  const getSearchYear = (el) => {
11
- return +el.querySelectorAll(".film-title-info .info")[0]?.innerText.replace(/[{()}]/g, "");
11
+ return +el.querySelector(".film-title-info .info")?.innerText.replace(/[{()}]/g, "");
12
12
  };
13
13
  const getSearchUrl = (el) => {
14
14
  return el.querySelector(".film-title-name").attributes.href;
@@ -1 +1 @@
1
- {"version":3,"file":"search.helper.cjs","names":["parseFilmType","parseColor","addProtocol","parseIdFromUrl"],"sources":["../../src/helpers/search.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes } from '../dto/global';\nimport { CSFDMovieCreator } from '../dto/movie';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { addProtocol, parseColor, parseFilmType, parseIdFromUrl } from './global.helper';\n\ntype Creator = 'Režie:' | 'Hrají:';\n\nexport const getSearchType = (el: HTMLElement): CSFDFilmTypes => {\n const type = el.querySelectorAll('.film-title-info .info')[1];\n return parseFilmType(type?.innerText?.replace(/[{()}]/g, '')?.trim() || 'film');\n};\n\nexport const getSearchTitle = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').text;\n};\n\nexport const getSearchYear = (el: HTMLElement): number => {\n return +el.querySelectorAll('.film-title-info .info')[0]?.innerText.replace(/[{()}]/g, '');\n};\n\nexport const getSearchUrl = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').attributes.href;\n};\n\nexport const getSearchColorRating = (el: HTMLElement): CSFDColorRating => {\n return parseColor(\n el.querySelector('.article-header i.icon').classNames.split(' ').pop() as CSFDColors\n );\n};\n\nexport const getSearchPoster = (el: HTMLElement): string => {\n const image = el.querySelector('img').attributes.src;\n return addProtocol(image);\n};\n\nexport const getSearchOrigins = (el: HTMLElement): string[] => {\n const originsRaw = el.querySelector('.article-content p .info')?.text;\n if (!originsRaw) return [];\n const originsAll = originsRaw?.split(', ')?.[0];\n return originsAll?.split('/').map((country) => country.trim());\n};\n\nexport const parseSearchPeople = (\n el: HTMLElement,\n type: 'directors' | 'actors'\n): CSFDMovieCreator[] => {\n let who: Creator;\n if (type === 'directors') who = 'Režie:';\n if (type === 'actors') who = 'Hrají:';\n\n const peopleNode = Array.from(el && el.querySelectorAll('.article-content p')).find((el) =>\n el.textContent.includes(who)\n );\n\n if (peopleNode) {\n const people = Array.from(peopleNode.querySelectorAll('a')) as unknown as HTMLElement[];\n\n return people.map((person) => {\n return {\n id: parseIdFromUrl(person.attributes.href),\n name: person.innerText.trim(),\n url: `https://www.csfd.cz${person.attributes.href}`\n };\n });\n } else {\n return [];\n }\n};\n"],"mappings":";;AAQA,MAAa,iBAAiB,OAAmC;CAC/D,MAAM,OAAO,GAAG,iBAAiB,yBAAyB,CAAC;AAC3D,QAAOA,sBAAAA,cAAc,MAAM,WAAW,QAAQ,WAAW,GAAG,EAAE,MAAM,IAAI,OAAO;;AAGjF,MAAa,kBAAkB,OAA4B;AACzD,QAAO,GAAG,cAAc,mBAAmB,CAAC;;AAG9C,MAAa,iBAAiB,OAA4B;AACxD,QAAO,CAAC,GAAG,iBAAiB,yBAAyB,CAAC,IAAI,UAAU,QAAQ,WAAW,GAAG;;AAG5F,MAAa,gBAAgB,OAA4B;AACvD,QAAO,GAAG,cAAc,mBAAmB,CAAC,WAAW;;AAGzD,MAAa,wBAAwB,OAAqC;AACxE,QAAOC,sBAAAA,WACL,GAAG,cAAc,yBAAyB,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK,CACvE;;AAGH,MAAa,mBAAmB,OAA4B;CAC1D,MAAM,QAAQ,GAAG,cAAc,MAAM,CAAC,WAAW;AACjD,QAAOC,sBAAAA,YAAY,MAAM;;AAG3B,MAAa,oBAAoB,OAA8B;CAC7D,MAAM,aAAa,GAAG,cAAc,2BAA2B,EAAE;AACjE,KAAI,CAAC,WAAY,QAAO,EAAE;AAE1B,SADmB,YAAY,MAAM,KAAK,GAAG,KAC1B,MAAM,IAAI,CAAC,KAAK,YAAY,QAAQ,MAAM,CAAC;;AAGhE,MAAa,qBACX,IACA,SACuB;CACvB,IAAI;AACJ,KAAI,SAAS,YAAa,OAAM;AAChC,KAAI,SAAS,SAAU,OAAM;CAE7B,MAAM,aAAa,MAAM,KAAK,MAAM,GAAG,iBAAiB,qBAAqB,CAAC,CAAC,MAAM,OACnF,GAAG,YAAY,SAAS,IAAI,CAC7B;AAED,KAAI,WAGF,QAFe,MAAM,KAAK,WAAW,iBAAiB,IAAI,CAAC,CAE7C,KAAK,WAAW;AAC5B,SAAO;GACL,IAAIC,sBAAAA,eAAe,OAAO,WAAW,KAAK;GAC1C,MAAM,OAAO,UAAU,MAAM;GAC7B,KAAK,sBAAsB,OAAO,WAAW;GAC9C;GACD;KAEF,QAAO,EAAE"}
1
+ {"version":3,"file":"search.helper.cjs","names":["parseFilmType","parseColor","addProtocol","parseIdFromUrl"],"sources":["../../src/helpers/search.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes } from '../dto/global';\nimport { CSFDMovieCreator } from '../dto/movie';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { addProtocol, parseColor, parseFilmType, parseIdFromUrl } from './global.helper';\n\ntype Creator = 'Režie:' | 'Hrají:';\n\nexport const getSearchType = (el: HTMLElement): CSFDFilmTypes => {\n const type = el.querySelectorAll('.film-title-info .info')[1];\n return parseFilmType(type?.innerText?.replace(/[{()}]/g, '')?.trim() || 'film');\n};\n\nexport const getSearchTitle = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').text;\n};\n\nexport const getSearchYear = (el: HTMLElement): number => {\n // Optimization: Use querySelector instead of querySelectorAll(...)[0]\n return +el.querySelector('.film-title-info .info')?.innerText.replace(/[{()}]/g, '');\n};\n\nexport const getSearchUrl = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').attributes.href;\n};\n\nexport const getSearchColorRating = (el: HTMLElement): CSFDColorRating => {\n return parseColor(\n el.querySelector('.article-header i.icon').classNames.split(' ').pop() as CSFDColors\n );\n};\n\nexport const getSearchPoster = (el: HTMLElement): string => {\n const image = el.querySelector('img').attributes.src;\n return addProtocol(image);\n};\n\nexport const getSearchOrigins = (el: HTMLElement): string[] => {\n const originsRaw = el.querySelector('.article-content p .info')?.text;\n if (!originsRaw) return [];\n const originsAll = originsRaw?.split(', ')?.[0];\n return originsAll?.split('/').map((country) => country.trim());\n};\n\nexport const parseSearchPeople = (\n el: HTMLElement,\n type: 'directors' | 'actors'\n): CSFDMovieCreator[] => {\n let who: Creator;\n if (type === 'directors') who = 'Režie:';\n if (type === 'actors') who = 'Hrají:';\n\n const peopleNode = Array.from(el && el.querySelectorAll('.article-content p')).find((el) =>\n el.textContent.includes(who)\n );\n\n if (peopleNode) {\n const people = Array.from(peopleNode.querySelectorAll('a')) as unknown as HTMLElement[];\n\n return people.map((person) => {\n return {\n id: parseIdFromUrl(person.attributes.href),\n name: person.innerText.trim(),\n url: `https://www.csfd.cz${person.attributes.href}`\n };\n });\n } else {\n return [];\n }\n};\n"],"mappings":";;AAQA,MAAa,iBAAiB,OAAmC;CAC/D,MAAM,OAAO,GAAG,iBAAiB,yBAAyB,CAAC;AAC3D,QAAOA,sBAAAA,cAAc,MAAM,WAAW,QAAQ,WAAW,GAAG,EAAE,MAAM,IAAI,OAAO;;AAGjF,MAAa,kBAAkB,OAA4B;AACzD,QAAO,GAAG,cAAc,mBAAmB,CAAC;;AAG9C,MAAa,iBAAiB,OAA4B;AAExD,QAAO,CAAC,GAAG,cAAc,yBAAyB,EAAE,UAAU,QAAQ,WAAW,GAAG;;AAGtF,MAAa,gBAAgB,OAA4B;AACvD,QAAO,GAAG,cAAc,mBAAmB,CAAC,WAAW;;AAGzD,MAAa,wBAAwB,OAAqC;AACxE,QAAOC,sBAAAA,WACL,GAAG,cAAc,yBAAyB,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK,CACvE;;AAGH,MAAa,mBAAmB,OAA4B;CAC1D,MAAM,QAAQ,GAAG,cAAc,MAAM,CAAC,WAAW;AACjD,QAAOC,sBAAAA,YAAY,MAAM;;AAG3B,MAAa,oBAAoB,OAA8B;CAC7D,MAAM,aAAa,GAAG,cAAc,2BAA2B,EAAE;AACjE,KAAI,CAAC,WAAY,QAAO,EAAE;AAE1B,SADmB,YAAY,MAAM,KAAK,GAAG,KAC1B,MAAM,IAAI,CAAC,KAAK,YAAY,QAAQ,MAAM,CAAC;;AAGhE,MAAa,qBACX,IACA,SACuB;CACvB,IAAI;AACJ,KAAI,SAAS,YAAa,OAAM;AAChC,KAAI,SAAS,SAAU,OAAM;CAE7B,MAAM,aAAa,MAAM,KAAK,MAAM,GAAG,iBAAiB,qBAAqB,CAAC,CAAC,MAAM,OACnF,GAAG,YAAY,SAAS,IAAI,CAC7B;AAED,KAAI,WAGF,QAFe,MAAM,KAAK,WAAW,iBAAiB,IAAI,CAAC,CAE7C,KAAK,WAAW;AAC5B,SAAO;GACL,IAAIC,sBAAAA,eAAe,OAAO,WAAW,KAAK;GAC1C,MAAM,OAAO,UAAU,MAAM;GAC7B,KAAK,sBAAsB,OAAO,WAAW;GAC9C;GACD;KAEF,QAAO,EAAE"}
@@ -8,7 +8,7 @@ const getSearchTitle = (el) => {
8
8
  return el.querySelector(".film-title-name").text;
9
9
  };
10
10
  const getSearchYear = (el) => {
11
- return +el.querySelectorAll(".film-title-info .info")[0]?.innerText.replace(/[{()}]/g, "");
11
+ return +el.querySelector(".film-title-info .info")?.innerText.replace(/[{()}]/g, "");
12
12
  };
13
13
  const getSearchUrl = (el) => {
14
14
  return el.querySelector(".film-title-name").attributes.href;
@@ -1 +1 @@
1
- {"version":3,"file":"search.helper.js","names":[],"sources":["../../src/helpers/search.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes } from '../dto/global';\nimport { CSFDMovieCreator } from '../dto/movie';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { addProtocol, parseColor, parseFilmType, parseIdFromUrl } from './global.helper';\n\ntype Creator = 'Režie:' | 'Hrají:';\n\nexport const getSearchType = (el: HTMLElement): CSFDFilmTypes => {\n const type = el.querySelectorAll('.film-title-info .info')[1];\n return parseFilmType(type?.innerText?.replace(/[{()}]/g, '')?.trim() || 'film');\n};\n\nexport const getSearchTitle = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').text;\n};\n\nexport const getSearchYear = (el: HTMLElement): number => {\n return +el.querySelectorAll('.film-title-info .info')[0]?.innerText.replace(/[{()}]/g, '');\n};\n\nexport const getSearchUrl = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').attributes.href;\n};\n\nexport const getSearchColorRating = (el: HTMLElement): CSFDColorRating => {\n return parseColor(\n el.querySelector('.article-header i.icon').classNames.split(' ').pop() as CSFDColors\n );\n};\n\nexport const getSearchPoster = (el: HTMLElement): string => {\n const image = el.querySelector('img').attributes.src;\n return addProtocol(image);\n};\n\nexport const getSearchOrigins = (el: HTMLElement): string[] => {\n const originsRaw = el.querySelector('.article-content p .info')?.text;\n if (!originsRaw) return [];\n const originsAll = originsRaw?.split(', ')?.[0];\n return originsAll?.split('/').map((country) => country.trim());\n};\n\nexport const parseSearchPeople = (\n el: HTMLElement,\n type: 'directors' | 'actors'\n): CSFDMovieCreator[] => {\n let who: Creator;\n if (type === 'directors') who = 'Režie:';\n if (type === 'actors') who = 'Hrají:';\n\n const peopleNode = Array.from(el && el.querySelectorAll('.article-content p')).find((el) =>\n el.textContent.includes(who)\n );\n\n if (peopleNode) {\n const people = Array.from(peopleNode.querySelectorAll('a')) as unknown as HTMLElement[];\n\n return people.map((person) => {\n return {\n id: parseIdFromUrl(person.attributes.href),\n name: person.innerText.trim(),\n url: `https://www.csfd.cz${person.attributes.href}`\n };\n });\n } else {\n return [];\n }\n};\n"],"mappings":";;AAQA,MAAa,iBAAiB,OAAmC;CAC/D,MAAM,OAAO,GAAG,iBAAiB,yBAAyB,CAAC;AAC3D,QAAO,cAAc,MAAM,WAAW,QAAQ,WAAW,GAAG,EAAE,MAAM,IAAI,OAAO;;AAGjF,MAAa,kBAAkB,OAA4B;AACzD,QAAO,GAAG,cAAc,mBAAmB,CAAC;;AAG9C,MAAa,iBAAiB,OAA4B;AACxD,QAAO,CAAC,GAAG,iBAAiB,yBAAyB,CAAC,IAAI,UAAU,QAAQ,WAAW,GAAG;;AAG5F,MAAa,gBAAgB,OAA4B;AACvD,QAAO,GAAG,cAAc,mBAAmB,CAAC,WAAW;;AAGzD,MAAa,wBAAwB,OAAqC;AACxE,QAAO,WACL,GAAG,cAAc,yBAAyB,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK,CACvE;;AAGH,MAAa,mBAAmB,OAA4B;CAC1D,MAAM,QAAQ,GAAG,cAAc,MAAM,CAAC,WAAW;AACjD,QAAO,YAAY,MAAM;;AAG3B,MAAa,oBAAoB,OAA8B;CAC7D,MAAM,aAAa,GAAG,cAAc,2BAA2B,EAAE;AACjE,KAAI,CAAC,WAAY,QAAO,EAAE;AAE1B,SADmB,YAAY,MAAM,KAAK,GAAG,KAC1B,MAAM,IAAI,CAAC,KAAK,YAAY,QAAQ,MAAM,CAAC;;AAGhE,MAAa,qBACX,IACA,SACuB;CACvB,IAAI;AACJ,KAAI,SAAS,YAAa,OAAM;AAChC,KAAI,SAAS,SAAU,OAAM;CAE7B,MAAM,aAAa,MAAM,KAAK,MAAM,GAAG,iBAAiB,qBAAqB,CAAC,CAAC,MAAM,OACnF,GAAG,YAAY,SAAS,IAAI,CAC7B;AAED,KAAI,WAGF,QAFe,MAAM,KAAK,WAAW,iBAAiB,IAAI,CAAC,CAE7C,KAAK,WAAW;AAC5B,SAAO;GACL,IAAI,eAAe,OAAO,WAAW,KAAK;GAC1C,MAAM,OAAO,UAAU,MAAM;GAC7B,KAAK,sBAAsB,OAAO,WAAW;GAC9C;GACD;KAEF,QAAO,EAAE"}
1
+ {"version":3,"file":"search.helper.js","names":[],"sources":["../../src/helpers/search.helper.ts"],"sourcesContent":["import { HTMLElement } from 'node-html-parser';\nimport { CSFDColorRating, CSFDFilmTypes } from '../dto/global';\nimport { CSFDMovieCreator } from '../dto/movie';\nimport { CSFDColors } from '../dto/user-ratings';\nimport { addProtocol, parseColor, parseFilmType, parseIdFromUrl } from './global.helper';\n\ntype Creator = 'Režie:' | 'Hrají:';\n\nexport const getSearchType = (el: HTMLElement): CSFDFilmTypes => {\n const type = el.querySelectorAll('.film-title-info .info')[1];\n return parseFilmType(type?.innerText?.replace(/[{()}]/g, '')?.trim() || 'film');\n};\n\nexport const getSearchTitle = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').text;\n};\n\nexport const getSearchYear = (el: HTMLElement): number => {\n // Optimization: Use querySelector instead of querySelectorAll(...)[0]\n return +el.querySelector('.film-title-info .info')?.innerText.replace(/[{()}]/g, '');\n};\n\nexport const getSearchUrl = (el: HTMLElement): string => {\n return el.querySelector('.film-title-name').attributes.href;\n};\n\nexport const getSearchColorRating = (el: HTMLElement): CSFDColorRating => {\n return parseColor(\n el.querySelector('.article-header i.icon').classNames.split(' ').pop() as CSFDColors\n );\n};\n\nexport const getSearchPoster = (el: HTMLElement): string => {\n const image = el.querySelector('img').attributes.src;\n return addProtocol(image);\n};\n\nexport const getSearchOrigins = (el: HTMLElement): string[] => {\n const originsRaw = el.querySelector('.article-content p .info')?.text;\n if (!originsRaw) return [];\n const originsAll = originsRaw?.split(', ')?.[0];\n return originsAll?.split('/').map((country) => country.trim());\n};\n\nexport const parseSearchPeople = (\n el: HTMLElement,\n type: 'directors' | 'actors'\n): CSFDMovieCreator[] => {\n let who: Creator;\n if (type === 'directors') who = 'Režie:';\n if (type === 'actors') who = 'Hrají:';\n\n const peopleNode = Array.from(el && el.querySelectorAll('.article-content p')).find((el) =>\n el.textContent.includes(who)\n );\n\n if (peopleNode) {\n const people = Array.from(peopleNode.querySelectorAll('a')) as unknown as HTMLElement[];\n\n return people.map((person) => {\n return {\n id: parseIdFromUrl(person.attributes.href),\n name: person.innerText.trim(),\n url: `https://www.csfd.cz${person.attributes.href}`\n };\n });\n } else {\n return [];\n }\n};\n"],"mappings":";;AAQA,MAAa,iBAAiB,OAAmC;CAC/D,MAAM,OAAO,GAAG,iBAAiB,yBAAyB,CAAC;AAC3D,QAAO,cAAc,MAAM,WAAW,QAAQ,WAAW,GAAG,EAAE,MAAM,IAAI,OAAO;;AAGjF,MAAa,kBAAkB,OAA4B;AACzD,QAAO,GAAG,cAAc,mBAAmB,CAAC;;AAG9C,MAAa,iBAAiB,OAA4B;AAExD,QAAO,CAAC,GAAG,cAAc,yBAAyB,EAAE,UAAU,QAAQ,WAAW,GAAG;;AAGtF,MAAa,gBAAgB,OAA4B;AACvD,QAAO,GAAG,cAAc,mBAAmB,CAAC,WAAW;;AAGzD,MAAa,wBAAwB,OAAqC;AACxE,QAAO,WACL,GAAG,cAAc,yBAAyB,CAAC,WAAW,MAAM,IAAI,CAAC,KAAK,CACvE;;AAGH,MAAa,mBAAmB,OAA4B;CAC1D,MAAM,QAAQ,GAAG,cAAc,MAAM,CAAC,WAAW;AACjD,QAAO,YAAY,MAAM;;AAG3B,MAAa,oBAAoB,OAA8B;CAC7D,MAAM,aAAa,GAAG,cAAc,2BAA2B,EAAE;AACjE,KAAI,CAAC,WAAY,QAAO,EAAE;AAE1B,SADmB,YAAY,MAAM,KAAK,GAAG,KAC1B,MAAM,IAAI,CAAC,KAAK,YAAY,QAAQ,MAAM,CAAC;;AAGhE,MAAa,qBACX,IACA,SACuB;CACvB,IAAI;AACJ,KAAI,SAAS,YAAa,OAAM;AAChC,KAAI,SAAS,SAAU,OAAM;CAE7B,MAAM,aAAa,MAAM,KAAK,MAAM,GAAG,iBAAiB,qBAAqB,CAAC,CAAC,MAAM,OACnF,GAAG,YAAY,SAAS,IAAI,CAC7B;AAED,KAAI,WAGF,QAFe,MAAM,KAAK,WAAW,iBAAiB,IAAI,CAAC,CAE7C,KAAK,WAAW;AAC5B,SAAO;GACL,IAAI,eAAe,OAAO,WAAW,KAAK;GAC1C,MAAM,OAAO,UAAU,MAAM;GAC7B,KAAK,sBAAsB,OAAO,WAAW;GAC9C;GACD;KAEF,QAAO,EAAE"}
package/package.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  //#region package.json
3
3
  var name = "node-csfd-api";
4
- var version = "5.5.0-next.0";
4
+ var version = "5.6.0-next.0";
5
5
  var homepage = "https://github.com/bartholomej/node-csfd-api#readme";
6
6
 
7
7
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-csfd-api",
3
- "version": "5.5.0-next.0",
3
+ "version": "5.6.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": {
@@ -33,10 +33,17 @@
33
33
  "typescript",
34
34
  "scraper",
35
35
  "parser",
36
- "api"
36
+ "api",
37
+ "mcp",
38
+ "ai",
39
+ "docker",
40
+ "server",
41
+ "cli",
42
+ "claude",
43
+ "letterboxd"
37
44
  ],
38
45
  "engines": {
39
- "node": ">= 18"
46
+ "node": ">= 20"
40
47
  },
41
48
  "license": "MIT",
42
49
  "type": "module",
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- //#region src/fetchers/fetch.polyfill.ts
3
- const fetchSafe = typeof fetch === "function" && fetch || typeof global === "object" && global.fetch || typeof window !== "undefined" && window.fetch;
4
-
5
- //#endregion
6
- export { fetchSafe };
@@ -1,76 +0,0 @@
1
- #!/usr/bin/env node
2
- import { fetchSafe } from "./fetch.polyfill.js";
3
-
4
- //#region src/fetchers/index.ts
5
- const browserProfiles = [
6
- {
7
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
8
- "Sec-Ch-Ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
9
- "Sec-Ch-Ua-Platform": "\"Windows\""
10
- },
11
- {
12
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
13
- "Sec-Ch-Ua": "\"Google Chrome\";v=\"130\", \"Chromium\";v=\"130\", \"Not_A Brand\";v=\"24\"",
14
- "Sec-Ch-Ua-Platform": "\"Windows\""
15
- },
16
- {
17
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
18
- "Sec-Ch-Ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
19
- "Sec-Ch-Ua-Platform": "\"macOS\""
20
- },
21
- {
22
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
23
- "Sec-Ch-Ua": "\"Google Chrome\";v=\"130\", \"Chromium\";v=\"130\", \"Not_A Brand\";v=\"24\"",
24
- "Sec-Ch-Ua-Platform": "\"macOS\""
25
- },
26
- {
27
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
28
- "Sec-Ch-Ua": "\"Microsoft Edge\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
29
- "Sec-Ch-Ua-Platform": "\"Windows\""
30
- },
31
- {
32
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0",
33
- "Sec-Ch-Ua": "\"Microsoft Edge\";v=\"130\", \"Chromium\";v=\"130\", \"Not_A Brand\";v=\"24\"",
34
- "Sec-Ch-Ua-Platform": "\"Windows\""
35
- }
36
- ];
37
- const randomProfile = () => browserProfiles[Math.floor(Math.random() * browserProfiles.length)];
38
- const baseHeaders = {
39
- Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
40
- "Accept-Language": "cs-CZ,cs;q=0.9,en-US;q=0.8,en;q=0.7",
41
- "Accept-Encoding": "gzip, deflate, br",
42
- "Cache-Control": "max-age=0",
43
- Connection: "keep-alive",
44
- "Sec-Ch-Ua-Mobile": "?0",
45
- "Sec-Fetch-Dest": "document",
46
- "Sec-Fetch-Mode": "navigate",
47
- "Sec-Fetch-Site": "none",
48
- "Sec-Fetch-User": "?1",
49
- "Upgrade-Insecure-Requests": "1"
50
- };
51
- const fetchPage = async (url, optionsRequest) => {
52
- try {
53
- const mergedHeaders = new Headers({
54
- ...baseHeaders,
55
- ...randomProfile()
56
- });
57
- if (optionsRequest?.headers) new Headers(optionsRequest.headers).forEach((value, key) => mergedHeaders.set(key, value));
58
- const { headers: _, ...restOptions } = optionsRequest || {};
59
- const response = await fetchSafe(url, {
60
- credentials: "omit",
61
- ...restOptions,
62
- headers: mergedHeaders
63
- });
64
- if (!response.ok) throw new Error(`node-csfd-api: Bad response ${response.status} for url: ${url}`);
65
- const html = await response.text();
66
- if (html.includes("Making sure you're not a bot!")) console.warn("node-csfd-api: Trap detected.");
67
- return html;
68
- } catch (e) {
69
- if (e instanceof Error) console.error(e.message);
70
- else console.error(String(e));
71
- return "Error";
72
- }
73
- };
74
-
75
- //#endregion
76
- export { fetchPage };
@@ -1,80 +0,0 @@
1
- #!/usr/bin/env node
2
- import { parseColor, parseIdFromUrl } from "./global.helper.js";
3
-
4
- //#region src/helpers/cinema.helper.ts
5
- const getCinemaColorRating = (el) => {
6
- const classes = el?.classNames.split(" ") ?? [];
7
- const last = classes.length ? classes[classes.length - 1] : void 0;
8
- return last ? parseColor(last) : "unknown";
9
- };
10
- const getCinemaId = (el) => {
11
- return +el?.id?.split("-")[1];
12
- };
13
- const getCinemaUrlId = (url) => {
14
- if (!url) return null;
15
- return parseIdFromUrl(url);
16
- };
17
- const getCinemaCoords = (el) => {
18
- if (!el) return null;
19
- const linkMapsEl = el.querySelector("a[href*=\"q=\"]");
20
- if (!linkMapsEl) return null;
21
- const [_, latLng] = linkMapsEl.getAttribute("href").split("q=");
22
- const coords = latLng.split(",");
23
- if (coords.length !== 2) return null;
24
- const lat = Number(coords[0]);
25
- const lng = Number(coords[1]);
26
- if (Number.isFinite(lat) && Number.isFinite(lng)) return {
27
- lat,
28
- lng
29
- };
30
- return null;
31
- };
32
- const getCinemaUrl = (el) => {
33
- if (!el) return "";
34
- return el.querySelector(".cinema-logo a")?.attributes.href ?? "";
35
- };
36
- const parseCinema = (el) => {
37
- const [city, name] = el.querySelector("header h2").innerText.trim().split(" - ");
38
- return {
39
- city,
40
- name
41
- };
42
- };
43
- const getGroupedFilmsByDate = (el) => {
44
- const divs = el.querySelectorAll(":scope > div");
45
- return divs.map((_, index) => index).filter((index) => index % 2 === 0).map((index) => {
46
- const [date, films] = divs.slice(index, index + 2);
47
- return {
48
- date: date?.firstChild?.textContent?.trim() ?? null,
49
- films: getCinemaFilms("", films)
50
- };
51
- });
52
- };
53
- const getCinemaFilms = (date, el) => {
54
- return el.querySelectorAll(".cinema-table tr").map((filmNode) => {
55
- const url = filmNode.querySelector("td.name h3 a")?.attributes.href;
56
- const id = url ? getCinemaUrlId(url) : null;
57
- const title = filmNode.querySelector(".name h3")?.text.trim();
58
- const colorRating = getCinemaColorRating(filmNode.querySelector(".name .icon"));
59
- const showTimes = filmNode.querySelectorAll(".td-time")?.map((x) => x.textContent.trim());
60
- const meta = filmNode.querySelectorAll(".td-title span")?.map((x) => x.text.trim());
61
- return {
62
- id,
63
- title,
64
- url,
65
- colorRating,
66
- showTimes,
67
- meta: parseMeta(meta)
68
- };
69
- });
70
- };
71
- const parseMeta = (meta) => {
72
- const metaConvert = [];
73
- for (const element of meta) if (element === "T") metaConvert.push("subtitles");
74
- else if (element === "D") metaConvert.push("dubbing");
75
- else metaConvert.push(element);
76
- return metaConvert;
77
- };
78
-
79
- //#endregion
80
- export { getCinemaCoords, getCinemaId, getCinemaUrl, getGroupedFilmsByDate, parseCinema };
@@ -1,69 +0,0 @@
1
- #!/usr/bin/env node
2
- import { addProtocol, parseColor, parseDate, parseIdFromUrl } from "./global.helper.js";
3
-
4
- //#region src/helpers/creator.helper.ts
5
- const getCreatorColorRating = (el) => {
6
- const classes = el?.classNames.split(" ") ?? [];
7
- const last = classes[classes.length - 1];
8
- return parseColor(last);
9
- };
10
- const getCreatorId = (url) => {
11
- return url ? parseIdFromUrl(url) : null;
12
- };
13
- const getCreatorName = (el) => {
14
- return (el?.querySelector("h1"))?.innerText?.trim() ?? null;
15
- };
16
- const getCreatorBirthdayInfo = (el) => {
17
- const infoBlock = el?.querySelector(".creator-profile-details p");
18
- const text = infoBlock?.innerHTML.trim();
19
- const birthPlaceRow = infoBlock?.querySelector(".info-place")?.innerText.trim();
20
- const ageRow = infoBlock?.querySelector(".info")?.innerText.trim();
21
- let birthday = null;
22
- if (text) {
23
- const birthdayRow = text.split("\n").find((x) => x.includes("nar."));
24
- birthday = birthdayRow ? parseDate(parseBirthday(birthdayRow)) : null;
25
- }
26
- const age = ageRow ? +parseAge(ageRow) : null;
27
- const birthPlace = birthPlaceRow ? parseBirthPlace(birthPlaceRow) : "";
28
- return {
29
- birthday,
30
- age,
31
- birthPlace
32
- };
33
- };
34
- const getCreatorBio = (el) => {
35
- return (el?.querySelector(".article-content p"))?.text?.trim().split("\n")[0]?.trim() || null;
36
- };
37
- const getCreatorPhoto = (el) => {
38
- const src = el?.querySelector("img")?.getAttribute("src");
39
- return src ? addProtocol(src) : null;
40
- };
41
- const parseBirthday = (text) => text.replace(/nar\./g, "").trim();
42
- const parseAge = (text) => {
43
- const digits = text.replace(/[^\d]/g, "");
44
- return digits ? Number(digits) : null;
45
- };
46
- const parseBirthPlace = (text) => text.trim().replace(/<br>/g, "").trim();
47
- const getCreatorFilms = (el) => {
48
- const filmNodes = el?.querySelectorAll(".updated-box")?.[0]?.querySelectorAll("table tr") ?? [];
49
- let yearCache = null;
50
- return filmNodes.map((filmNode) => {
51
- const id = getCreatorId(filmNode.querySelector("td.name .film-title-name")?.attributes?.href);
52
- const title = filmNode.querySelector(".name")?.text?.trim();
53
- const yearText = filmNode.querySelector(".year")?.text?.trim();
54
- const year = yearText ? +yearText : null;
55
- const colorRating = getCreatorColorRating(filmNode.querySelector(".name .icon"));
56
- if (typeof year === "number" && !isNaN(year)) yearCache = +year;
57
- const finalYear = year ?? yearCache;
58
- if (id != null && title && finalYear != null) return {
59
- id,
60
- title,
61
- year: finalYear,
62
- colorRating
63
- };
64
- return null;
65
- }).filter(Boolean);
66
- };
67
-
68
- //#endregion
69
- export { getCreatorBio, getCreatorBirthdayInfo, getCreatorFilms, getCreatorName, getCreatorPhoto };
@@ -1,101 +0,0 @@
1
- #!/usr/bin/env node
2
- //#region src/helpers/global.helper.ts
3
- const LANG_PREFIX_REGEX = /^[a-z]{2,3}$/;
4
- const ISO8601_DURATION_REGEX = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
5
- const parseIdFromUrl = (url) => {
6
- if (!url) return null;
7
- const parts = url.split("/");
8
- const idParts = parts.filter((p) => /^\d+-/.test(p));
9
- if (idParts.length > 0) return +idParts[idParts.length - 1].split("-")[0] || null;
10
- return +parts[LANG_PREFIX_REGEX.test(parts[1]) ? 3 : 2]?.split("-")[0] || null;
11
- };
12
- const parseLastIdFromUrl = (url) => {
13
- if (url) return +(url?.split("/")[3])?.split("-")[0] || null;
14
- else return null;
15
- };
16
- const PAGE_COLORS = {
17
- "page-lightgrey": "unknown",
18
- "page-red": "good",
19
- "page-blue": "average",
20
- "page-grey": "bad"
21
- };
22
- const getColor = (cls) => {
23
- return PAGE_COLORS[cls] || "unknown";
24
- };
25
- const RATING_COLORS = {
26
- lightgrey: "unknown",
27
- red: "good",
28
- blue: "average",
29
- grey: "bad"
30
- };
31
- const parseColor = (quality) => {
32
- return RATING_COLORS[quality] || "unknown";
33
- };
34
- const FILM_TYPES = {
35
- "TV film": "tv-film",
36
- pořad: "tv-show",
37
- seriál: "series",
38
- "divadelní záznam": "theatrical",
39
- koncert: "concert",
40
- série: "season",
41
- "studentský film": "student-film",
42
- "amatérský film": "amateur-film",
43
- "hudební videoklip": "music-video",
44
- epizoda: "episode",
45
- "video kompilace": "video-compilation",
46
- film: "film"
47
- };
48
- const parseFilmType = (type) => {
49
- return FILM_TYPES[type] || "film";
50
- };
51
- const addProtocol = (url) => {
52
- return url.startsWith("//") ? "https:" + url : url;
53
- };
54
- const getDuration = (matches) => {
55
- return {
56
- sign: matches[1] === void 0 ? "+" : "-",
57
- years: matches[2] === void 0 ? 0 : matches[2],
58
- months: matches[3] === void 0 ? 0 : matches[3],
59
- weeks: matches[4] === void 0 ? 0 : matches[4],
60
- days: matches[5] === void 0 ? 0 : matches[5],
61
- hours: matches[6] === void 0 ? 0 : matches[6],
62
- minutes: matches[7] === void 0 ? 0 : matches[7],
63
- seconds: matches[8] === void 0 ? 0 : matches[8]
64
- };
65
- };
66
- const parseISO8601Duration = (iso) => {
67
- const duration = getDuration(iso.match(ISO8601_DURATION_REGEX));
68
- return +duration.hours * 60 + +duration.minutes;
69
- };
70
- /**
71
- * Parses a date string into a standardized YYYY-MM-DD format.
72
- * Supports:
73
- * - D.M.YYYY
74
- * - DD.MM.YYYY
75
- * - D. M. YYYY
76
- * - MM/DD/YYYY
77
- * - YYYY
78
- */
79
- const parseDate = (date) => {
80
- if (!date) return null;
81
- const cleanDate = date.trim();
82
- const dateMatch = cleanDate.match(/^(\d{1,2})\.\s*(\d{1,2})\.\s*(\d{4})$/);
83
- if (dateMatch) {
84
- const day = dateMatch[1].padStart(2, "0");
85
- const month = dateMatch[2].padStart(2, "0");
86
- return `${dateMatch[3]}-${month}-${day}`;
87
- }
88
- const slashMatch = cleanDate.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
89
- if (slashMatch) {
90
- const month = slashMatch[1].padStart(2, "0");
91
- const day = slashMatch[2].padStart(2, "0");
92
- return `${slashMatch[3]}-${month}-${day}`;
93
- }
94
- const yearMatch = cleanDate.match(/^(\d{4})$/);
95
- if (yearMatch) return `${yearMatch[1]}-01-01`;
96
- return null;
97
- };
98
- const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
99
-
100
- //#endregion
101
- export { addProtocol, getColor, parseColor, parseDate, parseFilmType, parseISO8601Duration, parseIdFromUrl, parseLastIdFromUrl, sleep };