rezka.ts 1.0.2 → 1.0.3

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.
Files changed (2) hide show
  1. package/README.md +145 -58
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,11 +1,14 @@
1
- # rezka-api
1
+ # rezka.ts
2
2
 
3
3
  > Unofficial TypeScript API wrapper for the [HDRezka](https://rezka.ag) streaming service.
4
4
 
5
5
  ## Features
6
6
 
7
- - **No boilerplate** — top-level `search()` and `load()` functions, no class instantiation required
7
+ - **No boilerplate** — top-level `search()`, `load()`, `browse()` functions, no class instantiation required
8
8
  - **Unified stream API** — one `.streams()` call for movies; fluent `.episode(s, e).streams()` for series
9
+ - **Rich metadata** — IMDb/KP ratings, actors, directors, genres, country, duration and more, parsed eagerly
10
+ - **Browse catalog** — list movies/series by type, filter (`last`, `popular`, `soon`) and page
11
+ - **Authentication** — `login()` returns a ready-to-use authenticated client
9
12
  - **Consistent types** — all IDs are `number`, `type` is a proper union, not `string`
10
13
  - **Smart defaults** — omit `translationId` to auto-use the first available track
11
14
  - **TypeScript-first** — full type definitions, zero `any`
@@ -16,11 +19,11 @@
16
19
  ## Installation
17
20
 
18
21
  ```bash
19
- npm install rezka-api
22
+ npm install rezka.ts
20
23
  # or
21
- yarn add rezka-api
24
+ yarn add rezka.ts
22
25
  # or
23
- pnpm add rezka-api
26
+ pnpm add rezka.ts
24
27
  ```
25
28
 
26
29
  **Requires Node.js ≥ 18.**
@@ -30,8 +33,8 @@ pnpm add rezka-api
30
33
  ## Quick Start
31
34
 
32
35
  ```typescript
33
- import { search, load } from 'rezka-api';
34
- // or: import rezka from 'rezka-api'; then rezka.search(...) / rezka.load(...)
36
+ import { search, load, browse } from 'rezka.ts';
37
+ // or: import rezka from 'rezka.ts'; then rezka.search(...) / rezka.load(...)
35
38
 
36
39
  // 1. Search
37
40
  const results = await search('mr robot');
@@ -40,19 +43,28 @@ console.log(results[0]);
40
43
 
41
44
  // 2. Load media page — all metadata available synchronously
42
45
  const media = await load(results[0].url);
43
- console.log(media.title); // "Мистер Робот"
44
- console.log(media.origTitle); // "Mr. Robot"
45
- console.log(media.type); // "series"
46
- console.log(media.translations); // [{ id: 56, title: 'English' }, ...]
46
+ console.log(media.title); // "Мистер Робот"
47
+ console.log(media.origTitle); // "Mr. Robot"
48
+ console.log(media.type); // "series"
49
+ console.log(media.rating.imdb); // { score: 8.5, votes: 1234567 }
50
+ console.log(media.directors); // ['Sam Esmail']
51
+ console.log(media.actors); // ['Rami Malek', ...]
52
+ console.log(media.genres); // ['Thriller', 'Drama']
53
+ console.log(media.translations); // [{ id: 56, title: 'English' }, ...]
47
54
 
48
55
  // 3a. Movie — get streams directly
49
- const streams = await media.streams(); // uses first translation
50
- const streams = await media.streams(56); // or pick a specific one
56
+ const streams = await media.streams(); // uses first translation
57
+ const streams = await media.streams(56); // or pick a specific one
51
58
  console.log(streams); // { '1080p': 'https://...m3u8', '720p': '...', ... }
52
59
 
53
60
  // 3b. Series — fluent episode selector
54
61
  const streams = await media.episode(1, 1).streams(); // S01E01, default translation
55
- const streams = await media.episode(1, 3).streams(56); // S01E03, English
62
+ const streams = await media.episode(1, 3).streams(56); // S01E03, specific translation
63
+
64
+ // 4. Browse catalog
65
+ const page = await browse({ type: 'movie', filter: 'popular' });
66
+ console.log(page.items[0].title);
67
+ console.log(page.hasNextPage); // → call with page: 2
56
68
  ```
57
69
 
58
70
  ---
@@ -92,12 +104,66 @@ const media = await load('https://rezka.ag/films/action/12345-inception.html');
92
104
 
93
105
  ---
94
106
 
107
+ ### `browse(browseOptions?, clientOptions?)`
108
+
109
+ ```typescript
110
+ browse(browseOptions?: BrowseOptions, clientOptions?: RezkaOptions): Promise<BrowsePage>
111
+ ```
112
+
113
+ Browse the HDRezka catalog — returns a paginated list of items.
114
+
115
+ ```typescript
116
+ import { browse } from 'rezka.ts';
117
+
118
+ // Popular movies, page 1
119
+ const page = await browse({ type: 'movie', filter: 'popular' });
120
+ console.log(page.items); // BrowseItem[]
121
+ console.log(page.hasNextPage); // true → fetch page 2
122
+
123
+ // Latest series, page 2
124
+ const page2 = await browse({ type: 'series', filter: 'last', page: 2 });
125
+
126
+ // Specific genre URL
127
+ const comedy = await browse({ genreUrl: '/films/comedy/' });
128
+ ```
129
+
130
+ #### `BrowseOptions`
131
+
132
+ | Option | Type | Description |
133
+ |---|---|---|
134
+ | `type` | `'movie' \| 'series' \| 'cartoon' \| 'anime'` | Content type to browse |
135
+ | `filter` | `'last' \| 'popular' \| 'soon' \| 'watching'` | Sort order |
136
+ | `page` | `number` | Page number, 1-based (default: `1`) |
137
+ | `genreUrl` | `string` | Direct genre path, e.g. `'/films/comedy/'` — overrides `type` |
138
+
139
+ ---
140
+
141
+ ### `login(email, password, options?)`
142
+
143
+ ```typescript
144
+ login(email: string, password: string, options?: RezkaOptions): Promise<client>
145
+ ```
146
+
147
+ Log in to HDRezka. Returns an **authenticated client** with session cookies baked in — use it like `createClient()`.
148
+
149
+ ```typescript
150
+ import { login } from 'rezka.ts';
151
+
152
+ const client = await login('you@email.com', 'password');
153
+ const results = await client.search('inception');
154
+ const media = await client.load(results[0].url);
155
+ ```
156
+
157
+ **Throws** if credentials are incorrect.
158
+
159
+ ---
160
+
95
161
  ### `createClient(options)`
96
162
 
97
163
  For cases where you need a **custom mirror, timeout, or shared configuration**. Returns an object with the same `search` and `load` methods bound to that config.
98
164
 
99
165
  ```typescript
100
- import { createClient } from 'rezka-api';
166
+ import { createClient } from 'rezka.ts';
101
167
 
102
168
  const client = createClient({
103
169
  baseUrl: 'https://my-mirror.com',
@@ -133,10 +199,19 @@ Returned by `load()`. All metadata is parsed eagerly and available as **readonly
133
199
  | `title` | `string` | Localized title |
134
200
  | `origTitle` | `string \| null` | Original (non-localized) title |
135
201
  | `type` | `MediaType` | `'movie' \| 'series' \| 'animation' \| 'cartoon' \| 'anime' \| 'documentary' \| 'unknown'` |
136
- | `year` | `string \| null` | Release year (from the metadata table) |
202
+ | `year` | `string \| null` | Release year |
137
203
  | `thumbnail` | `string \| null` | Poster image URL |
138
204
  | `description` | `string \| null` | Plot description |
139
- | `info` | `InfoRow[]` | Structured metadata table (genre, country, director, cast…) |
205
+ | `rating` | `{ imdb: Rating \| null; kp: Rating \| null }` | IMDb and KinoPoisk scores |
206
+ | `slogan` | `string \| null` | Tagline / slogan |
207
+ | `country` | `string \| null` | Country of production |
208
+ | `quality` | `string \| null` | Video quality label (e.g. `'HDRip'`) |
209
+ | `ageRating` | `number \| null` | Age restriction as a number (e.g. `16`) |
210
+ | `duration` | `number \| null` | Runtime in minutes |
211
+ | `genres` | `string[]` | Genre names |
212
+ | `directors` | `string[]` | Director names |
213
+ | `actors` | `string[]` | Main cast names |
214
+ | `info` | `InfoRow[]` | Raw metadata table rows |
140
215
  | `translations` | `Translation[]` | Available audio tracks — pass `.id` to `.streams()` |
141
216
  | `seasons` | `Season[]` | Season list — empty array for movies |
142
217
 
@@ -149,7 +224,7 @@ episodes(seasonId: number): Episode[]
149
224
  Get the episode list for a season. IDs come from `media.seasons`.
150
225
 
151
226
  ```typescript
152
- const season = media.seasons[0]; // { id: 1, title: 'Season 1' }
227
+ const season = media.seasons[0]; // { id: 1, title: 'Season 1' }
153
228
  const episodes = media.episodes(season.id); // [{ id, episodeId, seasonId, title }]
154
229
  ```
155
230
 
@@ -163,8 +238,8 @@ Select a specific episode. Returns an `EpisodeRef` synchronously (no network cal
163
238
 
164
239
  ```typescript
165
240
  const ref = media.episode(1, 1);
166
- const streams = await ref.streams(); // first available translation
167
- const streams = await ref.streams(56); // specific translation ID
241
+ const streams = await ref.streams(); // first available translation
242
+ const streams = await ref.streams(56); // specific translation ID
168
243
  ```
169
244
 
170
245
  #### `media.streams(translationId?)`
@@ -188,40 +263,45 @@ const url1080 = streams['1080p'];
188
263
  ### Types
189
264
 
190
265
  ```typescript
191
- type MediaType = 'movie' | 'series' | 'animation' | 'cartoon' | 'anime' | 'documentary' | 'unknown';
192
- type StreamUrls = Record<string, string>; // quality → HLS URL
266
+ type MediaType = 'movie' | 'series' | 'animation' | 'cartoon' | 'anime' | 'documentary' | 'unknown';
267
+ type StreamUrls = Record<string, string>; // quality → HLS URL
268
+ type BrowseFilter = 'last' | 'popular' | 'soon' | 'watching';
193
269
 
270
+ interface Rating { score: number; votes: number; }
194
271
  interface SearchResult { url: string; title: string; type: MediaType; year: string; }
195
272
  interface Translation { id: number; title: string; }
196
273
  interface Season { id: number; title: string; }
197
274
  interface Episode { id: number; episodeId: number; seasonId: number; title: string; }
198
275
  interface InfoRow { key: string; value: string; }
199
276
  interface EpisodeRef { streams(translationId?: number): Promise<StreamUrls>; }
277
+ interface BrowseItem { id: number; url: string; title: string; poster: string; type: MediaType; info: string; }
278
+ interface BrowsePage { items: BrowseItem[]; page: number; hasNextPage: boolean; }
279
+ interface BrowseOptions {
280
+ type?: 'movie' | 'series' | 'cartoon' | 'anime';
281
+ filter?: BrowseFilter;
282
+ page?: number;
283
+ genreUrl?: string;
284
+ }
200
285
  ```
201
286
 
202
287
  ---
203
288
 
204
- ## Full Example — Browse and Stream a Series
289
+ ## Full Example — Series
205
290
 
206
291
  ```typescript
207
- import { search, load } from 'rezka-api';
292
+ import { search, load } from 'rezka.ts';
208
293
 
209
294
  const results = await search('mr robot');
210
295
  const media = await load(results[0].url);
211
296
 
212
- console.log(`${media.title} (${media.origTitle})`); // Мистер Робот (Mr. Robot)
213
- console.log(`Type: ${media.type}`); // series
214
- console.log(`Year: ${media.year}`);
215
-
216
- // Translations
217
- media.translations.forEach(t => console.log(`[${t.id}] ${t.title}`));
218
- // [56] English
219
- // [238] Русский
297
+ console.log(`${media.title} (${media.origTitle})`); // Мистер Робот (Mr. Robot)
298
+ console.log(`IMDb: ${media.rating.imdb?.score}`); // IMDb: 8.5
299
+ console.log(`Genres: ${media.genres.join(', ')}`);
300
+ console.log(`Directors: ${media.directors.join(', ')}`);
220
301
 
221
302
  // Seasons & episodes
222
303
  const season = media.seasons[0];
223
304
  const episodes = media.episodes(season.id);
224
- console.log(`${season.title}: ${episodes.length} episodes`);
225
305
 
226
306
  // Stream S01E01 with the first available translation
227
307
  const streams = await media.episode(season.id, episodes[0].episodeId).streams();
@@ -231,21 +311,38 @@ console.log('1080p:', streams['1080p']);
231
311
 
232
312
  ---
233
313
 
234
- ## Full Example — Stream a Movie
314
+ ## Full Example — Movie
235
315
 
236
316
  ```typescript
237
- import { search, load } from 'rezka-api';
317
+ import { search, load } from 'rezka.ts';
238
318
 
239
319
  const results = await search('inception');
240
320
  const media = await load(results[0].url);
241
321
 
242
- // Get all qualities, auto-pick first translation
243
- const streams = await media.streams();
322
+ console.log(`${media.duration} min, ${media.country}, ${media.ageRating}+`);
323
+
324
+ const streams = await media.streams(); // auto first translation
325
+ const streams = await media.streams(56); // specific translation
244
326
  console.log(streams);
245
- // { '1080p': 'https://...m3u8', '720p': '...', '480p': '...' }
327
+ // { '1080p': 'https://...m3u8', '720p': '...', '4K': '...' }
328
+ ```
329
+
330
+ ---
331
+
332
+ ## Full Example — Browse Catalog
246
333
 
247
- // Pick a specific translation
248
- const streams = await media.streams(56);
334
+ ```typescript
335
+ import { browse } from 'rezka.ts';
336
+
337
+ let page = 1;
338
+ while (true) {
339
+ const result = await browse({ type: 'series', filter: 'popular', page });
340
+ for (const item of result.items) {
341
+ console.log(`[${item.id}] ${item.title} ${item.info}`);
342
+ }
343
+ if (!result.hasNextPage) break;
344
+ page++;
345
+ }
249
346
  ```
250
347
 
251
348
  ---
@@ -253,32 +350,22 @@ const streams = await media.streams(56);
253
350
  ## Development
254
351
 
255
352
  ```bash
256
- # Install dependencies
257
353
  npm install
258
-
259
- # Build (CJS + ESM → dist/)
260
- npm run build
261
-
262
- # Run tests
263
- npm test
264
-
265
- # Watch mode
266
- npm run test:watch
267
-
268
- # Type-check
354
+ npm run build # CJS + ESM → dist/
355
+ npm test # 59 tests
269
356
  npm run typecheck
270
357
  ```
271
358
 
272
359
  ### Project Structure
273
360
 
274
361
  ```
275
- rezka-api/
362
+ rezka.ts/
276
363
  ├── src/
277
- │ ├── types.ts # All TypeScript types and interfaces
278
- │ ├── utils.ts # Stream URL decoder, parser, type detector
279
- │ ├── http.ts # Axios client factory
280
- │ ├── Media.ts # Media class — metadata + stream methods
281
- │ └── index.ts # Public API: search(), load(), createClient()
364
+ │ ├── types.ts # All TypeScript types and interfaces
365
+ │ ├── utils.ts # Stream URL decoder, parser, type detector
366
+ │ ├── http.ts # Axios client factory
367
+ │ ├── Media.ts # Media class — metadata + stream methods
368
+ │ └── index.ts # Public API: search(), load(), browse(), login(), createClient()
282
369
  ├── tests/
283
370
  │ ├── utils.test.ts # Unit tests (decoder, parser, type detection)
284
371
  │ └── rezka.test.ts # Integration tests with mocked HTTP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rezka.ts",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Unofficial TypeScript API wrapper for the HDRezka streaming service",
5
5
  "author": "neverlxsss",
6
6
  "license": "ISC",