API Reference
ContentLanguage
Section titled “ContentLanguage”type ContentLanguage = 'sub' | 'dub' | 'raw';| Value | Meaning |
|---|---|
'sub' | Original audio with subtitles |
'dub' | Dubbed audio (usually English) |
'raw' | Original audio, no subtitles |
MediaCatalogType
Section titled “MediaCatalogType”type MediaCatalogType = 'ANIME' | 'MOVIE' | 'TV' | 'MANGA';Supported types include 'ANIME' and 'MANGA'.
type Urn = string;Every id flowing in or out of the SDK is a URN of shape
${providerId}:${rawId}. The first colon is the separator; the raw
portion is opaque and may itself contain colons or slashes. See the
URN helpers section for builders/parsers.
CallOptions
Section titled “CallOptions”interface CallOptions { /** Cancels the in-flight call. Threaded into fetch + rate limiter + retry. */ signal?: AbortSignal; /** Meta-layer only: throw on a missing episode instead of falling back. */ strictEpisodeMatching?: boolean; /** Meta-layer only: behaviour of the absolute-episode rescue. */ episodeAbsoluteMatching?: 'auto' | 'always' | 'never';}The canonical options bag — every public method in the SDK (content and metadata providers, mapping client) accepts an instance. Fields that aren’t meaningful at a particular layer are simply ignored there.
IMediaSearchResult
Section titled “IMediaSearchResult”Returned by provider.search().
interface IMediaSearchResult { id: Urn; // `${providerId}:${rawId}` — pass to fetchContentUnits title: string; thumbnailUrl?: string; catalogType: MediaCatalogType; providerId: string; // matches provider.id availableLanguages?: ContentLanguage[]; // omitted if unknown /** Year of release, when the provider exposes it. Used by the * metadata layer's fuzzy matcher as a discriminator. */ year?: number;}IContentUnit
Section titled “IContentUnit”Returned by provider.fetchContentUnits(). Represents a single episode or chapter in a unified, language-agnostic list: the caller picks a translation at resolveStream time.
interface IContentUnit { id: Urn; // pass to resolveStream title: string; number: number; // episode/chapter number (may be fractional, e.g. 1.5) availableLanguages?: ContentLanguage[]; // e.g. ['sub', 'dub']: omitted if unknown availableSubtitles?: ISubtitleAvailability[]; // optional, list-time metadata availableQualities?: IVideoPayload['quality'][]; // optional /** Per-episode metadata folded in by the metadata layer * (AniList `streamingEpisodes`, Jikan filler/recap flags). */ thumbnailUrl?: string; description?: string; airDate?: string; isFiller?: boolean; isRecap?: boolean;}ISubtitleTrack / ISubtitleAvailability
Section titled “ISubtitleTrack / ISubtitleAvailability”interface ISubtitleAvailability { language: string; // BCP-47 (e.g. 'en', 'pt-BR') label: string; // human-readable ('English') format?: 'vtt' | 'srt' | 'ass';}
interface ISubtitleTrack extends ISubtitleAvailability { url: string;}ISubtitleAvailability is the metadata-only shape used in list contexts; ISubtitleTrack adds the URL once resolved.
IUnitTracks
Section titled “IUnitTracks”Returned by the optional provider.fetchUnitTracks(). Lets a UI introspect tracks without paying the cost of a full stream resolution.
interface IUnitTracks { subtitles: ISubtitleTrack[]; qualities: IVideoPayload['quality'][]; headers?: Record<string, string>; // forwarded to the subtitle/stream fetcher when present}IVideoPayload
Section titled “IVideoPayload”A single playable stream.
interface IVideoPayload { sourceUrl: string; isHLS: boolean; // true → HLS manifest (.m3u8) quality: '1080p' | '720p' | '480p' | '360p' | 'auto'; language?: ContentLanguage; headers?: Record<string, string>; // forward to fetch/player (Referer, User-Agent) subtitles?: ISubtitleTrack[]; // external VTT tracks when the provider has them}SdkCache
Section titled “SdkCache”Minimal pluggable cache contract: bring whatever store you want (Map, Redis, SQLite, edge KV). Both methods may be sync or async; the SDK awaits either way.
interface SdkCache { get(key: string): unknown | Promise<unknown>; set(key: string, value: unknown): void | Promise<void>;}Keys are namespaced strings produced by startServer: search:<providerId>:<query>, content:<providerId>:<mediaId>, stream:<providerId>:<unitId>:<lang>, tracks:<providerId>:<unitId>:<lang>. get returns undefined for a miss; any other value (including null) is treated as a hit. Inspect the key prefix in your set to apply different TTLs per endpoint or refuse to cache (e.g. stream: if your provider hands out signed expiring URLs).
IMangaPayload
Section titled “IMangaPayload”interface IMangaPayload { imageUrls: string[]; headers?: Record<string, string>;}ResolvedMediaStream
Section titled “ResolvedMediaStream”Returned by provider.resolveStream(). A discriminated union: always check type before accessing the payload.
type ResolvedMediaStream = | { type: 'video'; streams: IVideoPayload[] } | { type: 'manga'; pages: IMangaPayload };Anime providers return type: 'video'; manga providers return type: 'manga'. The streams array on video results is sorted best-first by the provider.
HttpClient
Section titled “HttpClient”The shared HTTP transport. All providers and extractors accept one via constructor.
class HttpClient { constructor(config?: HttpClientConfig);}
interface HttpClientConfig { timeoutMs?: number; // default: 10000 (10s) proxyUrl?: string; // e.g. 'https://proxy.example.com' proxyType?: 'prepend' | 'query'; // default: 'prepend' proxyQueryParam?: string; // used when proxyType='query'; default: 'url' defaultHeaders?: Record<string, string>; /** Per-host token-bucket rate limits. Merged on top of DEFAULT_RATE_LIMITS * (AniList / Jikan / Kitsu / MALSync / Anify / arm-server). */ rateLimits?: PerHostRateLimits; defaultRateLimit?: RateLimitConfig; disableRateLimit?: boolean; /** Retry policy. Defaults to 3 attempts with exponential backoff on * 408/425/429/5xx and transient network errors. Honours Retry-After. */ retry?: RetryConfig | false; /** Pluggable transport. Defaults to CurlFallbackTransport on Node. */ transport?: HttpTransport;}Rate limiting + retry + AbortSignal
Section titled “Rate limiting + retry + AbortSignal”HttpClient.request composes three middlewares around the underlying
transport, each on by default:
- Per-host token-bucket rate limiter (
RateLimiter) with built-in policies for the catalogue APIs (DEFAULT_RATE_LIMITS). Re-acquired on every retry so a retry storm can’t blow past the configured cap. - Exponential-backoff retry (
withRetry) that honoursRetry-Afteron408/425/429/5xxresponses and transient network errors. ThrowsHttpRetryableErrorafter exhausting attempts. - AbortSignal composition: the caller’s
options.signaland the internal timeout signal are merged — either one aborts the in-flight request.
const ac = new AbortController();setTimeout(() => ac.abort(), 1500);await http.get('https://graphql.anilist.co', { signal: ac.signal });Pluggable transport
Section titled “Pluggable transport”HttpTransport is a one-method interface: fetch(url, init). Two
implementations ship:
CurlFallbackTransport(default on Node): triesfetchfirst, falls back tocurlviachild_process.execSyncon network error.FetchTransport: plainfetch. Use on Workers/Deno or in tests where the curl fallback is undesirable.
Bring your own (Undici dispatcher, Cloudflare-bypass proxy, in-process
test transport) by implementing the interface and passing it as
transport.
Methods
Section titled “Methods”get(url: string, options?: RequestInit): Promise<Response>post(url: string, body?: any, options?: RequestInit): Promise<Response>request(url: string, options?: RequestInit): Promise<Response>
// Convenience mutators (affect defaultHeaders)setCookie(name: string, value: string): voidsetUserAgent(userAgent: string): void
// Proxy internals (used by HlsUtils)getProxyUrl(): string | undefinedgetProxyType(): 'prepend' | 'query'getProxyQueryParam(): stringgetDefaultHeaders(): Record<string, string>requestUrl(url: string): string // applies proxy rewriting to a URLProxy modes
Section titled “Proxy modes”prepend: strips the protocol from the target and prepends the proxy base:
proxyUrl: 'https://proxy.example.com'target: 'https://cdn.site.com/video.m3u8'result: 'https://proxy.example.com/cdn.site.com/video.m3u8'query: passes the target as a query param:
proxyUrl: 'https://proxy.example.com/fetch'proxyQueryParam: 'url'result: 'https://proxy.example.com/fetch?url=https%3A%2F%2Fcdn.site.com%2Fvideo.m3u8'curl fallback (default Node transport)
Section titled “curl fallback (default Node transport)”When CurlFallbackTransport is in use and fetch throws (timeout, TLS
error, anti-bot rejection), the transport automatically retries the
request using curl via child_process.execSync. The fallback
synthesises a standard Response-shaped object, so callers see no
difference. Cookie persistence uses a per-client temp file.
URN helpers
Section titled “URN helpers”Every id flowing through the SDK is a URN of shape
${providerId}:${rawId}. Helpers live in utils/urn.ts:
buildUrn(providerId: string, rawId: string): UrnparseUrn(urn: string): { providerId: string; rawId: string }unwrapUrn(providerId: string, urn: string): stringstrictUnwrapUrn(providerId: string, urn: string): string // throws on mismatchisUrn(value: string, providerId?: string): boolean
// Typed catalogue URNs — for MAL/Kitsu where the integer ID isn't// globally unique across anime/manga.buildTypedUrn(providerId: string, kind: 'anime' | 'manga', rawId: string | number): UrnparseTypedUrn(providerId: string, urn: string): { kind?: 'anime' | 'manga'; rawId: string }strictUnwrapUrn throws on a prefix mismatch — use it in routing paths
where a wrong URN should fail loudly instead of being silently
misrouted (the server uses it on /meta/info).
BaseProvider
Section titled “BaseProvider”abstract class BaseProvider { abstract readonly id: string; abstract readonly supportedTypes: MediaCatalogType[];
constructor(protected http: HttpClient);
// Public API — URN-prefixed ids in and out. search(query: string, options?: CallOptions): Promise<IMediaSearchResult[]>; fetchContentUnits(mediaUrn: Urn, options?: CallOptions): Promise<IContentUnit[]>; resolveStream( unitUrn: Urn, language?: ContentLanguage, options?: CallOptions, ): Promise<ResolvedMediaStream>; fetchUnitTracks( unitUrn: Urn, language?: ContentLanguage, options?: CallOptions, ): Promise<IUnitTracks>;
get supportsUnitTracks(): boolean;
/** Optional: provider-native cross-source lookup. When a provider's * site indexes by AniList/MAL ID directly, implement this and * MappingClient will use it before the external/fuzzy fallbacks. */ lookupByMapping?( mappings: IMediaMappings, options?: CallOptions, ): Promise<string | null>;
/** Optional: MALSync `Sites` aliases this provider corresponds to. */ static readonly malsyncSites: readonly string[];
/** Optional: caps in-flight calls on this provider via FIFO semaphore. */ readonly maxConcurrency: number;
// Subclass surface — raw (non-URN) IDs. Subclasses implement these; // the public methods above wrap them with URN encoding + concurrency cap. protected abstract searchRaw( query: string, options?: CallOptions, ): Promise<IMediaSearchResult[]>; protected abstract fetchContentUnitsRaw( rawMediaId: string, options?: CallOptions, ): Promise<IContentUnit[]>; protected abstract resolveStreamRaw( rawUnitId: string, language?: ContentLanguage, options?: CallOptions, ): Promise<ResolvedMediaStream>; protected fetchUnitTracksRaw?( rawUnitId: string, language?: ContentLanguage, options?: CallOptions, ): Promise<IUnitTracks>;}Metadata layer
Section titled “Metadata layer”IMediaMetadata
Section titled “IMediaMetadata”The full normalized metadata record returned by
metaProvider.fetchMediaInfo. Includes everything the catalogue ships:
interface IMediaMetadata { id: Urn; // e.g. 'anilist:1' providerId: string; // 'anilist' | 'mal' | 'kitsu' catalogType: MediaCatalogType; title: IMediaTitle; // romaji / english / native / userPreferred description?: string; cover?: IMediaImage; banner?: string; status?: MediaStatus; format?: MediaFormat; episodeCount?: number; chapterCount?: number; durationMinutes?: number; genres?: string[]; tags?: string[]; studios?: string[]; year?: number; season?: MediaSeason; startDate?: string; // ISO 8601 yyyy-mm-dd endDate?: string; score?: number; // normalized 0–100 trailer?: string; isAdult?: boolean; synonyms?: string[]; mappings?: IMediaMappings; // cross-source: anilist / mal / kitsu / anidb / … relations?: IMediaRelation[]; characters?: IMediaCharacter[]; // includes voiceActors[] staff?: IMediaStaff[]; recommendations?: IMediaRecommendation[]; externalLinks?: IMediaExternalLink[]; streamingEpisodes?: IStreamingEpisode[]; // per-episode title/thumbnail}BaseMetadataProvider
Section titled “BaseMetadataProvider”abstract class BaseMetadataProvider { abstract readonly id: string; // 'anilist' | 'mal' | 'kitsu' abstract readonly supportedTypes: MediaCatalogType[];
search(query: string, options?: CallOptions): Promise<IMetaSearchResult[]>; fetchMediaInfo(metaUrn: Urn, options?: CallOptions): Promise<IMediaMetadata>; fetchContentUnits( metaUrn: Urn, contentProvider: BaseProvider, options?: CallOptions, ): Promise<IContentUnit[]>; resolveStream( metaUrn: Urn, episodeNumber: number, contentProvider: BaseProvider, language?: ContentLanguage, options?: CallOptions, ): Promise<ResolvedMediaStream>; fetchUnitTracks( metaUrn: Urn, episodeNumber: number, contentProvider: BaseProvider, language?: ContentLanguage, options?: CallOptions, ): Promise<IUnitTracks>; browse(kind: BrowseKind, options?: BrowseOptions): Promise<IMetaSearchResult[]>; supportsBrowseKind(kind: BrowseKind): boolean;
/** Walks PREQUEL relations to compute a season offset for multi-season * titles where the meta record's per-season numbering misaligns with * the content provider's continuous list. */ computeAbsoluteEpisodeOffset(metaUrn: Urn, options?: CallOptions): Promise<number>;}
type BrowseKind = 'trending' | 'popular' | 'seasonal' | 'top';Concrete providers: AnilistMeta, MalMeta, KitsuMeta.
MappingClient
Section titled “MappingClient”Resolves a metadata record onto a content provider’s raw media ID via a
four-step waterfall: SdkCache → provider.lookupByMapping →
MALSync/Anify/arm-server (raced in parallel) → fuzzy title search.
class MappingClient { constructor(http: HttpClient, options?: MappingClientOptions);
resolveProviderMediaId( metadata: IMediaMetadata, contentProvider: BaseProvider, options?: CallOptions, ): Promise<MappingResolution | null>;}
interface MappingResolution { providerId: string; rawMediaId: string; matchedTitle: string; method: 'cached' | 'provider' | 'malsync' | 'anify' | 'arm' | 'fuzzy'; similarity?: number;}The fuzzy matcher uses composite similarity (Sørensen–Dice + token
Jaccard + prefix score) with year and catalogType discriminators and an
optional episode-count cross-check for borderline matches. The metadata
record is never mutated; results land in the optional SdkCache keyed
by mapping:${metaProvider}:${metaNativeId}:${contentProvider}.
BaseExtractor
Section titled “BaseExtractor”abstract class BaseExtractor { abstract readonly id: string; constructor(protected http: HttpClient); abstract extract(embedUrl: string): Promise<IVideoPayload[]>;}Extractors return an empty array (never throw) when they cannot handle the given URL.
Extractors
Section titled “Extractors”BloggerExtractor
Section titled “BloggerExtractor”Extracts googlevideo.com MP4 URLs from Blogger video embeds.
class BloggerExtractor extends BaseExtractor { readonly id = 'blogger'; static matches(url: string): boolean; // true for blogger.com/video.g?token=... extract(embedUrl: string): Promise<IVideoPayload[]>;}Flow:
- Fetch the embed page; extract
FdrFJe(session ID) andcfb2h(build hash). - POST to
/_/BloggerVideoPlayerUi/data/batchexecutewith theWcwnYdRPC. - Parse the response: find the stream array inside the
wrb.fr/WcwnYdenvelope; preferitag=22(720p) overitag=18(360p). - Falls back to a raw regex scan for
googlevideo.comURLs if structured parsing yields nothing.
Returned URLs require { Referer: 'https://www.blogger.com/' } headers.
Mp4UploadExtractor
Section titled “Mp4UploadExtractor”Extracts direct MP4 URLs from mp4upload.com embed pages.
class Mp4UploadExtractor extends BaseExtractor { readonly id = 'mp4upload'; static matches(url: string): boolean; // true for mp4upload.com URLs extract(embedUrl: string): Promise<IVideoPayload[]>;}Parses the player.src({ src: "https://...video.mp4" }) line from the embed HTML. Returned URLs require { Referer: 'https://mp4upload.com/' }.
GenericHlsExtractor
Section titled “GenericHlsExtractor”Best-effort extractor that scans any embed page for a .m3u8 or .mp4 URL.
class GenericHlsExtractor extends BaseExtractor { readonly id = 'generic-hls'; extract(embedUrl: string): Promise<IVideoPayload[]>;}Prefers .m3u8 over .mp4. Handles JSON-embedded URLs with escaped slashes. Cannot decrypt obfuscated players (filemoon, streamwish, ok.ru). Returns [] if nothing matches.
VidstreamingExtractor
Section titled “VidstreamingExtractor”Handles vidstreaming.io / gogoplay embeds with AES-CBC decryption.
class VidstreamingExtractor extends BaseExtractor { readonly id = 'vidstreaming'; extract(embedUrl: string): Promise<IVideoPayload[]>;}Flow:
- Fetch the embed page. Extract encryption key, IV, and decryption key from
container-N/videocontent-Nclass names. - Decrypt the
data-valueattribute with AES-CBC using the encryption key. - Encrypt the
idquery param with AES-CBC. - GET
{host}/encrypt-ajax.php?{decrypted_data}&id={encrypted_id}&alias={contentId}withX-Requested-With: XMLHttpRequest. - Decrypt the
datafield of the JSON response with AES-CBC using the decryption key. - Parse the resulting JSON:
sourceandsource_bkarrays each contain{ file, label }entries.
HlsUtils
Section titled “HlsUtils”class HlsUtils { static rewriteManifest(manifestText: string, playlistUrl: string, httpClient: HttpClient): string;}Rewrites every URI in an .m3u8 manifest to route through the HttpClient’s configured proxy:
- Plain URI lines (chunks, sub-playlists)
URI="..."attributes inside#EXT-X-KEY,#EXT-X-MAP, and similar tags- Relative URIs are first resolved to absolute using
playlistUrl
Returns the manifest unchanged if no proxy is configured on the client.
DomRegistry
Section titled “DomRegistry”class DomRegistry { static register(customParser: IDomParser): void; static getParser(): IDomParser; static parse(html: string): IDomElement;}Global singleton parser used by all providers that scrape HTML. The default is BrowserDomParser, which wraps globalThis.DOMParser.
linkedom is registered automatically on import in Node environments: no setup is needed. DomRegistry.register() is only needed when you want to swap in a fully custom parser.
Custom parser:
import { DomRegistry, IDomParser, IDomElement } from 'anime-sdk';
class MyParser implements IDomParser { parse(html: string): IDomElement { /* ... */ }}
DomRegistry.register(new MyParser());IDomElement / IDomParser
Section titled “IDomElement / IDomParser”interface IDomParser { parse(html: string): IDomElement;}
interface IDomElement { querySelector(selector: string): IDomElement | null; querySelectorAll(selector: string): IDomElement[]; getAttribute(name: string): string | null; readonly textContent: string | null; readonly outerHTML: string; readonly innerHTML: string;}startServer
Section titled “startServer”function startServer(options: ServerOptions): http.Server;
interface ServerOptions { providers: BaseProvider[]; /** Metadata providers exposed under `/meta/*`. */ metaProviders?: BaseMetadataProvider[]; port?: number; // default: 3000 auth?: { token: string }; proxy?: boolean; /** Explicit proxy base. When omitted, derived from each request's * Host header (+ X-Forwarded-Proto) — so the SDK works behind * reverse proxies without configuration. */ proxyBase?: string; /** When set, /proxy requires an HMAC-SHA256 `sig` query parameter * on every request. The server signs URLs it emits automatically. */ proxySignSecret?: string; /** Suffix-matched SSRF allowlist. Targets outside the list return 403. */ proxyAllowedHosts?: string[]; cache?: SdkCache;}Launches a Node http.Server. Returns the server instance so you can call server.close(). All requests must be GET; all responses are JSON. See HTTP Server for full route documentation and Stream Proxy for the proxy behaviour.
Routes:
- Content provider:
/search,/content,/stream,/tracks - Metadata layer:
/meta/search,/meta/info,/meta/content,/meta/stream,/meta/tracks,/meta/browse - Discovery:
/health,/openapi.json - Downloads:
/download/video,/download/video/progress,/download/video/file,/download/manga/page,/download/manga/chapter,/download/manga/chapter/progress,/download/manga/chapter/file - Proxy (when
proxy: true):/proxy
Download utilities
Section titled “Download utilities”Functions for saving resolved streams to disk. Requires Node 20+; ffmpeg on PATH for HLS video downloads only.
downloadVideo
Section titled “downloadVideo”function downloadVideo( streams: IVideoPayload | IVideoPayload[], outputPath: string, options?: DownloadVideoOptions,): Promise<DownloadVideoResult>;Downloads an anime episode to a .mp4 file. Accepts a single stream or an array: if an array, each candidate is tried in order until one succeeds. Throws with a combined error message if all fail.
- HLS streams: downloads segments, strips PNG-wrapped bytes, concatenates into a
.tsfile, then callsffmpeg -c copyto mux to MP4. - Direct MP4: streams to disk via
fetch.
interface DownloadVideoOptions { onProgress?: (info: { phase: string; detail?: string }) => void; timeoutMs?: number; // default 300_000 (5 min)}
interface DownloadVideoResult { outputPath: string; stream: IVideoPayload; // the candidate that succeeded fileSize: number; // bytes}Progress phase values: 'resolving' → 'downloading' → 'muxing' → 'complete'.
downloadMangaPage
Section titled “downloadMangaPage”function downloadMangaPage( pages: IMangaPayload, pageIndex: number, outputDir: string, options?: DownloadMangaPageOptions,): Promise<DownloadMangaPageResult>;Downloads a single manga page. The filename is auto-generated as page_<NNN><ext> where the extension is inferred from the Content-Type header.
interface DownloadMangaPageOptions { headers?: Record<string, string>; // override the headers on IMangaPayload timeoutMs?: number; // default 30_000}
interface DownloadMangaPageResult { outputPath: string; pageIndex: number; fileSize: number; contentType: string;}downloadMangaChapter
Section titled “downloadMangaChapter”function downloadMangaChapter( pages: IMangaPayload, outputPath: string, options?: DownloadMangaChapterOptions,): Promise<DownloadMangaChapterResult>;Downloads all pages and packages them as an uncompressed .zip archive (STORE method: images are already compressed). No external dependencies.
interface DownloadMangaChapterOptions { onProgress?: (info: { downloaded: number; total: number }) => void; timeoutMs?: number; // per page; default 30_000}
interface DownloadMangaChapterResult { outputPath: string; pageCount: number; fileSize: number; // total ZIP size in bytes}HLS helpers
Section titled “HLS helpers”Low-level utilities exported for custom pipelines:
// Parse variant playlist URLs from a master playlistparseHlsMaster(content: string, baseUrl: string): string[]
// Parse segment descriptors from a media playlistparseHlsSegments(content: string, baseUrl: string): Array<{ url: string; duration: number }>
// Infer file extension from Content-TypedetectImageExtension(contentType: string): string
// CRC-32 checksum (used by the ZIP writer)crc32(buf: Buffer): number
// Build an uncompressed ZIP buffercreateZipBuffer(entries: Array<{ filename: string; data: Buffer }>): BufferSubtitle utilities
Section titled “Subtitle utilities”Exported helpers used internally by AnimeParadiseProvider and startServer; available for custom providers and self-hosted setups.
// Normalise an arbitrary array of {src|url, label, type|format} entries into// ISubtitleTrack[]. Non-http(s) entries (Drive IDs, etc.) are dropped.normalizeSubtitleEntries(entries: unknown): ISubtitleTrack[]
// Wrap a subtitle URL through the SDK's /proxy endpoint. Forces// Content-Type: text/vtt on VTT files so browsers parse them correctly.proxifySubtitleUrl( proxyBase: string, track: ISubtitleTrack, options?: { headers?: Record<string, string>; contentType?: string }): string
// BCP-47 inference from a human label (best-effort).labelToBcp47(label: string): stringCrypto utilities
Section titled “Crypto utilities”Exported for use in custom providers.
// AES-CBC decrypt: returns plaintext stringaesDecrypt(ciphertextBase64: string, keyStr: string, ivStr: string): Promise<string>
// AES-CBC encrypt: returns base64 stringaesEncrypt(plaintext: string, keyStr: string, ivStr: string): Promise<string>
// SHA-256 hashsha256(text: string): Promise<Uint8Array>
// AES-CTR decrypt (used by AllmangaProvider)aesDecryptCtr(ciphertext: Uint8Array, key: Uint8Array, iv: Uint8Array): Promise<Uint8Array>All functions use globalThis.crypto.subtle (available in Node 20+ and all modern browsers).