node-csfd-api 4.3.4 → 5.0.0-next-2
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/README.md +11 -11
- package/bin/export-ratings.mjs +62 -58
- package/bin/mcp-server.mjs +274 -182
- package/bin/server.mjs +239 -281
- package/cli.mjs +74 -78
- package/dto/creator.d.mts +1 -1
- package/dto/creator.d.ts +1 -1
- package/dto/global.d.mts +1 -1
- package/dto/global.d.ts +1 -1
- package/dto/movie.d.mts +22 -1
- package/dto/movie.d.ts +22 -1
- package/dto/user-reviews.d.mts +1 -1
- package/dto/user-reviews.d.ts +1 -1
- package/helpers/creator.helper.js +2 -2
- package/helpers/creator.helper.js.map +1 -1
- package/helpers/creator.helper.mjs +3 -3
- package/helpers/creator.helper.mjs.map +1 -1
- package/helpers/global.helper.js +52 -0
- package/helpers/global.helper.js.map +1 -1
- package/helpers/global.helper.mjs +50 -1
- package/helpers/global.helper.mjs.map +1 -1
- package/helpers/movie.helper.js +87 -3
- package/helpers/movie.helper.js.map +1 -1
- package/helpers/movie.helper.mjs +84 -5
- package/helpers/movie.helper.mjs.map +1 -1
- package/helpers/search.helper.js +2 -1
- package/helpers/search.helper.js.map +1 -1
- package/helpers/search.helper.mjs +3 -2
- package/helpers/search.helper.mjs.map +1 -1
- package/helpers/user-ratings.helper.js +1 -1
- 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 +2 -2
- 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.d.mts +2 -2
- package/index.d.ts +2 -2
- package/package.json +2 -4
- package/package.mjs +8 -0
- package/services/movie.service.js +15 -3
- package/services/movie.service.js.map +1 -1
- package/services/movie.service.mjs +16 -4
- package/services/movie.service.mjs.map +1 -1
package/cli.mjs
CHANGED
|
@@ -1,87 +1,80 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
//#region src/cli.ts
|
|
6
|
+
/**
|
|
7
|
+
* Main CLI entry point for node-csfd-api.
|
|
8
|
+
*/
|
|
4
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
5
10
|
const __dirname = path.dirname(__filename);
|
|
6
11
|
async function main() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
console.error(`Unknown export target: ${args[1]}`);
|
|
71
|
-
printUsage();
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
break;
|
|
75
|
-
case "help":
|
|
76
|
-
case "--help":
|
|
77
|
-
case "-h":
|
|
78
|
-
default:
|
|
79
|
-
printUsage();
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
switch (args[0]) {
|
|
14
|
+
case "server":
|
|
15
|
+
case "api":
|
|
16
|
+
try {
|
|
17
|
+
await import(path.join(__dirname, "bin/server.mjs"));
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error("Failed to start server:", error);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
break;
|
|
23
|
+
case "mcp":
|
|
24
|
+
try {
|
|
25
|
+
await import(path.join(__dirname, "bin/mcp-server.mjs"));
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("Failed to start MCP server:", error);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
break;
|
|
31
|
+
case "export":
|
|
32
|
+
if (args[1] === "ratings") {
|
|
33
|
+
const userIdRaw = args[2];
|
|
34
|
+
const userId = Number(userIdRaw);
|
|
35
|
+
if (!userIdRaw || isNaN(userId)) {
|
|
36
|
+
console.error("Error: Please provide a valid numeric User ID.");
|
|
37
|
+
console.log("Usage: npx node-csfd-api export ratings <userId> [options]");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const isLetterboxd = args.includes("--letterboxd");
|
|
41
|
+
const isJson = args.includes("--json");
|
|
42
|
+
const isCsv = args.includes("--csv");
|
|
43
|
+
let format = "csv";
|
|
44
|
+
if (isLetterboxd) format = "letterboxd";
|
|
45
|
+
else if (isJson) format = "json";
|
|
46
|
+
else if (isCsv) format = "csv";
|
|
47
|
+
try {
|
|
48
|
+
const { runRatingsExport } = await import(path.join(__dirname, "bin/export-ratings.mjs"));
|
|
49
|
+
await runRatingsExport(userId, {
|
|
50
|
+
format,
|
|
51
|
+
userRatingsOptions: {
|
|
52
|
+
includesOnly: isLetterboxd ? ["film"] : void 0,
|
|
53
|
+
allPages: true,
|
|
54
|
+
allPagesDelay: 1e3
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error("Failed to run export:", error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
} else if (args[1] === "letterboxd") {
|
|
62
|
+
console.warn("Deprecation Warning: \"export letterboxd\" is deprecated. Please use \"export ratings <id> --letterboxd\" instead.");
|
|
63
|
+
console.log("Usage: npx node-csfd-api export ratings <userId> --letterboxd");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
} else {
|
|
66
|
+
console.error(`Unknown export target: ${args[1]}`);
|
|
67
|
+
printUsage();
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
printUsage();
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
82
75
|
}
|
|
83
76
|
function printUsage() {
|
|
84
|
-
|
|
77
|
+
console.log(`
|
|
85
78
|
Usage: npx node-csfd-api <command> [options]
|
|
86
79
|
|
|
87
80
|
Commands:
|
|
@@ -96,6 +89,9 @@ Commands:
|
|
|
96
89
|
`);
|
|
97
90
|
}
|
|
98
91
|
main().catch((error) => {
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
console.error("Fatal error:", error);
|
|
93
|
+
process.exit(1);
|
|
101
94
|
});
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
export { };
|
package/dto/creator.d.mts
CHANGED
package/dto/creator.d.ts
CHANGED
package/dto/global.d.mts
CHANGED
|
@@ -20,7 +20,7 @@ interface CSFDScreening {
|
|
|
20
20
|
}
|
|
21
21
|
type CSFDColorRating = 'bad' | 'average' | 'good' | 'unknown';
|
|
22
22
|
type CSFDStars = 0 | 1 | 2 | 3 | 4 | 5;
|
|
23
|
-
type CSFDFilmTypes = 'film' | '
|
|
23
|
+
type CSFDFilmTypes = 'film' | 'tv-film' | 'tv-show' | 'series' | 'theatrical' | 'concert' | 'season' | 'student-film' | 'amateur-film' | 'music-video' | 'episode' | 'video-compilation';
|
|
24
24
|
//#endregion
|
|
25
25
|
export { CSFDColorRating, CSFDFilmTypes, CSFDScreening, CSFDStars };
|
|
26
26
|
//# sourceMappingURL=global.d.mts.map
|
package/dto/global.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ interface CSFDScreening {
|
|
|
20
20
|
}
|
|
21
21
|
type CSFDColorRating = 'bad' | 'average' | 'good' | 'unknown';
|
|
22
22
|
type CSFDStars = 0 | 1 | 2 | 3 | 4 | 5;
|
|
23
|
-
type CSFDFilmTypes = 'film' | '
|
|
23
|
+
type CSFDFilmTypes = 'film' | 'tv-film' | 'tv-show' | 'series' | 'theatrical' | 'concert' | 'season' | 'student-film' | 'amateur-film' | 'music-video' | 'episode' | 'video-compilation';
|
|
24
24
|
//#endregion
|
|
25
25
|
export { CSFDColorRating, CSFDFilmTypes, CSFDScreening, CSFDStars };
|
|
26
26
|
//# sourceMappingURL=global.d.ts.map
|
package/dto/movie.d.mts
CHANGED
|
@@ -18,6 +18,21 @@ interface CSFDMovie extends CSFDScreening {
|
|
|
18
18
|
premieres: CSFDPremiere[];
|
|
19
19
|
related: CSFDMovieListItem[];
|
|
20
20
|
similar: CSFDMovieListItem[];
|
|
21
|
+
seasons: CSFDSeriesChild[] | null;
|
|
22
|
+
episodes: CSFDSeriesChild[] | null;
|
|
23
|
+
parent: CSFDParent | null;
|
|
24
|
+
episodeCode: string | null;
|
|
25
|
+
seasonName: string | null;
|
|
26
|
+
}
|
|
27
|
+
interface CSFDParent {
|
|
28
|
+
season: {
|
|
29
|
+
id: number;
|
|
30
|
+
title: string;
|
|
31
|
+
} | null;
|
|
32
|
+
series: {
|
|
33
|
+
id: number;
|
|
34
|
+
title: string;
|
|
35
|
+
} | null;
|
|
21
36
|
}
|
|
22
37
|
interface MovieJsonLd {
|
|
23
38
|
dateCreated?: string;
|
|
@@ -73,6 +88,12 @@ interface CSFDPremiere {
|
|
|
73
88
|
company: string;
|
|
74
89
|
}
|
|
75
90
|
type CSFDBoxContent = 'Související' | 'Podobné';
|
|
91
|
+
interface CSFDSeriesChild {
|
|
92
|
+
id: number;
|
|
93
|
+
title: string;
|
|
94
|
+
url: string;
|
|
95
|
+
info: string | null;
|
|
96
|
+
}
|
|
76
97
|
//#endregion
|
|
77
|
-
export { CSFDBoxContent, CSFDCreatorGroups, CSFDCreatorGroupsEnglish, CSFDCreatorGroupsSlovak, CSFDCreators, CSFDGenres, CSFDMovie, CSFDMovieCreator, CSFDMovieListItem, CSFDPremiere, CSFDTitlesOther, CSFDVod, CSFDVodService, MovieJsonLd };
|
|
98
|
+
export { CSFDBoxContent, CSFDCreatorGroups, CSFDCreatorGroupsEnglish, CSFDCreatorGroupsSlovak, CSFDCreators, CSFDGenres, CSFDMovie, CSFDMovieCreator, CSFDMovieListItem, CSFDParent, CSFDPremiere, CSFDSeriesChild, CSFDTitlesOther, CSFDVod, CSFDVodService, MovieJsonLd };
|
|
78
99
|
//# sourceMappingURL=movie.d.mts.map
|
package/dto/movie.d.ts
CHANGED
|
@@ -18,6 +18,21 @@ interface CSFDMovie extends CSFDScreening {
|
|
|
18
18
|
premieres: CSFDPremiere[];
|
|
19
19
|
related: CSFDMovieListItem[];
|
|
20
20
|
similar: CSFDMovieListItem[];
|
|
21
|
+
seasons: CSFDSeriesChild[] | null;
|
|
22
|
+
episodes: CSFDSeriesChild[] | null;
|
|
23
|
+
parent: CSFDParent | null;
|
|
24
|
+
episodeCode: string | null;
|
|
25
|
+
seasonName: string | null;
|
|
26
|
+
}
|
|
27
|
+
interface CSFDParent {
|
|
28
|
+
season: {
|
|
29
|
+
id: number;
|
|
30
|
+
title: string;
|
|
31
|
+
} | null;
|
|
32
|
+
series: {
|
|
33
|
+
id: number;
|
|
34
|
+
title: string;
|
|
35
|
+
} | null;
|
|
21
36
|
}
|
|
22
37
|
interface MovieJsonLd {
|
|
23
38
|
dateCreated?: string;
|
|
@@ -73,6 +88,12 @@ interface CSFDPremiere {
|
|
|
73
88
|
company: string;
|
|
74
89
|
}
|
|
75
90
|
type CSFDBoxContent = 'Související' | 'Podobné';
|
|
91
|
+
interface CSFDSeriesChild {
|
|
92
|
+
id: number;
|
|
93
|
+
title: string;
|
|
94
|
+
url: string;
|
|
95
|
+
info: string | null;
|
|
96
|
+
}
|
|
76
97
|
//#endregion
|
|
77
|
-
export { CSFDBoxContent, CSFDCreatorGroups, CSFDCreatorGroupsEnglish, CSFDCreatorGroupsSlovak, CSFDCreators, CSFDGenres, CSFDMovie, CSFDMovieCreator, CSFDMovieListItem, CSFDPremiere, CSFDTitlesOther, CSFDVod, CSFDVodService, MovieJsonLd };
|
|
98
|
+
export { CSFDBoxContent, CSFDCreatorGroups, CSFDCreatorGroupsEnglish, CSFDCreatorGroupsSlovak, CSFDCreators, CSFDGenres, CSFDMovie, CSFDMovieCreator, CSFDMovieListItem, CSFDParent, CSFDPremiere, CSFDSeriesChild, CSFDTitlesOther, CSFDVod, CSFDVodService, MovieJsonLd };
|
|
78
99
|
//# sourceMappingURL=movie.d.ts.map
|
package/dto/user-reviews.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { CSFDFilmTypes, CSFDScreening, CSFDStars } from "./global.mjs";
|
|
|
3
3
|
//#region src/dto/user-reviews.d.ts
|
|
4
4
|
interface CSFDUserReviews extends CSFDScreening {
|
|
5
5
|
userRating: CSFDStars;
|
|
6
|
-
userDate: string;
|
|
6
|
+
userDate: string | null;
|
|
7
7
|
text: string;
|
|
8
8
|
poster: string;
|
|
9
9
|
}
|
package/dto/user-reviews.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { CSFDFilmTypes, CSFDScreening, CSFDStars } from "./global.js";
|
|
|
3
3
|
//#region src/dto/user-reviews.d.ts
|
|
4
4
|
interface CSFDUserReviews extends CSFDScreening {
|
|
5
5
|
userRating: CSFDStars;
|
|
6
|
-
userDate: string;
|
|
6
|
+
userDate: string | null;
|
|
7
7
|
text: string;
|
|
8
8
|
poster: string;
|
|
9
9
|
}
|
|
@@ -17,10 +17,10 @@ const getCreatorBirthdayInfo = (el) => {
|
|
|
17
17
|
const text = infoBlock?.innerHTML.trim();
|
|
18
18
|
const birthPlaceRow = infoBlock?.querySelector(".info-place")?.innerText.trim();
|
|
19
19
|
const ageRow = infoBlock?.querySelector(".info")?.innerText.trim();
|
|
20
|
-
let birthday =
|
|
20
|
+
let birthday = null;
|
|
21
21
|
if (text) {
|
|
22
22
|
const birthdayRow = text.split("\n").find((x) => x.includes("nar."));
|
|
23
|
-
birthday = birthdayRow ? parseBirthday(birthdayRow) :
|
|
23
|
+
birthday = birthdayRow ? require_global_helper.parseDate(parseBirthday(birthdayRow)) : null;
|
|
24
24
|
}
|
|
25
25
|
const age = ageRow ? +parseAge(ageRow) : null;
|
|
26
26
|
const birthPlace = birthPlaceRow ? parseBirthPlace(birthPlaceRow) : "";
|
|
@@ -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('.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 =
|
|
1
|
+
{"version":3,"file":"creator.helper.js","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,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,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,gCAAU,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,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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addProtocol, parseColor, parseIdFromUrl } from "./global.helper.mjs";
|
|
1
|
+
import { addProtocol, parseColor, parseDate, parseIdFromUrl } from "./global.helper.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/helpers/creator.helper.ts
|
|
4
4
|
const getCreatorColorRating = (el) => {
|
|
@@ -17,10 +17,10 @@ const getCreatorBirthdayInfo = (el) => {
|
|
|
17
17
|
const text = infoBlock?.innerHTML.trim();
|
|
18
18
|
const birthPlaceRow = infoBlock?.querySelector(".info-place")?.innerText.trim();
|
|
19
19
|
const ageRow = infoBlock?.querySelector(".info")?.innerText.trim();
|
|
20
|
-
let birthday =
|
|
20
|
+
let birthday = null;
|
|
21
21
|
if (text) {
|
|
22
22
|
const birthdayRow = text.split("\n").find((x) => x.includes("nar."));
|
|
23
|
-
birthday = birthdayRow ? parseBirthday(birthdayRow) :
|
|
23
|
+
birthday = birthdayRow ? parseDate(parseBirthday(birthdayRow)) : null;
|
|
24
24
|
}
|
|
25
25
|
const age = ageRow ? +parseAge(ageRow) : null;
|
|
26
26
|
const birthPlace = birthPlaceRow ? parseBirthPlace(birthPlaceRow) : "";
|
|
@@ -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('.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 =
|
|
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, 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"}
|
package/helpers/global.helper.js
CHANGED
|
@@ -7,6 +7,10 @@ const parseIdFromUrl = (url) => {
|
|
|
7
7
|
const parts = url.split("/");
|
|
8
8
|
return +parts[LANG_PREFIX_REGEX.test(parts[1]) ? 3 : 2]?.split("-")[0] || null;
|
|
9
9
|
};
|
|
10
|
+
const parseLastIdFromUrl = (url) => {
|
|
11
|
+
if (url) return +(url?.split("/")[3])?.split("-")[0] || null;
|
|
12
|
+
else return null;
|
|
13
|
+
};
|
|
10
14
|
const getColor = (cls) => {
|
|
11
15
|
switch (cls) {
|
|
12
16
|
case "page-lightgrey": return "unknown";
|
|
@@ -25,6 +29,23 @@ const parseColor = (quality) => {
|
|
|
25
29
|
default: return "unknown";
|
|
26
30
|
}
|
|
27
31
|
};
|
|
32
|
+
const filmTypeMap = {
|
|
33
|
+
"TV film": "tv-film",
|
|
34
|
+
pořad: "tv-show",
|
|
35
|
+
seriál: "series",
|
|
36
|
+
"divadelní záznam": "theatrical",
|
|
37
|
+
koncert: "concert",
|
|
38
|
+
série: "season",
|
|
39
|
+
"studentský film": "student-film",
|
|
40
|
+
"amatérský film": "amateur-film",
|
|
41
|
+
"hudební videoklip": "music-video",
|
|
42
|
+
epizoda: "episode",
|
|
43
|
+
"video kompilace": "video-compilation",
|
|
44
|
+
film: "film"
|
|
45
|
+
};
|
|
46
|
+
const parseFilmType = (type) => {
|
|
47
|
+
return filmTypeMap[type] || "film";
|
|
48
|
+
};
|
|
28
49
|
const addProtocol = (url) => {
|
|
29
50
|
return url.startsWith("//") ? "https:" + url : url;
|
|
30
51
|
};
|
|
@@ -44,13 +65,44 @@ const parseISO8601Duration = (iso) => {
|
|
|
44
65
|
const duration = getDuration(iso.match(ISO8601_DURATION_REGEX));
|
|
45
66
|
return +duration.hours * 60 + +duration.minutes;
|
|
46
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* Parses a date string into a standardized YYYY-MM-DD format.
|
|
70
|
+
* Supports:
|
|
71
|
+
* - D.M.YYYY
|
|
72
|
+
* - DD.MM.YYYY
|
|
73
|
+
* - D. M. YYYY
|
|
74
|
+
* - MM/DD/YYYY
|
|
75
|
+
* - YYYY
|
|
76
|
+
*/
|
|
77
|
+
const parseDate = (date) => {
|
|
78
|
+
if (!date) return null;
|
|
79
|
+
const cleanDate = date.trim();
|
|
80
|
+
const dateMatch = cleanDate.match(/^(\d{1,2})\.\s*(\d{1,2})\.\s*(\d{4})$/);
|
|
81
|
+
if (dateMatch) {
|
|
82
|
+
const day = dateMatch[1].padStart(2, "0");
|
|
83
|
+
const month = dateMatch[2].padStart(2, "0");
|
|
84
|
+
return `${dateMatch[3]}-${month}-${day}`;
|
|
85
|
+
}
|
|
86
|
+
const slashMatch = cleanDate.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
|
|
87
|
+
if (slashMatch) {
|
|
88
|
+
const month = slashMatch[1].padStart(2, "0");
|
|
89
|
+
const day = slashMatch[2].padStart(2, "0");
|
|
90
|
+
return `${slashMatch[3]}-${month}-${day}`;
|
|
91
|
+
}
|
|
92
|
+
const yearMatch = cleanDate.match(/^(\d{4})$/);
|
|
93
|
+
if (yearMatch) return `${yearMatch[1]}-01-01`;
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
47
96
|
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
48
97
|
|
|
49
98
|
//#endregion
|
|
50
99
|
exports.addProtocol = addProtocol;
|
|
51
100
|
exports.getColor = getColor;
|
|
52
101
|
exports.parseColor = parseColor;
|
|
102
|
+
exports.parseDate = parseDate;
|
|
103
|
+
exports.parseFilmType = parseFilmType;
|
|
53
104
|
exports.parseISO8601Duration = parseISO8601Duration;
|
|
54
105
|
exports.parseIdFromUrl = parseIdFromUrl;
|
|
106
|
+
exports.parseLastIdFromUrl = parseLastIdFromUrl;
|
|
55
107
|
exports.sleep = sleep;
|
|
56
108
|
//# sourceMappingURL=global.helper.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global.helper.js","names":[],"sources":["../../src/helpers/global.helper.ts"],"sourcesContent":["import { CSFDColorRating } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\n\nconst LANG_PREFIX_REGEX = /^[a-z]{2,3}$/;\nconst ISO8601_DURATION_REGEX =\n /(-)?P(?:([.,\\d]+)Y)?(?:([.,\\d]+)M)?(?:([.,\\d]+)W)?(?:([.,\\d]+)D)?T(?:([.,\\d]+)H)?(?:([.,\\d]+)M)?(?:([.,\\d]+)S)?/;\n\nexport const parseIdFromUrl = (url: string): number => {\n if (!url) return null;\n\n const parts = url.split('/');\n // Detect language prefix like /en/ or /sk/\n const hasLangPrefix = LANG_PREFIX_REGEX.test(parts[1]);\n const idSlug = parts[hasLangPrefix ? 3 : 2];\n const id = idSlug?.split('-')[0];\n return +id || null;\n};\n\nexport const getColor = (cls: string): CSFDColorRating => {\n switch (cls) {\n case 'page-lightgrey':\n return 'unknown';\n case 'page-red':\n return 'good';\n case 'page-blue':\n return 'average';\n case 'page-grey':\n return 'bad';\n default:\n return 'unknown';\n }\n};\n\nexport const parseColor = (quality: CSFDColors): CSFDColorRating => {\n switch (quality) {\n case 'lightgrey':\n return 'unknown';\n case 'red':\n return 'good';\n case 'blue':\n return 'average';\n case 'grey':\n return 'bad';\n default:\n return 'unknown';\n }\n};\n\nexport const addProtocol = (url: string): string => {\n return url.startsWith('//') ? 'https:' + url : url;\n};\n\nexport const getDuration = (matches: RegExpMatchArray) => {\n return {\n sign: matches[1] === undefined ? '+' : '-',\n years: matches[2] === undefined ? 0 : matches[2],\n months: matches[3] === undefined ? 0 : matches[3],\n weeks: matches[4] === undefined ? 0 : matches[4],\n days: matches[5] === undefined ? 0 : matches[5],\n hours: matches[6] === undefined ? 0 : matches[6],\n minutes: matches[7] === undefined ? 0 : matches[7],\n seconds: matches[8] === undefined ? 0 : matches[8]\n };\n};\n\nexport const parseISO8601Duration = (iso: string): number => {\n const matches = iso.match(ISO8601_DURATION_REGEX);\n\n const duration = getDuration(matches);\n\n return +duration.hours * 60 + +duration.minutes;\n};\n\n// Sleep in loop\nexport const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));\n"],"mappings":";;AAGA,MAAM,oBAAoB;AAC1B,MAAM,yBACJ;AAEF,MAAa,kBAAkB,QAAwB;AACrD,KAAI,CAAC,IAAK,QAAO;CAEjB,MAAM,QAAQ,IAAI,MAAM,IAAI;AAK5B,QAAO,CAFQ,MADO,kBAAkB,KAAK,MAAM,GAAG,GACjB,IAAI,IACtB,MAAM,IAAI,CAAC,MAChB;;AAGhB,MAAa,YAAY,QAAiC;AACxD,SAAQ,KAAR;EACE,KAAK,iBACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,cAAc,YAAyC;AAClE,SAAQ,SAAR;EACE,KAAK,YACH,QAAO;EACT,KAAK,MACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,eAAe,QAAwB;AAClD,QAAO,IAAI,WAAW,KAAK,GAAG,WAAW,MAAM;;AAGjD,MAAa,eAAe,YAA8B;AACxD,QAAO;EACL,MAAM,QAAQ,OAAO,SAAY,MAAM;EACvC,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,QAAQ,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC/C,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,MAAM,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC7C,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,SAAS,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAChD,SAAS,QAAQ,OAAO,SAAY,IAAI,QAAQ;EACjD;;AAGH,MAAa,wBAAwB,QAAwB;CAG3D,MAAM,WAAW,YAFD,IAAI,MAAM,uBAAuB,CAEZ;AAErC,QAAO,CAAC,SAAS,QAAQ,KAAK,CAAC,SAAS;;
|
|
1
|
+
{"version":3,"file":"global.helper.js","names":[],"sources":["../../src/helpers/global.helper.ts"],"sourcesContent":["import { CSFDColorRating, CSFDFilmTypes } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\n\nconst LANG_PREFIX_REGEX = /^[a-z]{2,3}$/;\nconst ISO8601_DURATION_REGEX =\n /(-)?P(?:([.,\\d]+)Y)?(?:([.,\\d]+)M)?(?:([.,\\d]+)W)?(?:([.,\\d]+)D)?T(?:([.,\\d]+)H)?(?:([.,\\d]+)M)?(?:([.,\\d]+)S)?/;\n\nexport const parseIdFromUrl = (url: string): number => {\n if (!url) return null;\n\n const parts = url.split('/');\n // Detect language prefix like /en/ or /sk/\n const hasLangPrefix = LANG_PREFIX_REGEX.test(parts[1]);\n const idSlug = parts[hasLangPrefix ? 3 : 2];\n const id = idSlug?.split('-')[0];\n return +id || null;\n};\n\nexport const parseLastIdFromUrl = (url: string): number => {\n if (url) {\n const idSlug = url?.split('/')[3];\n const id = idSlug?.split('-')[0];\n return +id || null;\n } else {\n return null;\n }\n};\n\nexport const getColor = (cls: string): CSFDColorRating => {\n switch (cls) {\n case 'page-lightgrey':\n return 'unknown';\n case 'page-red':\n return 'good';\n case 'page-blue':\n return 'average';\n case 'page-grey':\n return 'bad';\n default:\n return 'unknown';\n }\n};\n\nexport const parseColor = (quality: CSFDColors): CSFDColorRating => {\n switch (quality) {\n case 'lightgrey':\n return 'unknown';\n case 'red':\n return 'good';\n case 'blue':\n return 'average';\n case 'grey':\n return 'bad';\n default:\n return 'unknown';\n }\n};\n\nconst filmTypeMap: Record<string, CSFDFilmTypes> = {\n 'TV film': 'tv-film',\n pořad: 'tv-show',\n seriál: 'series',\n 'divadelní záznam': 'theatrical',\n koncert: 'concert',\n série: 'season',\n 'studentský film': 'student-film',\n 'amatérský film': 'amateur-film',\n 'hudební videoklip': 'music-video',\n epizoda: 'episode',\n 'video kompilace': 'video-compilation',\n film: 'film'\n};\n\nexport const parseFilmType = (type: string): CSFDFilmTypes => {\n return filmTypeMap[type] || 'film';\n};\n\nexport const addProtocol = (url: string): string => {\n return url.startsWith('//') ? 'https:' + url : url;\n};\n\nexport const getDuration = (matches: RegExpMatchArray) => {\n return {\n sign: matches[1] === undefined ? '+' : '-',\n years: matches[2] === undefined ? 0 : matches[2],\n months: matches[3] === undefined ? 0 : matches[3],\n weeks: matches[4] === undefined ? 0 : matches[4],\n days: matches[5] === undefined ? 0 : matches[5],\n hours: matches[6] === undefined ? 0 : matches[6],\n minutes: matches[7] === undefined ? 0 : matches[7],\n seconds: matches[8] === undefined ? 0 : matches[8]\n };\n};\n\nexport const parseISO8601Duration = (iso: string): number => {\n const matches = iso.match(ISO8601_DURATION_REGEX);\n\n const duration = getDuration(matches);\n\n return +duration.hours * 60 + +duration.minutes;\n};\n\n/**\n * Parses a date string into a standardized YYYY-MM-DD format.\n * Supports:\n * - D.M.YYYY\n * - DD.MM.YYYY\n * - D. M. YYYY\n * - MM/DD/YYYY\n * - YYYY\n */\nexport const parseDate = (date: string): string | null => {\n if (!date) return null;\n\n // Clean the input\n const cleanDate = date.trim();\n\n // Try parsing DD.MM.YYYY or D.M.YYYY with optional spaces\n const dateMatch = cleanDate.match(/^(\\d{1,2})\\.\\s*(\\d{1,2})\\.\\s*(\\d{4})$/);\n if (dateMatch) {\n const day = dateMatch[1].padStart(2, '0');\n const month = dateMatch[2].padStart(2, '0');\n const year = dateMatch[3];\n return `${year}-${month}-${day}`;\n }\n\n // Try parsing MM/DD/YYYY\n const slashMatch = cleanDate.match(/^(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})$/);\n if (slashMatch) {\n const month = slashMatch[1].padStart(2, '0');\n const day = slashMatch[2].padStart(2, '0');\n const year = slashMatch[3];\n return `${year}-${month}-${day}`;\n }\n\n // Try parsing YYYY\n const yearMatch = cleanDate.match(/^(\\d{4})$/);\n if (yearMatch) {\n return `${yearMatch[1]}-01-01`;\n }\n\n return null;\n};\n\n// Sleep in loop\nexport const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));\n"],"mappings":";;AAGA,MAAM,oBAAoB;AAC1B,MAAM,yBACJ;AAEF,MAAa,kBAAkB,QAAwB;AACrD,KAAI,CAAC,IAAK,QAAO;CAEjB,MAAM,QAAQ,IAAI,MAAM,IAAI;AAK5B,QAAO,CAFQ,MADO,kBAAkB,KAAK,MAAM,GAAG,GACjB,IAAI,IACtB,MAAM,IAAI,CAAC,MAChB;;AAGhB,MAAa,sBAAsB,QAAwB;AACzD,KAAI,IAGF,QAAO,EAFQ,KAAK,MAAM,IAAI,CAAC,KACZ,MAAM,IAAI,CAAC,MAChB;KAEd,QAAO;;AAIX,MAAa,YAAY,QAAiC;AACxD,SAAQ,KAAR;EACE,KAAK,iBACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,cAAc,YAAyC;AAClE,SAAQ,SAAR;EACE,KAAK,YACH,QAAO;EACT,KAAK,MACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAM,cAA6C;CACjD,WAAW;CACX,OAAO;CACP,QAAQ;CACR,oBAAoB;CACpB,SAAS;CACT,OAAO;CACP,mBAAmB;CACnB,kBAAkB;CAClB,qBAAqB;CACrB,SAAS;CACT,mBAAmB;CACnB,MAAM;CACP;AAED,MAAa,iBAAiB,SAAgC;AAC5D,QAAO,YAAY,SAAS;;AAG9B,MAAa,eAAe,QAAwB;AAClD,QAAO,IAAI,WAAW,KAAK,GAAG,WAAW,MAAM;;AAGjD,MAAa,eAAe,YAA8B;AACxD,QAAO;EACL,MAAM,QAAQ,OAAO,SAAY,MAAM;EACvC,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,QAAQ,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC/C,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,MAAM,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC7C,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,SAAS,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAChD,SAAS,QAAQ,OAAO,SAAY,IAAI,QAAQ;EACjD;;AAGH,MAAa,wBAAwB,QAAwB;CAG3D,MAAM,WAAW,YAFD,IAAI,MAAM,uBAAuB,CAEZ;AAErC,QAAO,CAAC,SAAS,QAAQ,KAAK,CAAC,SAAS;;;;;;;;;;;AAY1C,MAAa,aAAa,SAAgC;AACxD,KAAI,CAAC,KAAM,QAAO;CAGlB,MAAM,YAAY,KAAK,MAAM;CAG7B,MAAM,YAAY,UAAU,MAAM,wCAAwC;AAC1E,KAAI,WAAW;EACb,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,IAAI;EACzC,MAAM,QAAQ,UAAU,GAAG,SAAS,GAAG,IAAI;AAE3C,SAAO,GADM,UAAU,GACR,GAAG,MAAM,GAAG;;CAI7B,MAAM,aAAa,UAAU,MAAM,kCAAkC;AACrE,KAAI,YAAY;EACd,MAAM,QAAQ,WAAW,GAAG,SAAS,GAAG,IAAI;EAC5C,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,IAAI;AAE1C,SAAO,GADM,WAAW,GACT,GAAG,MAAM,GAAG;;CAI7B,MAAM,YAAY,UAAU,MAAM,YAAY;AAC9C,KAAI,UACF,QAAO,GAAG,UAAU,GAAG;AAGzB,QAAO;;AAIT,MAAa,SAAS,OAAe,IAAI,SAAS,QAAQ,WAAW,KAAK,GAAG,CAAC"}
|
|
@@ -6,6 +6,10 @@ const parseIdFromUrl = (url) => {
|
|
|
6
6
|
const parts = url.split("/");
|
|
7
7
|
return +parts[LANG_PREFIX_REGEX.test(parts[1]) ? 3 : 2]?.split("-")[0] || null;
|
|
8
8
|
};
|
|
9
|
+
const parseLastIdFromUrl = (url) => {
|
|
10
|
+
if (url) return +(url?.split("/")[3])?.split("-")[0] || null;
|
|
11
|
+
else return null;
|
|
12
|
+
};
|
|
9
13
|
const getColor = (cls) => {
|
|
10
14
|
switch (cls) {
|
|
11
15
|
case "page-lightgrey": return "unknown";
|
|
@@ -24,6 +28,23 @@ const parseColor = (quality) => {
|
|
|
24
28
|
default: return "unknown";
|
|
25
29
|
}
|
|
26
30
|
};
|
|
31
|
+
const filmTypeMap = {
|
|
32
|
+
"TV film": "tv-film",
|
|
33
|
+
pořad: "tv-show",
|
|
34
|
+
seriál: "series",
|
|
35
|
+
"divadelní záznam": "theatrical",
|
|
36
|
+
koncert: "concert",
|
|
37
|
+
série: "season",
|
|
38
|
+
"studentský film": "student-film",
|
|
39
|
+
"amatérský film": "amateur-film",
|
|
40
|
+
"hudební videoklip": "music-video",
|
|
41
|
+
epizoda: "episode",
|
|
42
|
+
"video kompilace": "video-compilation",
|
|
43
|
+
film: "film"
|
|
44
|
+
};
|
|
45
|
+
const parseFilmType = (type) => {
|
|
46
|
+
return filmTypeMap[type] || "film";
|
|
47
|
+
};
|
|
27
48
|
const addProtocol = (url) => {
|
|
28
49
|
return url.startsWith("//") ? "https:" + url : url;
|
|
29
50
|
};
|
|
@@ -43,8 +64,36 @@ const parseISO8601Duration = (iso) => {
|
|
|
43
64
|
const duration = getDuration(iso.match(ISO8601_DURATION_REGEX));
|
|
44
65
|
return +duration.hours * 60 + +duration.minutes;
|
|
45
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Parses a date string into a standardized YYYY-MM-DD format.
|
|
69
|
+
* Supports:
|
|
70
|
+
* - D.M.YYYY
|
|
71
|
+
* - DD.MM.YYYY
|
|
72
|
+
* - D. M. YYYY
|
|
73
|
+
* - MM/DD/YYYY
|
|
74
|
+
* - YYYY
|
|
75
|
+
*/
|
|
76
|
+
const parseDate = (date) => {
|
|
77
|
+
if (!date) return null;
|
|
78
|
+
const cleanDate = date.trim();
|
|
79
|
+
const dateMatch = cleanDate.match(/^(\d{1,2})\.\s*(\d{1,2})\.\s*(\d{4})$/);
|
|
80
|
+
if (dateMatch) {
|
|
81
|
+
const day = dateMatch[1].padStart(2, "0");
|
|
82
|
+
const month = dateMatch[2].padStart(2, "0");
|
|
83
|
+
return `${dateMatch[3]}-${month}-${day}`;
|
|
84
|
+
}
|
|
85
|
+
const slashMatch = cleanDate.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
|
|
86
|
+
if (slashMatch) {
|
|
87
|
+
const month = slashMatch[1].padStart(2, "0");
|
|
88
|
+
const day = slashMatch[2].padStart(2, "0");
|
|
89
|
+
return `${slashMatch[3]}-${month}-${day}`;
|
|
90
|
+
}
|
|
91
|
+
const yearMatch = cleanDate.match(/^(\d{4})$/);
|
|
92
|
+
if (yearMatch) return `${yearMatch[1]}-01-01`;
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
46
95
|
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
47
96
|
|
|
48
97
|
//#endregion
|
|
49
|
-
export { addProtocol, getColor, parseColor, parseISO8601Duration, parseIdFromUrl, sleep };
|
|
98
|
+
export { addProtocol, getColor, parseColor, parseDate, parseFilmType, parseISO8601Duration, parseIdFromUrl, parseLastIdFromUrl, sleep };
|
|
50
99
|
//# sourceMappingURL=global.helper.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global.helper.mjs","names":[],"sources":["../../src/helpers/global.helper.ts"],"sourcesContent":["import { CSFDColorRating } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\n\nconst LANG_PREFIX_REGEX = /^[a-z]{2,3}$/;\nconst ISO8601_DURATION_REGEX =\n /(-)?P(?:([.,\\d]+)Y)?(?:([.,\\d]+)M)?(?:([.,\\d]+)W)?(?:([.,\\d]+)D)?T(?:([.,\\d]+)H)?(?:([.,\\d]+)M)?(?:([.,\\d]+)S)?/;\n\nexport const parseIdFromUrl = (url: string): number => {\n if (!url) return null;\n\n const parts = url.split('/');\n // Detect language prefix like /en/ or /sk/\n const hasLangPrefix = LANG_PREFIX_REGEX.test(parts[1]);\n const idSlug = parts[hasLangPrefix ? 3 : 2];\n const id = idSlug?.split('-')[0];\n return +id || null;\n};\n\nexport const getColor = (cls: string): CSFDColorRating => {\n switch (cls) {\n case 'page-lightgrey':\n return 'unknown';\n case 'page-red':\n return 'good';\n case 'page-blue':\n return 'average';\n case 'page-grey':\n return 'bad';\n default:\n return 'unknown';\n }\n};\n\nexport const parseColor = (quality: CSFDColors): CSFDColorRating => {\n switch (quality) {\n case 'lightgrey':\n return 'unknown';\n case 'red':\n return 'good';\n case 'blue':\n return 'average';\n case 'grey':\n return 'bad';\n default:\n return 'unknown';\n }\n};\n\nexport const addProtocol = (url: string): string => {\n return url.startsWith('//') ? 'https:' + url : url;\n};\n\nexport const getDuration = (matches: RegExpMatchArray) => {\n return {\n sign: matches[1] === undefined ? '+' : '-',\n years: matches[2] === undefined ? 0 : matches[2],\n months: matches[3] === undefined ? 0 : matches[3],\n weeks: matches[4] === undefined ? 0 : matches[4],\n days: matches[5] === undefined ? 0 : matches[5],\n hours: matches[6] === undefined ? 0 : matches[6],\n minutes: matches[7] === undefined ? 0 : matches[7],\n seconds: matches[8] === undefined ? 0 : matches[8]\n };\n};\n\nexport const parseISO8601Duration = (iso: string): number => {\n const matches = iso.match(ISO8601_DURATION_REGEX);\n\n const duration = getDuration(matches);\n\n return +duration.hours * 60 + +duration.minutes;\n};\n\n// Sleep in loop\nexport const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));\n"],"mappings":";AAGA,MAAM,oBAAoB;AAC1B,MAAM,yBACJ;AAEF,MAAa,kBAAkB,QAAwB;AACrD,KAAI,CAAC,IAAK,QAAO;CAEjB,MAAM,QAAQ,IAAI,MAAM,IAAI;AAK5B,QAAO,CAFQ,MADO,kBAAkB,KAAK,MAAM,GAAG,GACjB,IAAI,IACtB,MAAM,IAAI,CAAC,MAChB;;AAGhB,MAAa,YAAY,QAAiC;AACxD,SAAQ,KAAR;EACE,KAAK,iBACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,cAAc,YAAyC;AAClE,SAAQ,SAAR;EACE,KAAK,YACH,QAAO;EACT,KAAK,MACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,eAAe,QAAwB;AAClD,QAAO,IAAI,WAAW,KAAK,GAAG,WAAW,MAAM;;AAGjD,MAAa,eAAe,YAA8B;AACxD,QAAO;EACL,MAAM,QAAQ,OAAO,SAAY,MAAM;EACvC,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,QAAQ,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC/C,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,MAAM,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC7C,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,SAAS,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAChD,SAAS,QAAQ,OAAO,SAAY,IAAI,QAAQ;EACjD;;AAGH,MAAa,wBAAwB,QAAwB;CAG3D,MAAM,WAAW,YAFD,IAAI,MAAM,uBAAuB,CAEZ;AAErC,QAAO,CAAC,SAAS,QAAQ,KAAK,CAAC,SAAS;;
|
|
1
|
+
{"version":3,"file":"global.helper.mjs","names":[],"sources":["../../src/helpers/global.helper.ts"],"sourcesContent":["import { CSFDColorRating, CSFDFilmTypes } from '../dto/global';\nimport { CSFDColors } from '../dto/user-ratings';\n\nconst LANG_PREFIX_REGEX = /^[a-z]{2,3}$/;\nconst ISO8601_DURATION_REGEX =\n /(-)?P(?:([.,\\d]+)Y)?(?:([.,\\d]+)M)?(?:([.,\\d]+)W)?(?:([.,\\d]+)D)?T(?:([.,\\d]+)H)?(?:([.,\\d]+)M)?(?:([.,\\d]+)S)?/;\n\nexport const parseIdFromUrl = (url: string): number => {\n if (!url) return null;\n\n const parts = url.split('/');\n // Detect language prefix like /en/ or /sk/\n const hasLangPrefix = LANG_PREFIX_REGEX.test(parts[1]);\n const idSlug = parts[hasLangPrefix ? 3 : 2];\n const id = idSlug?.split('-')[0];\n return +id || null;\n};\n\nexport const parseLastIdFromUrl = (url: string): number => {\n if (url) {\n const idSlug = url?.split('/')[3];\n const id = idSlug?.split('-')[0];\n return +id || null;\n } else {\n return null;\n }\n};\n\nexport const getColor = (cls: string): CSFDColorRating => {\n switch (cls) {\n case 'page-lightgrey':\n return 'unknown';\n case 'page-red':\n return 'good';\n case 'page-blue':\n return 'average';\n case 'page-grey':\n return 'bad';\n default:\n return 'unknown';\n }\n};\n\nexport const parseColor = (quality: CSFDColors): CSFDColorRating => {\n switch (quality) {\n case 'lightgrey':\n return 'unknown';\n case 'red':\n return 'good';\n case 'blue':\n return 'average';\n case 'grey':\n return 'bad';\n default:\n return 'unknown';\n }\n};\n\nconst filmTypeMap: Record<string, CSFDFilmTypes> = {\n 'TV film': 'tv-film',\n pořad: 'tv-show',\n seriál: 'series',\n 'divadelní záznam': 'theatrical',\n koncert: 'concert',\n série: 'season',\n 'studentský film': 'student-film',\n 'amatérský film': 'amateur-film',\n 'hudební videoklip': 'music-video',\n epizoda: 'episode',\n 'video kompilace': 'video-compilation',\n film: 'film'\n};\n\nexport const parseFilmType = (type: string): CSFDFilmTypes => {\n return filmTypeMap[type] || 'film';\n};\n\nexport const addProtocol = (url: string): string => {\n return url.startsWith('//') ? 'https:' + url : url;\n};\n\nexport const getDuration = (matches: RegExpMatchArray) => {\n return {\n sign: matches[1] === undefined ? '+' : '-',\n years: matches[2] === undefined ? 0 : matches[2],\n months: matches[3] === undefined ? 0 : matches[3],\n weeks: matches[4] === undefined ? 0 : matches[4],\n days: matches[5] === undefined ? 0 : matches[5],\n hours: matches[6] === undefined ? 0 : matches[6],\n minutes: matches[7] === undefined ? 0 : matches[7],\n seconds: matches[8] === undefined ? 0 : matches[8]\n };\n};\n\nexport const parseISO8601Duration = (iso: string): number => {\n const matches = iso.match(ISO8601_DURATION_REGEX);\n\n const duration = getDuration(matches);\n\n return +duration.hours * 60 + +duration.minutes;\n};\n\n/**\n * Parses a date string into a standardized YYYY-MM-DD format.\n * Supports:\n * - D.M.YYYY\n * - DD.MM.YYYY\n * - D. M. YYYY\n * - MM/DD/YYYY\n * - YYYY\n */\nexport const parseDate = (date: string): string | null => {\n if (!date) return null;\n\n // Clean the input\n const cleanDate = date.trim();\n\n // Try parsing DD.MM.YYYY or D.M.YYYY with optional spaces\n const dateMatch = cleanDate.match(/^(\\d{1,2})\\.\\s*(\\d{1,2})\\.\\s*(\\d{4})$/);\n if (dateMatch) {\n const day = dateMatch[1].padStart(2, '0');\n const month = dateMatch[2].padStart(2, '0');\n const year = dateMatch[3];\n return `${year}-${month}-${day}`;\n }\n\n // Try parsing MM/DD/YYYY\n const slashMatch = cleanDate.match(/^(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})$/);\n if (slashMatch) {\n const month = slashMatch[1].padStart(2, '0');\n const day = slashMatch[2].padStart(2, '0');\n const year = slashMatch[3];\n return `${year}-${month}-${day}`;\n }\n\n // Try parsing YYYY\n const yearMatch = cleanDate.match(/^(\\d{4})$/);\n if (yearMatch) {\n return `${yearMatch[1]}-01-01`;\n }\n\n return null;\n};\n\n// Sleep in loop\nexport const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));\n"],"mappings":";AAGA,MAAM,oBAAoB;AAC1B,MAAM,yBACJ;AAEF,MAAa,kBAAkB,QAAwB;AACrD,KAAI,CAAC,IAAK,QAAO;CAEjB,MAAM,QAAQ,IAAI,MAAM,IAAI;AAK5B,QAAO,CAFQ,MADO,kBAAkB,KAAK,MAAM,GAAG,GACjB,IAAI,IACtB,MAAM,IAAI,CAAC,MAChB;;AAGhB,MAAa,sBAAsB,QAAwB;AACzD,KAAI,IAGF,QAAO,EAFQ,KAAK,MAAM,IAAI,CAAC,KACZ,MAAM,IAAI,CAAC,MAChB;KAEd,QAAO;;AAIX,MAAa,YAAY,QAAiC;AACxD,SAAQ,KAAR;EACE,KAAK,iBACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,cAAc,YAAyC;AAClE,SAAQ,SAAR;EACE,KAAK,YACH,QAAO;EACT,KAAK,MACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAM,cAA6C;CACjD,WAAW;CACX,OAAO;CACP,QAAQ;CACR,oBAAoB;CACpB,SAAS;CACT,OAAO;CACP,mBAAmB;CACnB,kBAAkB;CAClB,qBAAqB;CACrB,SAAS;CACT,mBAAmB;CACnB,MAAM;CACP;AAED,MAAa,iBAAiB,SAAgC;AAC5D,QAAO,YAAY,SAAS;;AAG9B,MAAa,eAAe,QAAwB;AAClD,QAAO,IAAI,WAAW,KAAK,GAAG,WAAW,MAAM;;AAGjD,MAAa,eAAe,YAA8B;AACxD,QAAO;EACL,MAAM,QAAQ,OAAO,SAAY,MAAM;EACvC,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,QAAQ,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC/C,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,MAAM,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC7C,OAAO,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAC9C,SAAS,QAAQ,OAAO,SAAY,IAAI,QAAQ;EAChD,SAAS,QAAQ,OAAO,SAAY,IAAI,QAAQ;EACjD;;AAGH,MAAa,wBAAwB,QAAwB;CAG3D,MAAM,WAAW,YAFD,IAAI,MAAM,uBAAuB,CAEZ;AAErC,QAAO,CAAC,SAAS,QAAQ,KAAK,CAAC,SAAS;;;;;;;;;;;AAY1C,MAAa,aAAa,SAAgC;AACxD,KAAI,CAAC,KAAM,QAAO;CAGlB,MAAM,YAAY,KAAK,MAAM;CAG7B,MAAM,YAAY,UAAU,MAAM,wCAAwC;AAC1E,KAAI,WAAW;EACb,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,IAAI;EACzC,MAAM,QAAQ,UAAU,GAAG,SAAS,GAAG,IAAI;AAE3C,SAAO,GADM,UAAU,GACR,GAAG,MAAM,GAAG;;CAI7B,MAAM,aAAa,UAAU,MAAM,kCAAkC;AACrE,KAAI,YAAY;EACd,MAAM,QAAQ,WAAW,GAAG,SAAS,GAAG,IAAI;EAC5C,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,IAAI;AAE1C,SAAO,GADM,WAAW,GACT,GAAG,MAAM,GAAG;;CAI7B,MAAM,YAAY,UAAU,MAAM,YAAY;AAC9C,KAAI,UACF,QAAO,GAAG,UAAU,GAAG;AAGzB,QAAO;;AAIT,MAAa,SAAS,OAAe,IAAI,SAAS,QAAQ,WAAW,KAAK,GAAG,CAAC"}
|