node-csfd-api 3.0.0-next.2 → 3.0.0-next.20
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 +65 -45
- package/cjs/fetchers/fetch.polyfill.js +9 -0
- package/cjs/fetchers/index.js +2 -2
- package/cjs/helpers/cinema.helper.js +94 -0
- package/cjs/helpers/global.helper.js +9 -5
- package/cjs/helpers/movie.helper.js +9 -4
- package/cjs/index.js +10 -2
- package/cjs/interfaces/cinema.interface.js +2 -0
- package/cjs/services/cinema.service.js +45 -0
- package/cjs/services/search.service.js +23 -3
- package/cjs/vars.js +5 -1
- package/esm/fetchers/fetch.polyfill.js +6 -0
- package/esm/fetchers/index.js +2 -2
- package/esm/helpers/cinema.helper.js +81 -0
- package/esm/helpers/global.helper.js +7 -4
- package/esm/helpers/movie.helper.js +10 -5
- package/esm/index.js +10 -2
- package/esm/interfaces/cinema.interface.js +1 -0
- package/esm/services/cinema.service.js +41 -0
- package/esm/services/search.service.js +23 -3
- package/esm/vars.js +3 -0
- package/package.json +15 -6
- package/types/fetchers/fetch.polyfill.d.ts +1 -0
- package/types/helpers/cinema.helper.d.ts +19 -0
- package/types/helpers/global.helper.d.ts +10 -0
- package/types/index.d.ts +5 -1
- package/types/index.ts +4 -0
- package/types/interfaces/cinema.interface.d.ts +23 -0
- package/types/services/cinema.service.d.ts +6 -0
- package/types/vars.d.ts +2 -0
package/README.md
CHANGED
|
@@ -3,29 +3,30 @@
|
|
|
3
3
|
[](https://github.com/bartholomej/node-csfd-api/actions)
|
|
4
4
|
[](https://codecov.io/gh/bartholomej/node-csfd-api)
|
|
5
5
|
|
|
6
|
-
# CSFD API
|
|
6
|
+
# CSFD API 🎥 2024
|
|
7
7
|
|
|
8
8
|
> JavaScript NPM library for scraping **Czech Movie Database (csfd.cz)**
|
|
9
9
|
>
|
|
10
|
-
> - Browser + Node.js (SSR)
|
|
11
10
|
> - JavaScript / TypeScript
|
|
11
|
+
> - Browser + Node.js (SSR)
|
|
12
12
|
> - Tested (~100% Code coverage)
|
|
13
|
-
> - ✅ Ready for new ČSFD
|
|
13
|
+
> - ✅ Ready for new ČSFD 2024!
|
|
14
14
|
> - You can use in:
|
|
15
15
|
> - Firebase function
|
|
16
16
|
> - AWS λ (lambda function)
|
|
17
|
+
> - CloudFlare Worker
|
|
17
18
|
> - Chrome extension
|
|
18
19
|
> - React native app
|
|
19
|
-
> -
|
|
20
|
+
> - Browsers (Pay attention to CORS)
|
|
20
21
|
|
|
21
|
-
## Install
|
|
22
|
+
## 🗜️ Install
|
|
22
23
|
|
|
23
24
|
```bash
|
|
24
|
-
npm install node-csfd-api
|
|
25
|
+
npm install node-csfd-api
|
|
25
26
|
# yarn add node-csfd-api
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
## Usage and examples
|
|
29
|
+
## 🛠️ Usage and examples
|
|
29
30
|
|
|
30
31
|
- [Movies and TV Series](#Movie)
|
|
31
32
|
- [User Ratings](#User-Ratings)
|
|
@@ -34,7 +35,7 @@ npm install node-csfd-api --save
|
|
|
34
35
|
|
|
35
36
|
### Movie
|
|
36
37
|
|
|
37
|
-
Get info about [this movie](https://www.csfd.cz/film/535121-na-spatne-strane/komentare/) _(id: 535121)_
|
|
38
|
+
> Get info about [this movie](https://www.csfd.cz/film/535121-na-spatne-strane/komentare/) _(id: 535121)_
|
|
38
39
|
|
|
39
40
|
```javascript
|
|
40
41
|
import { csfd } from 'node-csfd-api';
|
|
@@ -42,8 +43,9 @@ import { csfd } from 'node-csfd-api';
|
|
|
42
43
|
csfd.movie(535121).then((movie) => console.log(movie));
|
|
43
44
|
```
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
<details>
|
|
47
|
+
<summary>Click here to see full result example</summary>
|
|
48
|
+
|
|
47
49
|
```javascript
|
|
48
50
|
{
|
|
49
51
|
id: 535121,
|
|
@@ -135,10 +137,11 @@ csfd.movie(535121).then((movie) => console.log(movie));
|
|
|
135
137
|
]
|
|
136
138
|
}
|
|
137
139
|
```
|
|
140
|
+
</details>
|
|
138
141
|
|
|
139
142
|
### Search
|
|
140
143
|
|
|
141
|
-
> Search movies and
|
|
144
|
+
> Search movies, users and TV series
|
|
142
145
|
|
|
143
146
|
```javascript
|
|
144
147
|
import { csfd } from 'node-csfd-api';
|
|
@@ -146,10 +149,11 @@ import { csfd } from 'node-csfd-api';
|
|
|
146
149
|
csfd.search('bart').then((search) => console.log(search));
|
|
147
150
|
```
|
|
148
151
|
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
<details>
|
|
153
|
+
<summary>Click here to see full result example</summary>
|
|
154
|
+
|
|
151
155
|
```javascript
|
|
152
|
-
|
|
156
|
+
[
|
|
153
157
|
{
|
|
154
158
|
id: 19653,
|
|
155
159
|
title: 'Black Bart',
|
|
@@ -173,6 +177,19 @@ movies: [
|
|
|
173
177
|
}
|
|
174
178
|
}
|
|
175
179
|
],
|
|
180
|
+
tvSeries: [
|
|
181
|
+
{
|
|
182
|
+
id: 71924,
|
|
183
|
+
title: 'Království',
|
|
184
|
+
year: 1994,
|
|
185
|
+
url: 'https://www.csfd.cz/film/71924-kralovstvi/',
|
|
186
|
+
type: 'seriál',
|
|
187
|
+
colorRating: 'good',
|
|
188
|
+
poster: 'https://image.pmgstatic.com/cache/resized/w60h85/files/images/film/posters/166/708/166708064_2da697.jpg',
|
|
189
|
+
origins: ['Dánsko'],
|
|
190
|
+
creators: []
|
|
191
|
+
}
|
|
192
|
+
],
|
|
176
193
|
users: [
|
|
177
194
|
{
|
|
178
195
|
id: 912,
|
|
@@ -184,9 +201,11 @@ users: [
|
|
|
184
201
|
]
|
|
185
202
|
```
|
|
186
203
|
|
|
204
|
+
</details>
|
|
205
|
+
|
|
187
206
|
### Creators
|
|
188
207
|
|
|
189
|
-
>
|
|
208
|
+
> Get creator info + filmography
|
|
190
209
|
|
|
191
210
|
```javascript
|
|
192
211
|
import { csfd } from 'node-csfd-api';
|
|
@@ -194,7 +213,8 @@ import { csfd } from 'node-csfd-api';
|
|
|
194
213
|
csfd.creator(2120).then((creator) => console.log(creator));
|
|
195
214
|
```
|
|
196
215
|
|
|
197
|
-
|
|
216
|
+
<details>
|
|
217
|
+
<summary>Click here to see full result example</summary>
|
|
198
218
|
|
|
199
219
|
```javascript
|
|
200
220
|
{
|
|
@@ -271,6 +291,8 @@ csfd.creator(2120).then((creator) => console.log(creator));
|
|
|
271
291
|
}
|
|
272
292
|
```
|
|
273
293
|
|
|
294
|
+
</details>
|
|
295
|
+
|
|
274
296
|
### User Ratings
|
|
275
297
|
|
|
276
298
|
#### Last ratings (last page)
|
|
@@ -300,7 +322,8 @@ csfd
|
|
|
300
322
|
.then((ratings) => console.log(ratings));
|
|
301
323
|
```
|
|
302
324
|
|
|
303
|
-
|
|
325
|
+
<details>
|
|
326
|
+
<summary>Click here to see full result example</summary>
|
|
304
327
|
|
|
305
328
|
```javascript
|
|
306
329
|
[
|
|
@@ -325,7 +348,9 @@ csfd
|
|
|
325
348
|
];
|
|
326
349
|
```
|
|
327
350
|
|
|
328
|
-
|
|
351
|
+
</details>
|
|
352
|
+
|
|
353
|
+
#### Options for user ratings
|
|
329
354
|
|
|
330
355
|
| Option | Type | Default | Description |
|
|
331
356
|
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ------------------------------------------------------ |
|
|
@@ -334,15 +359,25 @@ csfd
|
|
|
334
359
|
| **allPages** | boolean | false | Get all pages |
|
|
335
360
|
| **allPagesDelay** | number | 0 | Delay on each page request. In milliseconds |
|
|
336
361
|
|
|
337
|
-
_Note: You can not use both parameters
|
|
362
|
+
_Note: You can not use both parameters `includesOnly` and `excludes`. Parameter `includesOnly` has a priority._
|
|
338
363
|
|
|
339
|
-
## Used by
|
|
364
|
+
## 🧑💻 Used by
|
|
340
365
|
|
|
341
|
-
|
|
342
|
-
- [bartweb.cz](https://bartweb.cz) – **Last seen** section (Firebase function)
|
|
343
|
-
- KinoKlub – Mobile application for AeroFilms (native Android + iOS application)
|
|
366
|
+
### Web extensions
|
|
344
367
|
|
|
345
|
-
|
|
368
|
+
- [Netflix: chrome extension](https://chrome.google.com/webstore/detail/netflix-csfd/eomgekccbddnlpmehgdjmlphndjgnlni) ([code](https://github.com/bartholomej/netflix-csfd-ext))
|
|
369
|
+
- [Dafilms: chrome extension](https://chrome.google.com/webstore/detail/dafilms/hgcgneddmgflnbmhkjnefiobjgobbmdm) ([code](https://github.com/bartholomej/dafilms-ext))
|
|
370
|
+
- [Kviff.tv: chrome extension](https://chrome.google.com/webstore/detail/kvifftv-%20-csfd/ihpngekoejodiligajlppbeedofhnmfm) ([code](https://github.com/bartholomej/kviff-ext))
|
|
371
|
+
|
|
372
|
+
### Web applications
|
|
373
|
+
|
|
374
|
+
- [bartweb.cz](https://bartweb.cz) – **Last seen** section (**Firebase function**)
|
|
375
|
+
|
|
376
|
+
### Mobile applications
|
|
377
|
+
|
|
378
|
+
- [KinoKlub](https://play.google.com/store/apps/details?id=com.aquasoup) – Mobile application for AeroFilms (React Native: Android + iOS application)
|
|
379
|
+
|
|
380
|
+
## 🔮 Roadmap
|
|
346
381
|
|
|
347
382
|
### Scraping more pages
|
|
348
383
|
|
|
@@ -381,8 +416,8 @@ _Note: You can not use both parameters 'includesOnly' and 'excludes'. Parameter
|
|
|
381
416
|
- [ ] Search
|
|
382
417
|
- [x] Movies
|
|
383
418
|
- [x] Users
|
|
419
|
+
- [x] TV Series
|
|
384
420
|
- [ ] Creators
|
|
385
|
-
- [ ] TV Series
|
|
386
421
|
- [x] Creators
|
|
387
422
|
- [x] Bio
|
|
388
423
|
- [x] Movies (TODO categories)
|
|
@@ -390,7 +425,7 @@ _Note: You can not use both parameters 'includesOnly' and 'excludes'. Parameter
|
|
|
390
425
|
- [x] Last ratings
|
|
391
426
|
- [x] All pages
|
|
392
427
|
|
|
393
|
-
## Development
|
|
428
|
+
## 🛠️ Development
|
|
394
429
|
|
|
395
430
|
### Developing and debugging library
|
|
396
431
|
|
|
@@ -406,22 +441,7 @@ You can find and modify it in [`./demo.ts`](https://github.com/bartholomej/node-
|
|
|
406
441
|
yarn demo
|
|
407
442
|
```
|
|
408
443
|
|
|
409
|
-
##
|
|
410
|
-
|
|
411
|
-
### Publish Stable
|
|
412
|
-
|
|
413
|
-
```shell
|
|
414
|
-
yarn release:patch
|
|
415
|
-
# yarn release:minor
|
|
416
|
-
# yarn release:major
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
### Publish next channel
|
|
420
|
-
|
|
421
|
-
1. Bump version `-beta.0` in `package.json`
|
|
422
|
-
2. `yarn release:beta`
|
|
423
|
-
|
|
424
|
-
## Contribution
|
|
444
|
+
## 🤝 Contribution
|
|
425
445
|
|
|
426
446
|
I welcome you to customize this according to your needs ;)
|
|
427
447
|
|
|
@@ -433,7 +453,7 @@ Give a ⭐️ if this project helped you!
|
|
|
433
453
|
|
|
434
454
|
Or if you are brave enough consider [making a donation](https://github.com/sponsors/bartholomej) for some 🍺 or 🍵 ;)
|
|
435
455
|
|
|
436
|
-
## Privacy Policy
|
|
456
|
+
## 🕵️♀️ Privacy Policy
|
|
437
457
|
|
|
438
458
|
I DO NOT STORE ANY DATA. PERIOD.
|
|
439
459
|
|
|
@@ -441,9 +461,9 @@ I physically can't. I have nowhere to store it. I don't even have a server datab
|
|
|
441
461
|
|
|
442
462
|
That's why, with node-csfd-api, what happens on your device stays on your device till disappear.
|
|
443
463
|
|
|
444
|
-
## License
|
|
464
|
+
## 📝 License
|
|
445
465
|
|
|
446
|
-
Copyright ©
|
|
466
|
+
Copyright © 2020 – 2024 [Lukas Bartak](http://bartweb.cz)
|
|
447
467
|
|
|
448
468
|
Proudly powered by nature 🗻, wind 💨, tea 🍵 and beer 🍺 ;)
|
|
449
469
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchSafe = void 0;
|
|
4
|
+
// Check if `fetch` is available in global scope (nodejs 18+) or in window (browser). If not, use cross-fetch polyfill.
|
|
5
|
+
const cross_fetch_1 = require("cross-fetch");
|
|
6
|
+
exports.fetchSafe = (typeof fetch === 'function' && fetch) || // ServiceWorker fetch (Cloud Functions + Chrome extension)
|
|
7
|
+
(typeof global === 'object' && global.fetch) || // Node.js 18+ fetch
|
|
8
|
+
(typeof window !== 'undefined' && window.fetch) || // Browser fetch
|
|
9
|
+
cross_fetch_1.fetch; // Polyfill fetch
|
package/cjs/fetchers/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// import fetch from 'cross-fetch';
|
|
3
2
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
3
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
4
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -11,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
10
|
};
|
|
12
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
12
|
exports.fetchPage = void 0;
|
|
13
|
+
const fetch_polyfill_1 = require("./fetch.polyfill");
|
|
14
14
|
const USER_AGENTS = [
|
|
15
15
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
|
|
16
16
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1',
|
|
@@ -22,7 +22,7 @@ const headers = {
|
|
|
22
22
|
};
|
|
23
23
|
const fetchPage = (url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
24
|
try {
|
|
25
|
-
const response = yield
|
|
25
|
+
const response = yield (0, fetch_polyfill_1.fetchSafe)(url, { headers });
|
|
26
26
|
if (response.status >= 400 && response.status < 600) {
|
|
27
27
|
throw new Error(`node-csfd-api: Bad response ${response.status} for url: ${url}`);
|
|
28
28
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseMeta = exports.getFilms = exports.getGroupedFilmsByDate = exports.parseCinema = exports.getCinemaUrl = exports.getCoords = exports.getName = exports.getId = exports.getCinemaId = exports.getColorRating = void 0;
|
|
4
|
+
const global_helper_1 = require("./global.helper");
|
|
5
|
+
const getColorRating = (el) => {
|
|
6
|
+
return (0, global_helper_1.parseColor)(el === null || el === void 0 ? void 0 : el.classNames.split(' ').pop());
|
|
7
|
+
};
|
|
8
|
+
exports.getColorRating = getColorRating;
|
|
9
|
+
const getCinemaId = (el) => {
|
|
10
|
+
var _a;
|
|
11
|
+
const id = (_a = el === null || el === void 0 ? void 0 : el.id) === null || _a === void 0 ? void 0 : _a.split('-')[1];
|
|
12
|
+
return +id;
|
|
13
|
+
};
|
|
14
|
+
exports.getCinemaId = getCinemaId;
|
|
15
|
+
const getId = (url) => {
|
|
16
|
+
if (url) {
|
|
17
|
+
return (0, global_helper_1.parseIdFromUrl)(url);
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
exports.getId = getId;
|
|
22
|
+
const getName = (el) => {
|
|
23
|
+
return el.querySelector('h1').innerText.trim();
|
|
24
|
+
};
|
|
25
|
+
exports.getName = getName;
|
|
26
|
+
const getCoords = (el) => {
|
|
27
|
+
const link = el === null || el === void 0 ? void 0 : el.querySelector('.box-header img[alt="Google Maps"]').closest('a').getAttribute('href');
|
|
28
|
+
const coords = link.split('q=')[1].split(',');
|
|
29
|
+
const [lat, lng] = coords;
|
|
30
|
+
return { lat: +lat, lng: +lng };
|
|
31
|
+
};
|
|
32
|
+
exports.getCoords = getCoords;
|
|
33
|
+
const getCinemaUrl = (el) => {
|
|
34
|
+
var _a;
|
|
35
|
+
return (_a = el.querySelector('.box-header .cinema-logo a')) === null || _a === void 0 ? void 0 : _a.attributes.href;
|
|
36
|
+
};
|
|
37
|
+
exports.getCinemaUrl = getCinemaUrl;
|
|
38
|
+
const parseCinema = (el) => {
|
|
39
|
+
const title = el.querySelector('.box-header h2').innerText.trim();
|
|
40
|
+
const [city, name] = title.split(' - ');
|
|
41
|
+
return { city, name };
|
|
42
|
+
};
|
|
43
|
+
exports.parseCinema = parseCinema;
|
|
44
|
+
const getGroupedFilmsByDate = (el) => {
|
|
45
|
+
const divs = el.querySelectorAll(':scope > div');
|
|
46
|
+
const getDatesAndFilms = divs
|
|
47
|
+
.map((_, index) => index)
|
|
48
|
+
.filter((index) => index % 2 === 0)
|
|
49
|
+
.map((index) => {
|
|
50
|
+
const [date, films] = divs.slice(index, index + 2);
|
|
51
|
+
const dateText = date === null || date === void 0 ? void 0 : date.innerText.trim();
|
|
52
|
+
return { date: dateText, films: (0, exports.getFilms)('', films) };
|
|
53
|
+
});
|
|
54
|
+
return getDatesAndFilms;
|
|
55
|
+
};
|
|
56
|
+
exports.getGroupedFilmsByDate = getGroupedFilmsByDate;
|
|
57
|
+
const getFilms = (date, el) => {
|
|
58
|
+
const filmNodes = el.querySelectorAll('.cinema-table tr');
|
|
59
|
+
const films = filmNodes.map((filmNode) => {
|
|
60
|
+
var _a, _b, _c, _d;
|
|
61
|
+
const url = (_a = filmNode.querySelector('td.name h3 a')) === null || _a === void 0 ? void 0 : _a.attributes.href;
|
|
62
|
+
const id = (0, exports.getId)(url);
|
|
63
|
+
const title = (_b = filmNode.querySelector('.name h3')) === null || _b === void 0 ? void 0 : _b.text.trim();
|
|
64
|
+
const colorRating = (0, exports.getColorRating)(filmNode.querySelector('.name .icon'));
|
|
65
|
+
const showTimes = (_c = filmNode.querySelectorAll('.td-time')) === null || _c === void 0 ? void 0 : _c.map((x) => x.textContent.trim());
|
|
66
|
+
const meta = (_d = filmNode.querySelectorAll('.td-title span')) === null || _d === void 0 ? void 0 : _d.map((x) => x.text.trim());
|
|
67
|
+
return {
|
|
68
|
+
id,
|
|
69
|
+
title,
|
|
70
|
+
url,
|
|
71
|
+
colorRating,
|
|
72
|
+
showTimes,
|
|
73
|
+
meta: (0, exports.parseMeta)(meta)
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
return films;
|
|
77
|
+
};
|
|
78
|
+
exports.getFilms = getFilms;
|
|
79
|
+
const parseMeta = (meta) => {
|
|
80
|
+
const metaConvert = [];
|
|
81
|
+
for (const element of meta) {
|
|
82
|
+
if (element === 'T') {
|
|
83
|
+
metaConvert.push('subtitles');
|
|
84
|
+
}
|
|
85
|
+
else if (element === 'D') {
|
|
86
|
+
metaConvert.push('dubbing');
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
metaConvert.push(element);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return metaConvert;
|
|
93
|
+
};
|
|
94
|
+
exports.parseMeta = parseMeta;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseISO8601Duration = exports.addProtocol = exports.parseColor = exports.getColor = exports.parseIdFromUrl = void 0;
|
|
3
|
+
exports.parseISO8601Duration = exports.getDuration = exports.addProtocol = exports.parseColor = exports.getColor = exports.parseIdFromUrl = void 0;
|
|
4
4
|
const parseIdFromUrl = (url) => {
|
|
5
5
|
if (url) {
|
|
6
6
|
const idSlug = url === null || url === void 0 ? void 0 : url.split('/')[2];
|
|
@@ -46,10 +46,8 @@ const addProtocol = (url) => {
|
|
|
46
46
|
return url.startsWith('//') ? 'https:' + url : url;
|
|
47
47
|
};
|
|
48
48
|
exports.addProtocol = addProtocol;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
const matches = iso.match(iso8601DurationRegex);
|
|
52
|
-
const duration = {
|
|
49
|
+
const getDuration = (matches) => {
|
|
50
|
+
return {
|
|
53
51
|
sign: matches[1] === undefined ? '+' : '-',
|
|
54
52
|
years: matches[2] === undefined ? 0 : matches[2],
|
|
55
53
|
months: matches[3] === undefined ? 0 : matches[3],
|
|
@@ -59,6 +57,12 @@ const parseISO8601Duration = (iso) => {
|
|
|
59
57
|
minutes: matches[7] === undefined ? 0 : matches[7],
|
|
60
58
|
seconds: matches[8] === undefined ? 0 : matches[8]
|
|
61
59
|
};
|
|
60
|
+
};
|
|
61
|
+
exports.getDuration = getDuration;
|
|
62
|
+
const parseISO8601Duration = (iso) => {
|
|
63
|
+
const iso8601DurationRegex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
|
|
64
|
+
const matches = iso.match(iso8601DurationRegex);
|
|
65
|
+
const duration = (0, exports.getDuration)(matches);
|
|
62
66
|
return +duration.minutes;
|
|
63
67
|
};
|
|
64
68
|
exports.parseISO8601Duration = parseISO8601Duration;
|
|
@@ -28,9 +28,10 @@ const getColorRating = (bodyClasses) => {
|
|
|
28
28
|
exports.getColorRating = getColorRating;
|
|
29
29
|
const getRating = (el) => {
|
|
30
30
|
const ratingRaw = el.querySelector('.film-rating-average').textContent;
|
|
31
|
-
const rating =
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const rating = ratingRaw === null || ratingRaw === void 0 ? void 0 : ratingRaw.replace(/%/g, '').trim();
|
|
32
|
+
const ratingInt = parseInt(rating);
|
|
33
|
+
if (Number.isInteger(ratingInt)) {
|
|
34
|
+
return ratingInt;
|
|
34
35
|
}
|
|
35
36
|
else {
|
|
36
37
|
return null;
|
|
@@ -90,7 +91,10 @@ const getDuration = (jsonLdRaw, el) => {
|
|
|
90
91
|
exports.getDuration = getDuration;
|
|
91
92
|
const getTitlesOther = (el) => {
|
|
92
93
|
const namesNode = el.querySelectorAll('.film-names li');
|
|
93
|
-
|
|
94
|
+
if (!namesNode.length) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const titlesOther = namesNode.map((el) => {
|
|
94
98
|
const country = el.querySelector('img.flag').attributes.alt;
|
|
95
99
|
const title = el.textContent.trim().split('\n')[0];
|
|
96
100
|
if (country && title) {
|
|
@@ -103,6 +107,7 @@ const getTitlesOther = (el) => {
|
|
|
103
107
|
return null;
|
|
104
108
|
}
|
|
105
109
|
});
|
|
110
|
+
return titlesOther.filter((x) => x);
|
|
106
111
|
};
|
|
107
112
|
exports.getTitlesOther = getTitlesOther;
|
|
108
113
|
const getPoster = (el) => {
|
package/cjs/index.js
CHANGED
|
@@ -10,16 +10,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.csfd = exports.Csfd = void 0;
|
|
13
|
+
const cinema_service_1 = require("./services/cinema.service");
|
|
13
14
|
const creator_service_1 = require("./services/creator.service");
|
|
14
15
|
const movie_service_1 = require("./services/movie.service");
|
|
15
16
|
const search_service_1 = require("./services/search.service");
|
|
16
17
|
const user_ratings_service_1 = require("./services/user-ratings.service");
|
|
17
18
|
class Csfd {
|
|
18
|
-
constructor(userRatingsService, movieService, creatorService, searchService) {
|
|
19
|
+
constructor(userRatingsService, movieService, creatorService, searchService, cinemaService) {
|
|
19
20
|
this.userRatingsService = userRatingsService;
|
|
20
21
|
this.movieService = movieService;
|
|
21
22
|
this.creatorService = creatorService;
|
|
22
23
|
this.searchService = searchService;
|
|
24
|
+
this.cinemaService = cinemaService;
|
|
23
25
|
}
|
|
24
26
|
userRatings(user, config) {
|
|
25
27
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -41,10 +43,16 @@ class Csfd {
|
|
|
41
43
|
return this.searchService.search(text);
|
|
42
44
|
});
|
|
43
45
|
}
|
|
46
|
+
cinema(district, period) {
|
|
47
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
return this.cinemaService.cinemas(+district, period);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
44
51
|
}
|
|
45
52
|
exports.Csfd = Csfd;
|
|
46
53
|
const movieScraper = new movie_service_1.MovieScraper();
|
|
47
54
|
const userRatingsScraper = new user_ratings_service_1.UserRatingsScraper();
|
|
55
|
+
const cinemaScraper = new cinema_service_1.CinemaScraper();
|
|
48
56
|
const creatorScraper = new creator_service_1.CreatorScraper();
|
|
49
57
|
const searchScraper = new search_service_1.SearchScraper();
|
|
50
|
-
exports.csfd = new Csfd(userRatingsScraper, movieScraper, creatorScraper, searchScraper);
|
|
58
|
+
exports.csfd = new Csfd(userRatingsScraper, movieScraper, creatorScraper, searchScraper, cinemaScraper);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CinemaScraper = void 0;
|
|
13
|
+
const node_html_parser_1 = require("node-html-parser");
|
|
14
|
+
const fetchers_1 = require("../fetchers");
|
|
15
|
+
const vars_1 = require("../vars");
|
|
16
|
+
const cinema_helper_1 = require("./../helpers/cinema.helper");
|
|
17
|
+
class CinemaScraper {
|
|
18
|
+
cinemas(district = 1, period = 'today') {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
const url = (0, vars_1.cinemasUrl)(district, period);
|
|
21
|
+
const response = yield (0, fetchers_1.fetchPage)(url);
|
|
22
|
+
const cinemasHtml = (0, node_html_parser_1.parse)(response);
|
|
23
|
+
const contentNode = cinemasHtml.querySelectorAll('#snippet--cinemas section.box');
|
|
24
|
+
this.buildCinemas(contentNode);
|
|
25
|
+
return this.cinema;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
buildCinemas(contentNode) {
|
|
29
|
+
const cinemas = [];
|
|
30
|
+
contentNode.map((x) => {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
const cinema = {
|
|
33
|
+
id: (0, cinema_helper_1.getCinemaId)(x),
|
|
34
|
+
name: (_a = (0, cinema_helper_1.parseCinema)(x)) === null || _a === void 0 ? void 0 : _a.name,
|
|
35
|
+
city: (_b = (0, cinema_helper_1.parseCinema)(x)) === null || _b === void 0 ? void 0 : _b.city,
|
|
36
|
+
url: (0, cinema_helper_1.getCinemaUrl)(x),
|
|
37
|
+
coords: (0, cinema_helper_1.getCoords)(x),
|
|
38
|
+
screenings: (0, cinema_helper_1.getGroupedFilmsByDate)(x)
|
|
39
|
+
};
|
|
40
|
+
cinemas.push(cinema);
|
|
41
|
+
});
|
|
42
|
+
this.cinema = cinemas;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.CinemaScraper = CinemaScraper;
|
|
@@ -24,12 +24,14 @@ class SearchScraper {
|
|
|
24
24
|
const html = (0, node_html_parser_1.parse)(response);
|
|
25
25
|
const moviesNode = html.querySelectorAll('.main-movies article');
|
|
26
26
|
const usersNode = html.querySelectorAll('.main-users article');
|
|
27
|
-
|
|
27
|
+
const tvSeriesNode = html.querySelectorAll('.main-series article');
|
|
28
|
+
return this.parseSearch(moviesNode, usersNode, tvSeriesNode);
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
|
-
parseSearch(moviesNode, usersNode) {
|
|
31
|
+
parseSearch(moviesNode, usersNode, tvSeriesNode) {
|
|
31
32
|
const movies = [];
|
|
32
33
|
const users = [];
|
|
34
|
+
const tvSeries = [];
|
|
33
35
|
moviesNode.map((m) => {
|
|
34
36
|
const url = (0, search_helper_1.getUrl)(m);
|
|
35
37
|
const movie = {
|
|
@@ -59,10 +61,28 @@ class SearchScraper {
|
|
|
59
61
|
};
|
|
60
62
|
users.push(user);
|
|
61
63
|
});
|
|
64
|
+
tvSeriesNode.map((m) => {
|
|
65
|
+
const url = (0, search_helper_1.getUrl)(m);
|
|
66
|
+
const user = {
|
|
67
|
+
id: (0, global_helper_1.parseIdFromUrl)(url),
|
|
68
|
+
title: (0, search_helper_1.getTitle)(m),
|
|
69
|
+
year: (0, search_helper_1.getYear)(m),
|
|
70
|
+
url: `https://www.csfd.cz${url}`,
|
|
71
|
+
type: (0, search_helper_1.getType)(m),
|
|
72
|
+
colorRating: (0, search_helper_1.getColorRating)(m),
|
|
73
|
+
poster: (0, search_helper_1.getPoster)(m),
|
|
74
|
+
origins: (0, search_helper_1.getOrigins)(m),
|
|
75
|
+
creators: {
|
|
76
|
+
directors: (0, search_helper_1.parsePeople)(m, 'directors'),
|
|
77
|
+
actors: (0, search_helper_1.parsePeople)(m, 'actors')
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
tvSeries.push(user);
|
|
81
|
+
});
|
|
62
82
|
const search = {
|
|
63
83
|
movies: movies,
|
|
64
84
|
users: users,
|
|
65
|
-
tvSeries:
|
|
85
|
+
tvSeries: tvSeries,
|
|
66
86
|
creators: []
|
|
67
87
|
};
|
|
68
88
|
return search;
|
package/cjs/vars.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.searchUrl = exports.creatorUrl = exports.movieUrl = exports.userRatingsUrl = void 0;
|
|
3
|
+
exports.searchUrl = exports.cinemasUrl = exports.creatorUrl = exports.movieUrl = exports.userRatingsUrl = void 0;
|
|
4
4
|
const userRatingsUrl = (user, page) => `https://www.csfd.cz/uzivatel/${encodeURIComponent(user)}/hodnoceni/${page ? '?page=' + page : ''}`;
|
|
5
5
|
exports.userRatingsUrl = userRatingsUrl;
|
|
6
6
|
const movieUrl = (movie) => `https://www.csfd.cz/film/${encodeURIComponent(movie)}/prehled/`;
|
|
7
7
|
exports.movieUrl = movieUrl;
|
|
8
8
|
const creatorUrl = (creator) => `https://www.csfd.cz/tvurce/${encodeURIComponent(creator)}`;
|
|
9
9
|
exports.creatorUrl = creatorUrl;
|
|
10
|
+
const cinemasUrl = (district, period) => {
|
|
11
|
+
return `https://www.csfd.cz/kino/?period=${period}&district=${district}`;
|
|
12
|
+
};
|
|
13
|
+
exports.cinemasUrl = cinemasUrl;
|
|
10
14
|
const searchUrl = (text) => `https://www.csfd.cz/hledat/?q=${encodeURIComponent(text)}`;
|
|
11
15
|
exports.searchUrl = searchUrl;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Check if `fetch` is available in global scope (nodejs 18+) or in window (browser). If not, use cross-fetch polyfill.
|
|
2
|
+
import { fetch as crossFetch } from 'cross-fetch';
|
|
3
|
+
export const fetchSafe = (typeof fetch === 'function' && fetch) || // ServiceWorker fetch (Cloud Functions + Chrome extension)
|
|
4
|
+
(typeof global === 'object' && global.fetch) || // Node.js 18+ fetch
|
|
5
|
+
(typeof window !== 'undefined' && window.fetch) || // Browser fetch
|
|
6
|
+
crossFetch; // Polyfill fetch
|
package/esm/fetchers/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// import fetch from 'cross-fetch';
|
|
2
1
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
2
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
3
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -8,6 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
8
|
});
|
|
10
9
|
};
|
|
10
|
+
import { fetchSafe } from './fetch.polyfill';
|
|
11
11
|
const USER_AGENTS = [
|
|
12
12
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
|
|
13
13
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1',
|
|
@@ -19,7 +19,7 @@ const headers = {
|
|
|
19
19
|
};
|
|
20
20
|
export const fetchPage = (url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
21
21
|
try {
|
|
22
|
-
const response = yield
|
|
22
|
+
const response = yield fetchSafe(url, { headers });
|
|
23
23
|
if (response.status >= 400 && response.status < 600) {
|
|
24
24
|
throw new Error(`node-csfd-api: Bad response ${response.status} for url: ${url}`);
|
|
25
25
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { parseColor, parseIdFromUrl } from './global.helper';
|
|
2
|
+
export const getColorRating = (el) => {
|
|
3
|
+
return parseColor(el === null || el === void 0 ? void 0 : el.classNames.split(' ').pop());
|
|
4
|
+
};
|
|
5
|
+
export const getCinemaId = (el) => {
|
|
6
|
+
var _a;
|
|
7
|
+
const id = (_a = el === null || el === void 0 ? void 0 : el.id) === null || _a === void 0 ? void 0 : _a.split('-')[1];
|
|
8
|
+
return +id;
|
|
9
|
+
};
|
|
10
|
+
export const getId = (url) => {
|
|
11
|
+
if (url) {
|
|
12
|
+
return parseIdFromUrl(url);
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
export const getName = (el) => {
|
|
17
|
+
return el.querySelector('h1').innerText.trim();
|
|
18
|
+
};
|
|
19
|
+
export const getCoords = (el) => {
|
|
20
|
+
const link = el === null || el === void 0 ? void 0 : el.querySelector('.box-header img[alt="Google Maps"]').closest('a').getAttribute('href');
|
|
21
|
+
const coords = link.split('q=')[1].split(',');
|
|
22
|
+
const [lat, lng] = coords;
|
|
23
|
+
return { lat: +lat, lng: +lng };
|
|
24
|
+
};
|
|
25
|
+
export const getCinemaUrl = (el) => {
|
|
26
|
+
var _a;
|
|
27
|
+
return (_a = el.querySelector('.box-header .cinema-logo a')) === null || _a === void 0 ? void 0 : _a.attributes.href;
|
|
28
|
+
};
|
|
29
|
+
export const parseCinema = (el) => {
|
|
30
|
+
const title = el.querySelector('.box-header h2').innerText.trim();
|
|
31
|
+
const [city, name] = title.split(' - ');
|
|
32
|
+
return { city, name };
|
|
33
|
+
};
|
|
34
|
+
export const getGroupedFilmsByDate = (el) => {
|
|
35
|
+
const divs = el.querySelectorAll(':scope > div');
|
|
36
|
+
const getDatesAndFilms = divs
|
|
37
|
+
.map((_, index) => index)
|
|
38
|
+
.filter((index) => index % 2 === 0)
|
|
39
|
+
.map((index) => {
|
|
40
|
+
const [date, films] = divs.slice(index, index + 2);
|
|
41
|
+
const dateText = date === null || date === void 0 ? void 0 : date.innerText.trim();
|
|
42
|
+
return { date: dateText, films: getFilms('', films) };
|
|
43
|
+
});
|
|
44
|
+
return getDatesAndFilms;
|
|
45
|
+
};
|
|
46
|
+
export const getFilms = (date, el) => {
|
|
47
|
+
const filmNodes = el.querySelectorAll('.cinema-table tr');
|
|
48
|
+
const films = filmNodes.map((filmNode) => {
|
|
49
|
+
var _a, _b, _c, _d;
|
|
50
|
+
const url = (_a = filmNode.querySelector('td.name h3 a')) === null || _a === void 0 ? void 0 : _a.attributes.href;
|
|
51
|
+
const id = getId(url);
|
|
52
|
+
const title = (_b = filmNode.querySelector('.name h3')) === null || _b === void 0 ? void 0 : _b.text.trim();
|
|
53
|
+
const colorRating = getColorRating(filmNode.querySelector('.name .icon'));
|
|
54
|
+
const showTimes = (_c = filmNode.querySelectorAll('.td-time')) === null || _c === void 0 ? void 0 : _c.map((x) => x.textContent.trim());
|
|
55
|
+
const meta = (_d = filmNode.querySelectorAll('.td-title span')) === null || _d === void 0 ? void 0 : _d.map((x) => x.text.trim());
|
|
56
|
+
return {
|
|
57
|
+
id,
|
|
58
|
+
title,
|
|
59
|
+
url,
|
|
60
|
+
colorRating,
|
|
61
|
+
showTimes,
|
|
62
|
+
meta: parseMeta(meta)
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
return films;
|
|
66
|
+
};
|
|
67
|
+
export const parseMeta = (meta) => {
|
|
68
|
+
const metaConvert = [];
|
|
69
|
+
for (const element of meta) {
|
|
70
|
+
if (element === 'T') {
|
|
71
|
+
metaConvert.push('subtitles');
|
|
72
|
+
}
|
|
73
|
+
else if (element === 'D') {
|
|
74
|
+
metaConvert.push('dubbing');
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
metaConvert.push(element);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return metaConvert;
|
|
81
|
+
};
|
|
@@ -39,10 +39,8 @@ export const parseColor = (quality) => {
|
|
|
39
39
|
export const addProtocol = (url) => {
|
|
40
40
|
return url.startsWith('//') ? 'https:' + url : url;
|
|
41
41
|
};
|
|
42
|
-
export const
|
|
43
|
-
|
|
44
|
-
const matches = iso.match(iso8601DurationRegex);
|
|
45
|
-
const duration = {
|
|
42
|
+
export const getDuration = (matches) => {
|
|
43
|
+
return {
|
|
46
44
|
sign: matches[1] === undefined ? '+' : '-',
|
|
47
45
|
years: matches[2] === undefined ? 0 : matches[2],
|
|
48
46
|
months: matches[3] === undefined ? 0 : matches[3],
|
|
@@ -52,5 +50,10 @@ export const parseISO8601Duration = (iso) => {
|
|
|
52
50
|
minutes: matches[7] === undefined ? 0 : matches[7],
|
|
53
51
|
seconds: matches[8] === undefined ? 0 : matches[8]
|
|
54
52
|
};
|
|
53
|
+
};
|
|
54
|
+
export const parseISO8601Duration = (iso) => {
|
|
55
|
+
const iso8601DurationRegex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
|
|
56
|
+
const matches = iso.match(iso8601DurationRegex);
|
|
57
|
+
const duration = getDuration(matches);
|
|
55
58
|
return +duration.minutes;
|
|
56
59
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addProtocol, getColor,
|
|
1
|
+
import { addProtocol, getColor, parseISO8601Duration, parseIdFromUrl } from './global.helper';
|
|
2
2
|
export const getId = (el) => {
|
|
3
3
|
const url = el.querySelector('.tabs .tab-nav-list a').attributes.href;
|
|
4
4
|
return parseIdFromUrl(url);
|
|
@@ -20,9 +20,10 @@ export const getColorRating = (bodyClasses) => {
|
|
|
20
20
|
};
|
|
21
21
|
export const getRating = (el) => {
|
|
22
22
|
const ratingRaw = el.querySelector('.film-rating-average').textContent;
|
|
23
|
-
const rating =
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const rating = ratingRaw === null || ratingRaw === void 0 ? void 0 : ratingRaw.replace(/%/g, '').trim();
|
|
24
|
+
const ratingInt = parseInt(rating);
|
|
25
|
+
if (Number.isInteger(ratingInt)) {
|
|
26
|
+
return ratingInt;
|
|
26
27
|
}
|
|
27
28
|
else {
|
|
28
29
|
return null;
|
|
@@ -78,7 +79,10 @@ export const getDuration = (jsonLdRaw, el) => {
|
|
|
78
79
|
};
|
|
79
80
|
export const getTitlesOther = (el) => {
|
|
80
81
|
const namesNode = el.querySelectorAll('.film-names li');
|
|
81
|
-
|
|
82
|
+
if (!namesNode.length) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const titlesOther = namesNode.map((el) => {
|
|
82
86
|
const country = el.querySelector('img.flag').attributes.alt;
|
|
83
87
|
const title = el.textContent.trim().split('\n')[0];
|
|
84
88
|
if (country && title) {
|
|
@@ -91,6 +95,7 @@ export const getTitlesOther = (el) => {
|
|
|
91
95
|
return null;
|
|
92
96
|
}
|
|
93
97
|
});
|
|
98
|
+
return titlesOther.filter((x) => x);
|
|
94
99
|
};
|
|
95
100
|
export const getPoster = (el) => {
|
|
96
101
|
var _a;
|
package/esm/index.js
CHANGED
|
@@ -7,16 +7,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
+
import { CinemaScraper } from './services/cinema.service';
|
|
10
11
|
import { CreatorScraper } from './services/creator.service';
|
|
11
12
|
import { MovieScraper } from './services/movie.service';
|
|
12
13
|
import { SearchScraper } from './services/search.service';
|
|
13
14
|
import { UserRatingsScraper } from './services/user-ratings.service';
|
|
14
15
|
export class Csfd {
|
|
15
|
-
constructor(userRatingsService, movieService, creatorService, searchService) {
|
|
16
|
+
constructor(userRatingsService, movieService, creatorService, searchService, cinemaService) {
|
|
16
17
|
this.userRatingsService = userRatingsService;
|
|
17
18
|
this.movieService = movieService;
|
|
18
19
|
this.creatorService = creatorService;
|
|
19
20
|
this.searchService = searchService;
|
|
21
|
+
this.cinemaService = cinemaService;
|
|
20
22
|
}
|
|
21
23
|
userRatings(user, config) {
|
|
22
24
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -38,9 +40,15 @@ export class Csfd {
|
|
|
38
40
|
return this.searchService.search(text);
|
|
39
41
|
});
|
|
40
42
|
}
|
|
43
|
+
cinema(district, period) {
|
|
44
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
45
|
+
return this.cinemaService.cinemas(+district, period);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
49
|
const movieScraper = new MovieScraper();
|
|
43
50
|
const userRatingsScraper = new UserRatingsScraper();
|
|
51
|
+
const cinemaScraper = new CinemaScraper();
|
|
44
52
|
const creatorScraper = new CreatorScraper();
|
|
45
53
|
const searchScraper = new SearchScraper();
|
|
46
|
-
export const csfd = new Csfd(userRatingsScraper, movieScraper, creatorScraper, searchScraper);
|
|
54
|
+
export const csfd = new Csfd(userRatingsScraper, movieScraper, creatorScraper, searchScraper, cinemaScraper);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { parse } from 'node-html-parser';
|
|
11
|
+
import { fetchPage } from '../fetchers';
|
|
12
|
+
import { cinemasUrl } from '../vars';
|
|
13
|
+
import { getCinemaId, getCinemaUrl, getCoords, getGroupedFilmsByDate, parseCinema } from './../helpers/cinema.helper';
|
|
14
|
+
export class CinemaScraper {
|
|
15
|
+
cinemas(district = 1, period = 'today') {
|
|
16
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
17
|
+
const url = cinemasUrl(district, period);
|
|
18
|
+
const response = yield fetchPage(url);
|
|
19
|
+
const cinemasHtml = parse(response);
|
|
20
|
+
const contentNode = cinemasHtml.querySelectorAll('#snippet--cinemas section.box');
|
|
21
|
+
this.buildCinemas(contentNode);
|
|
22
|
+
return this.cinema;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
buildCinemas(contentNode) {
|
|
26
|
+
const cinemas = [];
|
|
27
|
+
contentNode.map((x) => {
|
|
28
|
+
var _a, _b;
|
|
29
|
+
const cinema = {
|
|
30
|
+
id: getCinemaId(x),
|
|
31
|
+
name: (_a = parseCinema(x)) === null || _a === void 0 ? void 0 : _a.name,
|
|
32
|
+
city: (_b = parseCinema(x)) === null || _b === void 0 ? void 0 : _b.city,
|
|
33
|
+
url: getCinemaUrl(x),
|
|
34
|
+
coords: getCoords(x),
|
|
35
|
+
screenings: getGroupedFilmsByDate(x)
|
|
36
|
+
};
|
|
37
|
+
cinemas.push(cinema);
|
|
38
|
+
});
|
|
39
|
+
this.cinema = cinemas;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -21,12 +21,14 @@ export class SearchScraper {
|
|
|
21
21
|
const html = parse(response);
|
|
22
22
|
const moviesNode = html.querySelectorAll('.main-movies article');
|
|
23
23
|
const usersNode = html.querySelectorAll('.main-users article');
|
|
24
|
-
|
|
24
|
+
const tvSeriesNode = html.querySelectorAll('.main-series article');
|
|
25
|
+
return this.parseSearch(moviesNode, usersNode, tvSeriesNode);
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
|
-
parseSearch(moviesNode, usersNode) {
|
|
28
|
+
parseSearch(moviesNode, usersNode, tvSeriesNode) {
|
|
28
29
|
const movies = [];
|
|
29
30
|
const users = [];
|
|
31
|
+
const tvSeries = [];
|
|
30
32
|
moviesNode.map((m) => {
|
|
31
33
|
const url = getUrl(m);
|
|
32
34
|
const movie = {
|
|
@@ -56,10 +58,28 @@ export class SearchScraper {
|
|
|
56
58
|
};
|
|
57
59
|
users.push(user);
|
|
58
60
|
});
|
|
61
|
+
tvSeriesNode.map((m) => {
|
|
62
|
+
const url = getUrl(m);
|
|
63
|
+
const user = {
|
|
64
|
+
id: parseIdFromUrl(url),
|
|
65
|
+
title: getTitle(m),
|
|
66
|
+
year: getYear(m),
|
|
67
|
+
url: `https://www.csfd.cz${url}`,
|
|
68
|
+
type: getType(m),
|
|
69
|
+
colorRating: getColorRating(m),
|
|
70
|
+
poster: getPoster(m),
|
|
71
|
+
origins: getOrigins(m),
|
|
72
|
+
creators: {
|
|
73
|
+
directors: parsePeople(m, 'directors'),
|
|
74
|
+
actors: parsePeople(m, 'actors')
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
tvSeries.push(user);
|
|
78
|
+
});
|
|
59
79
|
const search = {
|
|
60
80
|
movies: movies,
|
|
61
81
|
users: users,
|
|
62
|
-
tvSeries:
|
|
82
|
+
tvSeries: tvSeries,
|
|
63
83
|
creators: []
|
|
64
84
|
};
|
|
65
85
|
return search;
|
package/esm/vars.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export const userRatingsUrl = (user, page) => `https://www.csfd.cz/uzivatel/${encodeURIComponent(user)}/hodnoceni/${page ? '?page=' + page : ''}`;
|
|
2
2
|
export const movieUrl = (movie) => `https://www.csfd.cz/film/${encodeURIComponent(movie)}/prehled/`;
|
|
3
3
|
export const creatorUrl = (creator) => `https://www.csfd.cz/tvurce/${encodeURIComponent(creator)}`;
|
|
4
|
+
export const cinemasUrl = (district, period) => {
|
|
5
|
+
return `https://www.csfd.cz/kino/?period=${period}&district=${district}`;
|
|
6
|
+
};
|
|
4
7
|
export const searchUrl = (text) => `https://www.csfd.cz/hledat/?q=${encodeURIComponent(text)}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-csfd-api",
|
|
3
|
-
"version": "3.0.0-next.
|
|
3
|
+
"version": "3.0.0-next.20",
|
|
4
4
|
"description": "ČSFD API in JavaScript. Amazing NPM library for scrapping csfd.cz :)",
|
|
5
5
|
"main": "./cjs/index.js",
|
|
6
6
|
"author": "BART! <bart@bartweb.cz>",
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
"barrels": "barrelsby --delete -c barrels.json",
|
|
12
12
|
"postbuild": "npm-prepare-dist -s postinstall -s prepare && yarn fix-paths",
|
|
13
13
|
"tsc": "tsc",
|
|
14
|
-
"demo": "
|
|
14
|
+
"demo": "tsx demo",
|
|
15
15
|
"lint": "eslint ./src/**/**/* --fix",
|
|
16
|
-
"test": "
|
|
16
|
+
"test": "vitest",
|
|
17
|
+
"test:coverage": "yarn test run --coverage",
|
|
17
18
|
"fix-paths": "yarn json -I -f ./dist/package.json -e \"this.module='./esm/index.js';this.main='./cjs/index.js';this.types='./types/index.d.ts'\"",
|
|
18
|
-
"publish:next": "yarn && yarn build && yarn test
|
|
19
|
+
"publish:next": "yarn && yarn build && yarn test:coverage && cd dist && npm publish --tag next",
|
|
19
20
|
"postversion": "git push && git push --follow-tags",
|
|
20
21
|
"release:beta": "npm version preminor --preid=beta -m \"chore(update): prelease %s β\"",
|
|
21
22
|
"prerelease:beta": "npm version prerelease --preid=beta -m \"chore(update): prelease %s β\"",
|
|
@@ -23,8 +24,13 @@
|
|
|
23
24
|
"release:minor": "git checkout master && npm version minor -m \"chore(update): release %s 🚀\"",
|
|
24
25
|
"release:major": "git checkout master && npm version major -m \"chore(update): major release %s 💥\""
|
|
25
26
|
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"registry": "https://registry.npmjs.org"
|
|
30
|
+
},
|
|
26
31
|
"dependencies": {
|
|
27
|
-
"
|
|
32
|
+
"cross-fetch": "^4.0.0",
|
|
33
|
+
"node-html-parser": "^6.1.12"
|
|
28
34
|
},
|
|
29
35
|
"repository": {
|
|
30
36
|
"url": "git+https://github.com/bartholomej/node-csfd-api.git",
|
|
@@ -48,9 +54,12 @@
|
|
|
48
54
|
"api"
|
|
49
55
|
],
|
|
50
56
|
"engines": {
|
|
51
|
-
"node": ">=
|
|
57
|
+
"node": ">= 14"
|
|
52
58
|
},
|
|
53
59
|
"license": "MIT",
|
|
60
|
+
"lint-staged": {
|
|
61
|
+
"*.ts": "eslint --cache --fix"
|
|
62
|
+
},
|
|
54
63
|
"module": "./esm/index.js",
|
|
55
64
|
"types": "./types/index.d.ts"
|
|
56
65
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const fetchSafe: typeof fetch;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CSFDCinemaGroupedFilmsByDate, CSFDCinemaMeta, CSFDCinemaMovie } from 'interfaces/cinema.interface';
|
|
2
|
+
import { HTMLElement } from 'node-html-parser';
|
|
3
|
+
import { CSFDColorRating } from '../interfaces/global';
|
|
4
|
+
export declare const getColorRating: (el: HTMLElement) => CSFDColorRating;
|
|
5
|
+
export declare const getCinemaId: (el: HTMLElement | null) => number;
|
|
6
|
+
export declare const getId: (url: string) => number;
|
|
7
|
+
export declare const getName: (el: HTMLElement | null) => string;
|
|
8
|
+
export declare const getCoords: (el: HTMLElement | null) => {
|
|
9
|
+
lat: number;
|
|
10
|
+
lng: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const getCinemaUrl: (el: HTMLElement | null) => string;
|
|
13
|
+
export declare const parseCinema: (el: HTMLElement | null) => {
|
|
14
|
+
city: string;
|
|
15
|
+
name: string;
|
|
16
|
+
};
|
|
17
|
+
export declare const getGroupedFilmsByDate: (el: HTMLElement | null) => CSFDCinemaGroupedFilmsByDate[];
|
|
18
|
+
export declare const getFilms: (date: string, el: HTMLElement | null) => CSFDCinemaMovie[];
|
|
19
|
+
export declare const parseMeta: (meta: string[]) => CSFDCinemaMeta[];
|
|
@@ -4,4 +4,14 @@ export declare const parseIdFromUrl: (url: string) => number;
|
|
|
4
4
|
export declare const getColor: (cls: string) => CSFDColorRating;
|
|
5
5
|
export declare const parseColor: (quality: Colors) => CSFDColorRating;
|
|
6
6
|
export declare const addProtocol: (url: string) => string;
|
|
7
|
+
export declare const getDuration: (matches: any[]) => {
|
|
8
|
+
sign: string;
|
|
9
|
+
years: any;
|
|
10
|
+
months: any;
|
|
11
|
+
weeks: any;
|
|
12
|
+
days: any;
|
|
13
|
+
hours: any;
|
|
14
|
+
minutes: any;
|
|
15
|
+
seconds: any;
|
|
16
|
+
};
|
|
7
17
|
export declare const parseISO8601Duration: (iso: string) => number;
|
package/types/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { CSFDCinema, CSFDCinemaPeriod } from 'interfaces/cinema.interface';
|
|
1
2
|
import { CSFDCreator } from './interfaces/creator.interface';
|
|
2
3
|
import { CSFDMovie } from './interfaces/movie.interface';
|
|
3
4
|
import { CSFDSearch } from './interfaces/search.interface';
|
|
4
5
|
import { CSFDUserRatingConfig, CSFDUserRatings } from './interfaces/user-ratings.interface';
|
|
6
|
+
import { CinemaScraper } from './services/cinema.service';
|
|
5
7
|
import { CreatorScraper } from './services/creator.service';
|
|
6
8
|
import { MovieScraper } from './services/movie.service';
|
|
7
9
|
import { SearchScraper } from './services/search.service';
|
|
@@ -11,10 +13,12 @@ export declare class Csfd {
|
|
|
11
13
|
private movieService;
|
|
12
14
|
private creatorService;
|
|
13
15
|
private searchService;
|
|
14
|
-
|
|
16
|
+
private cinemaService;
|
|
17
|
+
constructor(userRatingsService: UserRatingsScraper, movieService: MovieScraper, creatorService: CreatorScraper, searchService: SearchScraper, cinemaService: CinemaScraper);
|
|
15
18
|
userRatings(user: string | number, config?: CSFDUserRatingConfig): Promise<CSFDUserRatings[]>;
|
|
16
19
|
movie(movie: number): Promise<CSFDMovie>;
|
|
17
20
|
creator(creator: number): Promise<CSFDCreator>;
|
|
18
21
|
search(text: string): Promise<CSFDSearch>;
|
|
22
|
+
cinema(district: number, period: CSFDCinemaPeriod): Promise<CSFDCinema[]>;
|
|
19
23
|
}
|
|
20
24
|
export declare const csfd: Csfd;
|
package/types/index.ts
CHANGED
|
@@ -4,18 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
export * from "./index";
|
|
6
6
|
export * from "./vars";
|
|
7
|
+
export * from "./fetchers/fetch.polyfill";
|
|
7
8
|
export * from "./fetchers/index";
|
|
9
|
+
export * from "./helpers/cinema.helper";
|
|
8
10
|
export * from "./helpers/creator.helper";
|
|
9
11
|
export * from "./helpers/global.helper";
|
|
10
12
|
export * from "./helpers/movie.helper";
|
|
11
13
|
export * from "./helpers/search-user.helper";
|
|
12
14
|
export * from "./helpers/search.helper";
|
|
13
15
|
export * from "./helpers/user-ratings.helper";
|
|
16
|
+
export * from "./interfaces/cinema.interface";
|
|
14
17
|
export * from "./interfaces/creator.interface";
|
|
15
18
|
export * from "./interfaces/global";
|
|
16
19
|
export * from "./interfaces/movie.interface";
|
|
17
20
|
export * from "./interfaces/search.interface";
|
|
18
21
|
export * from "./interfaces/user-ratings.interface";
|
|
22
|
+
export * from "./services/cinema.service";
|
|
19
23
|
export * from "./services/creator.service";
|
|
20
24
|
export * from "./services/movie.service";
|
|
21
25
|
export * from "./services/search.service";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CSFDMovieListItem } from './movie.interface';
|
|
2
|
+
export interface CSFDCinema {
|
|
3
|
+
id: number;
|
|
4
|
+
name: string;
|
|
5
|
+
city: string;
|
|
6
|
+
url: string;
|
|
7
|
+
coords: {
|
|
8
|
+
lat: number;
|
|
9
|
+
lng: number;
|
|
10
|
+
};
|
|
11
|
+
region?: string;
|
|
12
|
+
screenings: CSFDCinemaGroupedFilmsByDate[];
|
|
13
|
+
}
|
|
14
|
+
export interface CSFDCinemaGroupedFilmsByDate {
|
|
15
|
+
date: string;
|
|
16
|
+
films: CSFDCinemaMovie[];
|
|
17
|
+
}
|
|
18
|
+
export interface CSFDCinemaMovie extends CSFDMovieListItem {
|
|
19
|
+
meta: CSFDCinemaMeta[];
|
|
20
|
+
showTimes: string[];
|
|
21
|
+
}
|
|
22
|
+
export type CSFDCinemaMeta = 'dubbing' | '3D' | 'subtitles' | string;
|
|
23
|
+
export type CSFDCinemaPeriod = 'today' | 'weekend' | 'week' | 'tomorrow' | 'month';
|
package/types/vars.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { CSFDCinemaPeriod } from 'interfaces/cinema.interface';
|
|
1
2
|
export declare const userRatingsUrl: (user: string | number, page?: number) => string;
|
|
2
3
|
export declare const movieUrl: (movie: number) => string;
|
|
3
4
|
export declare const creatorUrl: (creator: number | string) => string;
|
|
5
|
+
export declare const cinemasUrl: (district: number | string, period: CSFDCinemaPeriod) => string;
|
|
4
6
|
export declare const searchUrl: (text: string) => string;
|