Metadata-Version: 2.4
Name: hdrezka
Version: 5.1.0
Summary: Async HDRezka client library for scripts and multi-user APIs
License-Expression: MIT
License-File: LICENSE
Keywords: HDRezka,watch online,api,stream,m3u8,hls,async,client
Author: Nikita Denissov
Author-email: n.denissov@proton.me
Requires-Python: >=3.10
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: Other Audience
Classifier: Intended Audience :: Telecommunications Industry
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Multimedia :: Video
Classifier: Typing :: Typed
Provides-Extra: socks
Requires-Dist: beautifulsoup4 (>=4.13.4,<5.0.0)
Requires-Dist: httpx (>=0.28.1,<0.29.0)
Requires-Dist: httpx[socks] (>=0.28.1,<0.29.0) ; extra == "socks"
Requires-Dist: lxml (>=6.0.0,<7.0.0) ; python_version < "3.15"
Project-URL: Bug Tracker, https://github.com/ndenissov/HDRezka/issues
Project-URL: Documentation, https://ndenissov.github.io/HDRezka/hdrezka
Project-URL: Homepage, https://github.com/ndenissov/HDRezka
Project-URL: Repository, https://github.com/ndenissov/HDRezka
Description-Content-Type: text/markdown

# HDRezka

[![PyPI version](https://img.shields.io/pypi/v/hdrezka.svg)](https://pypi.org/project/hdrezka/)
[![Downloads](https://static.pepy.tech/badge/hdrezka)](https://pepy.tech/project/hdrezka)
[![Python versions](https://img.shields.io/pypi/pyversions/hdrezka.svg)](https://pypi.org/project/hdrezka/)
[![License](https://img.shields.io/pypi/l/hdrezka.svg)](https://github.com/ndenissov/hdrezka/blob/main/LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/ndenissov/hdrezka)](https://github.com/ndenissov/hdrezka/stargazers)

Async Python client for HDRezka. Designed for scripts and for multi-user backends
(one `HDRezkaClient` instance per user / request scope).

## Install

```bash
pip install hdrezka
```

Socks proxy support:

```bash
pip install hdrezka[socks]
```

## Quick start

```python
import asyncio
import os

from hdrezka import HDRezkaClient


async def main():
    async with HDRezkaClient() as client:
        await client.login(os.environ['LOGIN_NAME'], os.environ['LOGIN_PASSWORD'])

        items = await client.search('Breaking Bad').get_page(1)
        player = await client.player(items[0].url)
        # or: player = await Player(items[0].url, client=client)

        print(player.post.info)

        translator_id = None
        for name, id_ in player.post.translators.name_id.items():
            if 'субтитры' in name.casefold():
                translator_id = id_
                break

        stream = await player.get_stream(1, 1, translator_id)
        video = stream.video
        print(await video.last_url[-1])
        print(await video[video.min].last_url[0].mp4)

        subtitles = stream.subtitles
        if subtitles.has_subtitles:
            print(subtitles.default.url)


if __name__ == '__main__':
    asyncio.run(main())
```

## HDRezkaClient

`HDRezkaClient` owns the HTTP session, active mirror (`host`), cookies, and a small
player cache. Create a separate client for each user when building an API.

| Method / property                                  | Purpose                                                      |
|----------------------------------------------------|--------------------------------------------------------------|
| `await client.login(email, password)`              | Discover an active mirror and authenticate                   |
| `client.search(query)`                             | Bound `Search`                                               |
| `client.page(url=None)`                            | Bound catalog `Page`                                         |
| `client.favorites(cat_id=None)`                    | Bound `Favorites` page (`/favorites/` or `/favorites/{id}/`) |
| `await client.player(url)`                         | `PlayerMovie` or `PlayerSeries`                              |
| `await client.post(url)`                           | Initialized `Post`                                           |
| `await client.navbar()`                            | Site top navigation map                                      |
| `await client.series_updates()`                    | Sidebar series update feed (home page)                       |
| `await client.add_favorites_cat(name)`             | Create a favorites collection                                |
| `await client.rename_favorites_cat(cat_id, name)`  | Rename a collection                                          |
| `await client.add_favorites_post(post_id, cat_id)` | Add a title to a collection                                  |
| `await client.remove_favorites_cat(cat_id)`        | Delete a collection                                          |
| `client.ajax`                                      | Bound `AJAX` helpers (streams, trailers, favorites, …)       |
| `client.host`                                      | Current mirror base URL                                      |
| `await client.aclose()`                            | Close the HTTP client (also via `async with`)                |

Constructor options: `host`, `proxy`, `http_client`, `request_kwargs`, `headers`,
`redirect_url`.

Domain types (`Search`, `Player`, `Post`, `Page`, `Favorites`, `AJAX`) require an
explicit `client=` (or are created through the client factories above).

## Navigation, filters, and sidebar

These parsers read whatever the HTML currently contains (no hardcoded genre lists).
Missing blocks return empty structures.

### Navbar

```python
nav = await client.navbar()
for item in nav.items:
    print(item.name, item.url, item.single)
    if item.submenu:
        for genre in item.submenu.genres:
            print(' ', genre.name, genre.url)
        for collection in item.submenu.collections:
            print(' ', collection.name, collection.url, collection.classes)
        if item.submenu.find_best:
            print(' ', item.submenu.find_best.categories, item.submenu.find_best.years)
```

You can also parse from any page: `await client.page().get_navbar()`, or call
`parse_navbar(html)` on a saved HTML string / BeautifulSoup tree.

### Catalog filters and sorting

On category/genre pages, `ul.b-content__main_filters` exposes sort links (`filter=`)
and content-type links (`genre=`).

```python
page = client.page('/films/')
filters = await page.get_filters()
for link in filters.sorts:
    print(link.name, link.param, link.value, link.active)
for link in filters.types:
    print(link.name, link.param, link.value, link.active)

# Apply a filter when fetching titles
items = await page.get_page(1, filter='popular', genre=1)

# Or get titles + filters + updates in one response
content = await page.get_page_content(1, filter='watching')
print(content.filters, content.series_updates)
```

### Series updates (sidebar)

```python
for block in await client.series_updates():
    print(block.date)
    for row in block.items:
        print(row.name, row.url, row.season, row.episode, row.translation)
```

### Post schedule

Many series pages include `.b-post__schedule` tables (air dates per season).
They are parsed when you await a `Post`:

```python
post = await client.post(url)
for block in post.schedule:
    print(block.title)
    for row in block.items:
        print(row.season, row.episode, row.title, row.date, row.exists)
```

Or call `parse_schedule(html)` on saved HTML / a BeautifulSoup tree.

## Favorites (collections)

Favorites require `login`. Mutations go through `POST /ajax/favorites/`; browsing
uses the normal `Page` flow on `/favorites/[id]`.

```python
async with HDRezkaClient() as client:
    await client.login(email, password)

    # Create a collection in the profile
    created = await client.add_favorites_cat('Смотрю')
    cat_id = created['id']

    # Rename
    await client.rename_favorites_cat(cat_id, 'Не буду смотреть')

    # Add a title (post id from Post / Player)
    post = await client.post('series/thriller/646-vo-vse-tyazhkie-2008.html')
    await client.add_favorites_post(post.id, cat_id)

    # Browse the collection (same pagination / InlineItem API as other pages)
    fav = client.favorites(cat_id)
    titles = await fav.get_page(1)
    cats = await fav.get_cats()  # a.b-favorites_content__cats_list_link
    for cat in cats:
        print(cat.id, cat.name, cat.count, cat.url)

    # Remove the collection
    await client.remove_favorites_cat(cat_id)
```

Equivalent low-level calls: `client.ajax.add_favorites_cat`,
`rename_favorites_cat`, `add_favorites_post`, `remove_favorites_cat`.

## Login and mirrors

Create an HDRezka account. If registration is disabled, try social login and set a password.

`login` requests the standby URL (default `https://standby-rezka.tv/`), follows the
redirect to an active mirror, posts credentials to `/ajax/login/`, updates
`client.host`, and stores cookies on that client only.

This also helps when the site returns 403 for a suspicious IP.

You can set a mirror without login:

```python
client = HDRezkaClient(host='https://hdrezka.club/')
```

If a domain is behind Cloudflare, responses may be unreliable. See
[mirrors.txt](https://github.com/ndenissov/HDRezka/blob/main/mirrors.txt).

Default constants (not shared session state):

```python
from hdrezka.url import Request, DEFAULT_HOST, DEFAULT_REDIRECT_URL
```

## Proxies

```python
from hdrezka import HDRezkaClient

client = HDRezkaClient(proxy='socks5://localhost:9050')
```

Or pass your own `httpx.AsyncClient` as `http_client=` (the library will not close it).

## Migration from 4.x

Version 5 removes process-global session state.

| 4.x                                         | 5.x                                                              |
|---------------------------------------------|------------------------------------------------------------------|
| `await login_global(email, password)`       | `await client.login(email, password)`                            |
| `DEFAULT_CLIENT` / `DEFAULT_REQUEST_KWARGS` | `HDRezkaClient(...)` / `request_kwargs=`                         |
| Mutating `Request.HOST` for the process     | `client.host` or `HDRezkaClient(host=...)`                       |
| `Search('query')`                           | `client.search('query')` or `Search('query', client=client)`     |
| `await Player(url)`                         | `await client.player(url)` or `await Player(url, client=client)` |
| `AJAX.get_stream(...)` (classmethods)       | `client.ajax.get_stream(...)`                                    |

Parsing and stream logic are unchanged; wire everything through a client.

## Documentation

- API reference: [ndenissov.github.io/HDRezka](https://ndenissov.github.io/HDRezka/)
- Changelog: [CHANGELOG.md](CHANGELOG.md)

