An asynchronous .NET 10 library for working with HDRezka that can use sessions, load account, catalog, comment, and media data, enumerate translations and episodes, resolve video streams and subtitles
Important
The website has multiple mirrors and can change its markup or API without notice, so pass the mirror URL you are allowed to access; this package does not contain a hard-coded domain
Note
The project was originally a fork of https://github.com/SuperZombi/HdRezkaApi intended to create a library for .NET, but it ultimately became completely self-sufficient and superior to the original in every aspect
- .NET 10 SDK
dotnet add package HDRezka.NETFor task-oriented guides and troubleshooting, see the
project wiki, published
automatically from docs/wiki on main
Use a session when performing more than one request:
usingHdRezka;usingvarsession=newClient("https://your-mirror.example");varauthentication=awaitsession.LoginAsync("mail@example.com","password",rememberMe:true);Console.WriteLine(authentication.IsAuthenticated);Console.WriteLine(authentication.AccountTier);usingvarmedia=awaitsession.GetAsync("/films/drama/123-title.html");Console.WriteLine(media.Name);Console.WriteLine(media.Description);Console.WriteLine(media.ShortDescription);Console.WriteLine(media.Thumbnail);Console.WriteLine(media.Rating.Value);Console.WriteLine(media.Rating.Votes);Console.WriteLine(media.Format);Console.WriteLine(media.Category);Console.WriteLine(media.Details.Duration);foreach(vargenreinmedia.Details.Genres){Console.WriteLine(genre.Name);}foreach(vartranslatorinmedia.TranslationOptions){Console.WriteLine($"{translator.Id}: {translator.Name}, Premium: {translator.IsPremium}");}TranslationOptions preserves every website entry even when normal and
director's-cut variants share the same numeric ID, while Translators remains
available as a compatibility view containing the first variant for each ID
Some media pages remain available while their player data is lost or being
restored. Check Playback before requesting streams:
if(!media.Playback.IsAvailable){Console.WriteLine(media.Playback.Availability);Console.WriteLine(media.Playback.Reason);}Metadata, comments, ratings, related parts, and account operations remain
available for these pages. Stream and episode operations throw
PlaybackUnavailableException without sending a player request
For a single page, a session is optional:
usingvarmedia=awaitMedia.CreateAsync("https://your-mirror.example/films/drama/123-title.html");Details also exposes the full release date, countries, genres, directors,
cast, quality, age rating, tagline, external ratings, collections, rankings,
recommendations, and series schedule when present
Description contains the complete text from the visible media page, while
ShortDescription contains the shorter metadata summary when the website
provides it. Both values are parsed from the same response without an extra
request
media.Rating is the aggregate rating submitted by HDRezka users. Ratings
imported from IMDb, Kinopoisk, and other services are available separately
through media.Details.ExternalRatings. Each external rating keeps the HDRezka
redirect in Url, exposes the decoded direct link in TargetUrl, and provides
the IMDb or Kinopoisk title identifier in Id when recognized. Redirects are
decoded locally without another request
All network methods accept a CancellationToken
Comments use the website AJAX endpoint, so the complete media page is not downloaded again:
varcomments=awaitmedia.Comments.GetPageAsync(page:1);foreach(varcommentincomments.Items){Console.WriteLine($"{comment.Author}: {comment.Text}");}varcreated=awaitmedia.Comments.AddAsync("A detailed review that follows the website rules");varreply=awaitmedia.Comments.ReplyAsync(created.Id,"A reply to the published comment");awaitmedia.Comments.DeleteAsync(reply.Id);varlike=awaitmedia.Comments.ToggleLikeAsync(comments.Items[0].Id);varlikedBy=awaitmedia.Comments.GetLikeUsersAsync(comments.Items[0].Id);Comment creation, replies, and deletion require an authenticated account. The website does not expose comment editing, so the library does not emulate it by deleting and reposting content
varcatalog=awaitsession.Catalog.GetDirectoryAsync(newCatalogQuery(MediaCategory.Film,"comedy",2025,Best:true));varperson=awaitsession.People.GetAsync(media.Details.Cast[0]);varfranchiseDirectory=awaitsession.Franchises.GetPageAsync();varfranchise=awaitsession.Franchises.GetAsync(franchiseDirectory.Items[0]);vartrailer=awaitmedia.GetTrailerAsync();varrelatedMedia=awaitmedia.GetOtherPartsAsync();Catalog cards expose structured years, countries, genres, card rating, and trailer availability. Person pages include biography and grouped filmography, while franchise parts include their order, year, rating, media ID, and URL
For a lightweight home-screen feed and on-demand preview metadata, use the website's compact endpoints directly:
varnewest=awaitsession.Catalog.GetNewestSliderAsync(MediaCategory.Series);varpreview=awaitsession.Catalog.GetQuickContentAsync(newest[0]);Console.WriteLine(preview.Title);Console.WriteLine(preview.Description);GetNewestSliderAsync returns compact CatalogItem cards. Quick content is
loaded only when requested, so enumerating a slider does not cause an implicit
request for every item
The current aggregate HDRezka rating is loaded with the media page. An authenticated account can submit one integer score from 1 through 10:
Console.WriteLine($"{media.Rating.Value} from {media.Rating.Votes} votes");varupdatedRating=awaitmedia.RateAsync(9);Console.WriteLine($"{updatedRating.Value} from {updatedRating.Votes} votes");The website can reject repeated voting by the same account. A rejected vote
throws RatingException
For a movie:
varstream=awaitmedia.GetStreamAsync();varhdUrls=stream.GetUrls("720");foreach(varurlinhdUrls){Console.WriteLine(url);}The returned URLs can contain direct MP4 files and HLS playlists, while player metadata also exposes the default quality, default subtitle, timeline preview, and premium-content marker when the website returns them
The subscription tier is detected from the authenticated page:
varstate=awaitsession.GetAuthenticationStateAsync();Console.WriteLine(state.AccountTier);Console.WriteLine(state.IsPremium);Console.WriteLine(media.IsPremiumAccount);AccountTier.Unknown means that the website did not provide a recognizable
account token and is not treated as proof of Premium access
Premium translations remain visible through TranslationOptions, while automatic
selection skips them on a standard account and selecting one explicitly throws
PremiumRequiredException before the player request is sent
The player can return 1080p Ultra, 2K, and 4K entries to a standard
account together with protected URLs, but the library keeps those entries as
metadata but does not expose their URLs:
foreach(varqualityinstream.Qualities.Values){Console.WriteLine($"{quality.Name}: Premium={quality.RequiresPremium}, Available={quality.IsAvailable}");}varavailableUrls=stream.Videos;Videos contains only available qualities, while calling GetUrls("4K") without
confirmed Premium access throws PremiumRequiredException
For a series:
varstream=awaitmedia.GetStreamAsync(season:1,episode:5);A translation can be selected by ID or exact name:
varbyId=awaitmedia.GetStreamAsync(1,5,translation:"56");varbyName=awaitmedia.GetStreamAsync(1,5,translation:"Дубляж");Without an explicit translation, the configured priority is used with defaults
are 56, 105, and 111; translator 238 is non-preferred
media.PreferredTranslators.Clear();media.PreferredTranslators.Add(111);media.NonPreferredTranslators.Add(999);Load every episode stream from one season:
varprogress=newProgress<SeasonDownloadProgress>(value =>Console.WriteLine($"{value.Completed}/{value.Total}"));varstreams=awaitmedia.GetSeasonStreamsAsync(season:1,progress:progress,cancellationToken:cancellationToken);Episodes are loaded concurrently using ClientOptions.MaxConcurrentRequests.
Each failed episode is retried once, after which its result is null on
another failure. Set ignoreErrors: true to keep retrying until success or
cancellation
Data for one translator:
varinfo=awaitmedia.GetSeriesInfoAsync("56");Data for every translator:
varinfoByTranslator=awaitmedia.GetSeriesInfoAsync();Merged seasons and episodes:
varseasons=awaitmedia.GetEpisodesInfoAsync();foreach(varseasoninseasons){foreach(varepisodeinseason.Episodes){Console.WriteLine($"S{season.Number}E{episode.Number}: {episode.Title}");foreach(vartranslationinepisode.Translations){Console.WriteLine($" {translation.TranslatorId}: {translation.TranslatorName}");}}}Results are cached for the lifetime of the loaded Media instance, while loading one
stream or season queries only the selected translator and the all-translator
overloads perform the explicit catalog-wide aggregation
varstream=awaitmedia.GetStreamAsync(1,5);Console.WriteLine(string.Join(", ",stream.Subtitles.Languages));varenglish=stream.Subtitles.GetUrl("en");varbyTitle=stream.Subtitles.GetUrl("English");varfirst=stream.Subtitles.GetUrl(0);GetUrl(string) accepts either a language code or a subtitle title
Fast AJAX search:
varresults=awaitsession.SearchAsync("Film name");foreach(varresultinresults){Console.WriteLine($"{result.Title}: {result.Url} ({result.Rating})");}Full search:
varpage=awaitsession.SearchPageAsync("Film name",page:2);varall=awaitsession.SearchAllAsync("Film name",maximumPages:10);maximumPages is optional. The first page determines the available page count,
after which remaining pages are loaded concurrently and returned in page order
A standalone search client is also available:
usingvarsearch=newSearchClient("https://your-mirror.example");varresults=awaitsearch.FastSearchAsync("Film name");Account operations share the authenticated session:
varprofile=awaitsession.Account.GetProfileAsync();Console.WriteLine(profile.Username);Console.WriteLine(profile.AvatarUrl);Console.WriteLine(profile.Tier);Console.WriteLine(profile.ContinueWatchingCount);varcontinueWatching=awaitsession.Account.GetContinueWatchingAsync();varbookmarkFolders=awaitsession.Account.GetBookmarksAsync();Continue-watching entries expose the saved date, cover, media category, season, episode, translator, watched state, and remaining episode count when available
Bookmarks preserve user-created folders and return their media as CatalogItem
instances
Playback progress can be synchronized after loading a stream
usingvarmedia=awaitsession.GetAsync("/series/drama/66689-title.html");varstream=awaitmedia.GetStreamAsync(season:1,episode:4);awaitsession.Account.SavePlaybackProgressAsync(newPlaybackProgress(media.Id,stream.TranslatorId,stream.Season,stream.Episode,Position:TimeSpan.FromMinutes(18),Duration:TimeSpan.FromMinutes(52)));The library resolves streams but does not play them, so the application reports the current position when playback starts, pauses, seeks, or closes
Continue-watching and bookmark mutations use the same authenticated session
varentry=continueWatching[0];entry=awaitsession.Account.SetContinueWatchingWatchedAsync(entry,isWatched:true);varfolder=awaitsession.Account.CreateBookmarkFolderAsync("Watch later");awaitmedia.SetBookmarkAsync(folder.Id,isBookmarked:true);awaitsession.Account.RemoveContinueWatchingAsync(entry.Id);awaitsession.Account.DeleteBookmarkFolderAsync(folder.Id);Media.BookmarkFolderIds contains the selected sections from the loaded media
page, so SetBookmarkAsync sends no request when the requested state already
matches
Deleting a bookmark folder also deletes every bookmark it contains
Password and avatar changes use the same authenticated session:
awaitsession.Account.ChangePasswordAsync(currentPassword:"current-password",newPassword:"new-password");awaitusingvaravatar=File.OpenRead("avatar.png");varavatarResult=awaitsession.Account.SetAvatarAsync(avatar,"avatar.png");The website requires passwords with at least eight characters. Avatar upload
is confirmed for PNG and JPEG images with dimensions of at least 60 by 60
pixels. By default the library applies the largest centered square crop; pass
an AvatarCrop to select another square in original image coordinates
The four home-page sections and their category filters are available through
Catalog:
varlatest=awaitsession.Catalog.GetLatestAsync();varpopularSeries=awaitsession.Catalog.GetPopularAsync(MediaCategory.Series,page:2);varupcoming=awaitsession.Catalog.GetUpcomingAsync();varwatchingNow=awaitsession.Catalog.GetWatchingAsync();varnewReleases=awaitsession.Catalog.GetNewReleasesAsync();varannouncements=awaitsession.Catalog.GetAnnouncementsAsync();varshows=awaitsession.Catalog.GetShowsAsync();Every result contains the current page, detected total page count, and media cards with title, cover, category, details, and episode or release information
vardirectory=awaitsession.Collections.GetPageAsync();varfirstCollection=directory.Items[0];varcollection=awaitsession.Collections.GetAsync(firstCollection);foreach(varitemincollection.Items){Console.WriteLine($"{item.Title}: {item.Url}");}Both the collection directory and collection contents support one-based pagination
Credential-based login reproduces the website flow: it sends
POST /ajax/login/, stores the returned session cookies in a real
CookieContainer, and verifies the session against /favorites/
varoptions=newClientOptions();options.Headers["X-Custom-Header"]="value";options.Proxy=newWebProxy("http://127.0.0.1:8080");options.MaxConcurrentRequests=4;options.ResponseCacheDuration=TimeSpan.FromSeconds(15);options.MaxCachedResponses=128;options.SecurityTokenCacheDuration=TimeSpan.FromSeconds(30);usingvarsession=newClient("https://your-mirror.example",options);varlogin=awaitsession.LoginAsync("mail@example.com","password",rememberMe:true);if(!login.IsAuthenticated){thrownewInvalidOperationException("Login was not verified.");}varcurrent=awaitsession.GetAuthenticationStateAsync();varlogout=awaitsession.LogoutAsync();rememberMe: true maps to the website's login_not_save=0 behavior, while the
authentication result exposes cookie names for diagnostics, but never cookie
values
Existing authentication cookies can still be imported explicitly when restoring a previously saved session:
foreach(varcookieinAuthenticationCookies.Create(userId,passwordHash)){options.Cookies[cookie.Key]=cookie.Value;}When supplying your own HttpClient, configure its handler if you need a
proxy, while compressed responses are handled by the library and the injected
client is never disposed
Safe identical reads share an active request within one client, including when
ResponseCacheDuration is zero. Setting a positive duration also retains
successful responses for that period. Failed requests are never retained,
caller cancellation does not cancel work still awaited by another caller, and
session cookies are part of cache isolation. Successful mutations invalidate
potentially stale retained reads
The library does not run a browser and does not read a browser DOM. Every
operation uses HttpClient:
- JSON or compact HTML endpoints are used directly for login, player data, seasons, fast search, and comments
- regular pages are downloaded as HTML and parsed in memory with AngleSharp
- bulk page, translator, bookmark, and episode requests use limited asynchronous concurrency without creating dedicated threads
- identical safe reads and stream resolutions share active work instead of issuing duplicate requests
The library emits ActivitySource traces and Meter instruments under the
stable name exposed by Diagnostics.ActivitySourceName and
Diagnostics.MeterName. Available metric names are
hdrezka.http.request.duration, hdrezka.http.response.body.duration,
hdrezka.http.response.body.size, hdrezka.response.parse.duration, and
hdrezka.cache.request.count. Traces record the URL path but omit query values
Production code is compiled into one HDRezka.NET assembly, while logical
responsibilities remain separated by directories without introducing extra
projects or NuGet dependencies:
Client: client entry point, options, authentication state, and cookie helpersAccount: profile metadata and changes, continue-watching history, and bookmarksCatalog: home-page catalog sections and shared media cardsCollections: curated collection directory and contentComments: loading, creation, replies, and deletionMedia: media facade, internal ratings, streams, subtitles, translators, seasons, and episodesSearch: search client and result modelsExceptions: public library exceptionsAbstractions: internal contracts shared by the client and parsersDiagnostics: tracing and metrics without an additional telemetry dependencyHttp: HTTP transport,CookieContainer, response decompression, and safe request sharingScraping: AngleSharp page parsing and authentication page inspectionTranslators: automatic translator ordering and selection
All public types remain in the single HdRezka namespace regardless of their
feature directory
Library-specific failures derive from ApiException:
LoginRequiredExceptionLoginFailedExceptionAccountUpdateExceptionCommentOperationExceptionRatingExceptionPremiumRequiredExceptionCaptchaExceptionStreamFetchExceptionHttpExceptionParseException
Invalid method arguments use standard .NET exceptions such as
ArgumentException, ArgumentOutOfRangeException, and
InvalidOperationException
dotnet build HDRezka.NET.slnx --configuration Release
dotnet test HDRezka.NET.slnx --configuration Release
dotnet pack src/HDRezka.NET/HDRezka.NET.csproj \
--configuration Release \
--output artifactsThe live integration test is opt-in and does not store credentials:
HDREZKA_TEST_EMAIL="mail@example.com" \
HDREZKA_TEST_PASSWORD="password" \
HDREZKA_TEST_ORIGIN="https://your-mirror.example" \
dotnet test tests/HDRezka.NET.IntegrationTests \
--configuration Release \
--filter "Category=Live"