mirror of
https://gitlab.com/vyla-entertainment/documentation.git
synced 2026-11-14 22:47:26 -05:00
huge rewrite
This commit is contained in:
@@ -1,179 +1,139 @@
|
||||
# Contributing to Sources
|
||||
# Contributing a Source Provider
|
||||
|
||||
<p class="page-description">Help maintain, fix broken scrapers, and add new video or download providers to the Vyla SDK.</p>
|
||||
<p class="page-description">Complete technical guide for authoring, testing, and contributing a new video scraping resolver to the Vyla SDK.</p>
|
||||
|
||||
Third-party streaming and scraping endpoints frequently change their DOM layout, API endpoints, tokens, and CDN protections. Community contributions are vital to keeping providers operational.
|
||||
## Overview
|
||||
|
||||
The Vyla SDK maintains its high reliability through a distributed collection of independent provider scrapers located in `src/sources/`. We welcome contributions of new sources that expand coverage for movies, television, and international content.
|
||||
|
||||
---
|
||||
|
||||
### Thank You
|
||||
## Scraper Architecture
|
||||
|
||||
If you’re a developer or just want to help test and fix sources, feel free to contribute! If you get a source working, send me the updated version.
|
||||
Every provider module is a self-contained ES Module adhering to a single exported contract:
|
||||
|
||||
As a thank-you, contributors will receive the Contributor role and credit directly in the code for their contribution, along with a custom message of your choice and your Discord username.
|
||||
|
||||
Any help is appreciated! ❤️
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 1. Clone the repository
|
||||
|
||||
Clone the SDK repository to your local machine:
|
||||
|
||||
```bash
|
||||
git clone https://gitlab.com/vyla-entertainment/sdk.git
|
||||
cd sdk
|
||||
```typescript
|
||||
export async function getStream(args: StreamArgs): Promise<StreamResult | { allUrls: StreamResult[] }>
|
||||
```
|
||||
|
||||
### 2. Install dependencies
|
||||
### The `args` Object
|
||||
When your scraper's `getStream()` is invoked, the SDK passes a normalized `args` context containing:
|
||||
|
||||
Ensure you are running Node.js 18 or newer:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 3. Environment configuration
|
||||
|
||||
Copy the example environment file and add your TMDB API key:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Open `.env` and set your key:
|
||||
|
||||
```ini
|
||||
TMDB_API_KEY=your_actual_tmdb_api_key_here
|
||||
```
|
||||
| Field | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `id` | `string \| number` | The TMDB numeric media ID or string IMDb ID. |
|
||||
| `s` | `number \| null` | Season number for television episodes (or `null` for movies). |
|
||||
| `e` | `number \| null` | Episode number for television episodes (or `null` for movies). |
|
||||
| `clientIP` | `string \| null` | The viewer's client IP address (if passed from an upstream proxy). |
|
||||
| `fetch` | `Function` | Node.js fetch wrapper pre-configured with timeout controls. |
|
||||
| `imdbId` | `string \| null` | Automatically resolved IMDb ID (if TMDB API key is configured). |
|
||||
| `title` | `string \| null` | Verified media title string. |
|
||||
| `year` | `number \| null` | Media release year. |
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
## Step-by-Step Implementation Guide
|
||||
|
||||
- `src/sources/`: Scraper implementations for each provider.
|
||||
- `src/manga/`: Manga catalog search and reader page resolvers.
|
||||
- `src/config.js`: Source registry containing timeouts, retries, headers, and proxy flags.
|
||||
- `src/downloads/downloads.js`: Downloader resolver extracting direct MP4 streams.
|
||||
- `src/subtitles.js`: Subtitle mirror aggregation endpoints.
|
||||
- `test/index.js`: Test runner for testing streams, subtitles, downloads, and manga.
|
||||
|
||||
---
|
||||
|
||||
## Testing Sources
|
||||
|
||||
Testing is driven by `test/index.js`. Open the file and update `CONFIG` to target the source you are fixing or adding:
|
||||
### Step 1: Create the Scraper Module
|
||||
Create a new file in `src/sources/<source_name>.js`:
|
||||
|
||||
```javascript
|
||||
const CONFIG = {
|
||||
DEBUG: true,
|
||||
const BASE_URL = 'https://provider-domain.example.com';
|
||||
|
||||
MOVIE_ID: "936075",
|
||||
TV_ID: "37854",
|
||||
TV_SEASON: "1",
|
||||
TV_EPISODE: "1",
|
||||
STREAM_SOURCE: "vidrock",
|
||||
|
||||
TEST_SUBTITLE: false,
|
||||
TEST_DOWNLOAD: false,
|
||||
TEST_MOVIE: true,
|
||||
TEST_TV: true,
|
||||
|
||||
EXTRA_SDK_PARAMS: {}
|
||||
const DEFAULT_HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
'Referer': `${BASE_URL}/`,
|
||||
'Origin': BASE_URL,
|
||||
};
|
||||
```
|
||||
|
||||
Run the test suite:
|
||||
export async function getStream({ id, s, e, imdbId }) {
|
||||
const isTv = s != null && e != null;
|
||||
const endpoint = isTv
|
||||
? `${BASE_URL}/api/tv/${id}/${s}/${e}`
|
||||
: `${BASE_URL}/api/movie/${id}`;
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
const response = await fetch(endpoint, {
|
||||
headers: DEFAULT_HEADERS
|
||||
});
|
||||
|
||||
When `DEBUG: true` is enabled, all outbound fetch calls, headers, request payloads, and raw responses will be logged to your terminal and saved to `test/debug-output.json`.
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upstream returned HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
---
|
||||
const data = await response.json();
|
||||
if (!data.streamUrl) {
|
||||
throw new Error('No stream found in upstream response');
|
||||
}
|
||||
|
||||
## Fixing or Adding a Source
|
||||
|
||||
### 1. Source Resolver
|
||||
|
||||
All source files are located in `src/sources/`. Each module must export an asynchronous `getStream` function:
|
||||
|
||||
```javascript
|
||||
export async function getStream({ id, s, e, clientIP, audio, sdk, config }) {
|
||||
const isTv = Boolean(s && e);
|
||||
const mediaId = id;
|
||||
|
||||
const streamUrl = "https://example-cdn.com/stream/playlist.m3u8";
|
||||
|
||||
return {
|
||||
url: streamUrl,
|
||||
quality: "auto",
|
||||
isM3U8: true,
|
||||
headers: {
|
||||
Referer: "https://example-provider.to/",
|
||||
Origin: "https://example-provider.to"
|
||||
},
|
||||
allUrls: [
|
||||
{
|
||||
url: streamUrl,
|
||||
quality: "1080p",
|
||||
isM3U8: true
|
||||
}
|
||||
]
|
||||
};
|
||||
return {
|
||||
url: data.streamUrl,
|
||||
headers: {
|
||||
...DEFAULT_HEADERS,
|
||||
'Referer': data.referer || DEFAULT_HEADERS.Referer
|
||||
},
|
||||
server: 'MySource-Primary',
|
||||
quality: data.quality || '1080p',
|
||||
type: data.streamUrl.includes('.m3u8') ? 'hls' : 'mp4'
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Registering in `src/config.js`
|
||||
---
|
||||
|
||||
Add or update the source entry inside the `SOURCES` array in `src/config.js`:
|
||||
### Step 2: Register in `src/config.js`
|
||||
|
||||
Add your provider configuration to the `SOURCES` array in `src/config.js`:
|
||||
|
||||
```javascript
|
||||
{
|
||||
key: 'mysource',
|
||||
label: 'MySource',
|
||||
label: 'My Source',
|
||||
sourceFile: 'mysource',
|
||||
proxyParam: 'ms',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /cdn\.mysource\.to/i,
|
||||
headers: {
|
||||
Referer: 'https://mysource.to/',
|
||||
Origin: 'https://mysource.to',
|
||||
},
|
||||
}],
|
||||
timeout: 12000,
|
||||
jitter: 400,
|
||||
retries: 1,
|
||||
multiUrl: false,
|
||||
skipProxy: false,
|
||||
skipVerify: false
|
||||
}
|
||||
```
|
||||
|
||||
- `skipProxy`: Set to `true` if the stream plays directly in client players without server-side proxying.
|
||||
- `multiUrl`: Set to `true` if the scraper provides multiple resolutions or fallback mirrors.
|
||||
- `cdnHeaders`: Include custom headers required by the upstream CDN to avoid HTTP 403 Forbidden responses.
|
||||
#### Configuration Field Guidelines:
|
||||
- `key`: Lowercase alphanumeric identifier without spaces (e.g. `vidlink`, `cinecat`).
|
||||
- `label`: Clean display title shown in UI menus.
|
||||
- `proxyParam`: Unique 2-to-4 character query flag used by the Stream API HLS proxy (must not collide with existing flags).
|
||||
- `timeout`: Maximum execution budget before aborting (typically 8,000ms–15,000ms).
|
||||
- `jitter`: Random delay ceiling (typically 300ms–500ms) to avoid server burst locks.
|
||||
|
||||
---
|
||||
|
||||
## Download Providers
|
||||
### Step 3: Local Testing
|
||||
|
||||
Downloads are handled automatically by `src/downloads/downloads.js`. The downloader queries active sources that have `skipProxy: true` enabled and inspects returned streams:
|
||||
Run your scraper against known TMDB titles using the test runner:
|
||||
|
||||
- URLs ending in `.mp4` or containing `.mp4` queries are automatically extracted.
|
||||
- `.m3u8` streams are filtered out because they are HLS segment playlists rather than direct file downloads.
|
||||
- If you are implementing a provider that supplies raw MP4 video files, return them in `allUrls` or `url` with `skipProxy: true` in `src/config.js`.
|
||||
```bash
|
||||
# Test a known movie (The Dark Knight - TMDB 155)
|
||||
node test/index.js mysource 155
|
||||
|
||||
To verify download resolution in `test/index.js`, toggle:
|
||||
|
||||
```javascript
|
||||
TEST_DOWNLOAD: true
|
||||
# Test a known TV episode (Breaking Bad S01E01 - TMDB 1396)
|
||||
node test/index.js mysource 1396 1 1
|
||||
```
|
||||
|
||||
Verify that:
|
||||
1. A valid URL is printed.
|
||||
2. The `type` is correctly identified as `'hls'` or `'mp4'`.
|
||||
3. Required upstream headers (`Referer`, `Origin`, `User-Agent`) are included.
|
||||
4. Testing an invalid ID throws a clean error without crashing the process.
|
||||
|
||||
---
|
||||
|
||||
## Submitting Pull Requests
|
||||
## Contribution Rules & Checklist
|
||||
|
||||
1. Verify that your updated source passes tests for both movies (`TEST_MOVIE: true`) and TV shows (`TEST_TV: true`).
|
||||
2. Ensure you have not committed your `.env` file or personal TMDB API keys.
|
||||
3. Push your branch and open a Merge Request on GitLab.
|
||||
Before submitting a Pull Request to the GitLab repository:
|
||||
|
||||
- [ ] Code is formatted with clean ES Module syntax.
|
||||
- [ ] No hardcoded personal API keys or credentials.
|
||||
- [ ] Provider does not execute external untrusted `eval()` or un-sanitized script execution.
|
||||
- [ ] Upstream stream candidate URLs must be verified for playback.
|
||||
- [ ] Errors are thrown with informative messages (`throw new Error(...)`).
|
||||
|
||||
@@ -1,500 +1,83 @@
|
||||
# SDK Reference
|
||||
# Vyla SDK (`@vyla-entertainment/sdk`)
|
||||
|
||||
<p class="page-description">Scrape video streams, fetch subtitles, search manga catalogs, resolve chapter pages, retrieve download links, and monitor provider health directly from your Node.js application.</p>
|
||||
<p class="page-description">The official Node.js scraping and resolver library for movies, television series, subtitles, downloads, and manga.</p>
|
||||
|
||||
The **Vyla SDK** (`@vyla-entertainment/sdk`) is a server-side JavaScript/TypeScript package that provides direct access to Vyla's stream resolvers, health probes, download resolvers, and subtitle providers.
|
||||
<img class="vyla-banner" src="/src/assets/images/devices.webp" alt="Vyla SDK developer tools and multi-platform support" loading="lazy" decoding="async" fetchpriority="low" />
|
||||
|
||||
By embedding the SDK directly into your backend application, provider resolution executes in your own environment. It is an alternative to operating the Stream API as a separate process: your application owns its request boundary, caching, authorization, timeouts, and response shape.
|
||||
## Overview
|
||||
|
||||
:::callout-note
|
||||
The SDK can be used independently of the self-hosted Stream API. If you prefer to embed Vyla capabilities in an existing Node.js application rather than run a separate HTTP service, begin with the [SDK quickstart](/sdk/vyla-sdk/quickstart).
|
||||
:::
|
||||
`@vyla-entertainment/sdk` is the core media resolver engine powering the entire Vyla ecosystem. Available as a pure ES Module for Node.js environments, the SDK aggregates over 60 streaming providers, extracts multi-language subtitle tracks, discovers direct download packages, and interfaces with manga archives.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Direct-to-Client Playback**: Scraped streams link directly to source CDNs. No proxy server overhead required unless specified by provider headers.
|
||||
- **Manga & Comic Aggregation**: Search title catalogs, inspect volume/chapter indexes, and resolve full reader image sets.
|
||||
- **Built-In Health Probing**: Monitor latency and uptime per provider with built-in health check utilities.
|
||||
- **Automated Anime Sub/Dub Routing**: Automatically configures audio track requirements (`sub` vs `dub`) based on provider naming conventions.
|
||||
- **Multi-Source Subtitle Aggregation**: Merges subtitle tracks from multiple redundant subtitle mirrors into a unified VTT/SRT format.
|
||||
- **Metadata Fallbacks**: Automatically maps TMDB IDs to external IDs (such as IMDB and AniList) for smooth stream lookups.
|
||||
- **Self-Contained**: No need to deploy a separate API server - all streaming and reading functionality runs within your existing Node.js application.
|
||||
Whether you are building a custom backend API, an automated scraper pipeline, a command-line tool, or an embeddable player backend, the SDK provides a clean, typed JavaScript and TypeScript interface.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Install the package into your Node.js backend using your package manager of choice:
|
||||
Install the package via npm, pnpm, or yarn:
|
||||
|
||||
:::code-group
|
||||
```bash [npm]
|
||||
```bash
|
||||
npm install @vyla-entertainment/sdk
|
||||
```
|
||||
```bash [pnpm]
|
||||
pnpm install @vyla-entertainment/sdk
|
||||
```
|
||||
```bash [yarn]
|
||||
yarn add @vyla-entertainment/sdk
|
||||
```
|
||||
:::
|
||||
|
||||
> **Note**: Node.js **v18+** is required due to modern standard `fetch` and `AbortSignal.timeout` usage.
|
||||
### System Requirements
|
||||
- **Node.js**: v20.0.0 or higher (ESM native)
|
||||
- **TMDB API Key (Optional but Recommended)**: Required for sources that query metadata by IMDb ID or episode titles. Obtain a free key from [The Movie Database](https://www.themoviedb.org/).
|
||||
|
||||
---
|
||||
|
||||
## Initialization
|
||||
## SDK Capabilities
|
||||
|
||||
Initialize the `VylaSDK` client by supplying your **TMDB API Key**. The key resolves media metadata, validates movie/TV show IDs, and translates TMDB IDs into AniList IDs for anime sources.
|
||||
| Functional Engine | Primary Methods | Output Data & Features |
|
||||
| :--- | :--- | :--- |
|
||||
| **Stream Engine** | `getStream()`, `getSources()`, `probeSource()` | Direct MP4 and adaptive HLS (`.m3u8`) links, quality tags, and verified playback flags. |
|
||||
| **Subtitle Parser** | `getSubtitles()` | Multi-language WebVTT (`.vtt`) and SubRip (`.srt`) track descriptors with language labels. |
|
||||
| **Download Engine** | `getDownloads()` | Direct high-speed download links, file sizes in bytes, and container specifications (MKV/MP4). |
|
||||
| **Manga Engine** | `searchManga()`, `getManga()`, `getMangaChapter()` | Titles, covers, chapter indexes, and image page URLs bypassing TLS fingerprint protection. |
|
||||
|
||||
You may optionally provide a pengu.uk manifest URL to enable the Pengu source.
|
||||
### 1. Video Stream Resolution
|
||||
Queries active providers for direct MP4 and adaptive HLS streaming links based on TMDB movie or TV identifiers.
|
||||
|
||||
### 2. Multi-Language Subtitles
|
||||
Extracts clean `.vtt` and `.srt` subtitle tracks from multiple open subtitle providers, categorized by language label.
|
||||
|
||||
### 3. Media Downloads
|
||||
Resolves high-speed direct media download packages (including MKV containers with file size metadata).
|
||||
|
||||
### 4. Manga Discovery & Reading
|
||||
Features a dedicated manga scraping module leveraging `node-tls-client` with Chrome 131 TLS fingerprinting to search manga, view chapters, and retrieve high-resolution page assets.
|
||||
|
||||
---
|
||||
|
||||
## Quick Example
|
||||
|
||||
```javascript
|
||||
import VylaSDK from "@vyla-entertainment/sdk";
|
||||
import VylaSDK from '@vyla-entertainment/sdk';
|
||||
|
||||
const sdk = new VylaSDK({
|
||||
tmdbApiKey: process.env.TMDB_API_KEY,
|
||||
|
||||
penguManifest: "" // Optional, get from pengu.uk
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Methods
|
||||
|
||||
### `getStream(key, id, s?, e?, clientIP?)`
|
||||
|
||||
Scrapes video stream URLs and playability headers for a specified provider key.
|
||||
|
||||
```javascript
|
||||
// Fetch a movie stream (TMDB ID: 155 - The Dark Knight)
|
||||
const movieStream = await sdk.getStream("sourcename", "155");
|
||||
|
||||
// Fetch a TV show episode (TMDB ID: 1399, Season 1, Episode 1)
|
||||
const tvStream = await sdk.getStream("sourcename", "1399", 1, 1);
|
||||
|
||||
// Pass client IP for providers that use geo-verification
|
||||
const geoStream = await sdk.getStream("sourcename", "155", null, null, "203.0.113.195");
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `key` | `string` | **Yes** | - | Unique key identifier for the provider (e.g. `"sourcename"`). |
|
||||
| `id` | `string` | **Yes** | - | TMDB media ID (e.g., `"155"`). |
|
||||
| `s` | `string \| number` | **No** | `null` | Season number (required for TV shows). |
|
||||
| `e` | `string \| number` | **No** | `null` | Episode number (required for TV shows). |
|
||||
| `clientIP` | `string` | **No** | `null` | End-user IP address forwarded for geo-restricted scrapers. |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "https://example-cdn.com/hls/manifest.m3u8",
|
||||
"quality": "auto",
|
||||
"isM3U8": true,
|
||||
"headers": {
|
||||
"Referer": "https://example.com/",
|
||||
"Origin": "https://example.com"
|
||||
},
|
||||
"allUrls": [
|
||||
{
|
||||
"url": "https://example-cdn.com/hls/manifest.m3u8",
|
||||
"quality": "1080p",
|
||||
"isM3U8": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getSubtitles(id, s?, e?)`
|
||||
|
||||
Fetches subtitle files aggregated across multiple subtitle backend mirrors (`v1`, `v2`, `febbox`). Returns clean VTT and SRT tracks.
|
||||
|
||||
```javascript
|
||||
// Fetch movie subtitles
|
||||
const movieSubs = await sdk.getSubtitles("155");
|
||||
|
||||
// Fetch TV show episode subtitles
|
||||
const tvSubs = await sdk.getSubtitles("1399", 1, 1);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | `string` | **Yes** | - | TMDB media ID. |
|
||||
| `s` | `string \| number` | **No** | `null` | Season number (required for TV shows). |
|
||||
| `e` | `string \| number` | **No** | `null` | Episode number (required for TV shows). |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"label": "English",
|
||||
"file": "https://sub.vdrk.site/v1/vtt/movie/155/English.vtt",
|
||||
"type": "vtt",
|
||||
"source": "v1"
|
||||
},
|
||||
{
|
||||
"label": "Spanish",
|
||||
"file": "https://fed-subs.pstream.mov/subtitles/spanish.srt",
|
||||
"type": "srt",
|
||||
"source": "febbox"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getDownloads(id, s?, e?)`
|
||||
|
||||
Fetches direct downloadable media files (MP4/MKV) paired with resolution and file size descriptors.
|
||||
|
||||
```javascript
|
||||
// Movie downloads
|
||||
const downloads = await sdk.getDownloads("155");
|
||||
|
||||
// TV Episode downloads
|
||||
const tvDownloads = await sdk.getDownloads("1399", 1, 1);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | `string` | **Yes** | - | TMDB media ID. |
|
||||
| `s` | `string \| number` | **No** | `null` | Season number (required for TV shows). |
|
||||
| `e` | `string \| number` | **No** | `null` | Episode number (required for TV shows). |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"url": "https://download-node.com/file/the-dark-knight-1080p.mp4",
|
||||
"quality": "1080p",
|
||||
"size": "2.14 GB",
|
||||
"format": "MP4"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `searchManga(query, page?, perPage?)`
|
||||
|
||||
Searches manga catalogs by title, author, or keyword.
|
||||
|
||||
```javascript
|
||||
const searchResults = await sdk.searchManga("Steel Ball Run", 1, 10);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `query` | `string` | **Yes** | - | Search query text. |
|
||||
| `page` | `number` | **No** | `1` | Page number. |
|
||||
| `perPage` | `number` | **No** | `12` | Results per page limit. |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"total": 2,
|
||||
"results": [
|
||||
{
|
||||
"id": "aKXN8",
|
||||
"title": "JoJo's Bizarre Adventure: Part 7–Steel Ball Run",
|
||||
"englishTitle": null,
|
||||
"authors": ["ARAKI Hirohiko"],
|
||||
"type": "Manga",
|
||||
"medium": "Comic",
|
||||
"status": "Completed",
|
||||
"year": 2004,
|
||||
"rating": 9.03681428571429,
|
||||
"popularity": "1.6M",
|
||||
"poster": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52.jpg",
|
||||
"posterSmall": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52-small.avif",
|
||||
"posterMedium": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52-medium.avif"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getManga(id)`
|
||||
|
||||
Retrieves manga entry details, author info, synopsis, and full chapter list.
|
||||
|
||||
```javascript
|
||||
const manga = await sdk.getManga("aKXN8");
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | `string` | **Yes** | - | Unique manga ID. |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "aKXN8",
|
||||
"title": "JoJo's Bizarre Adventure: Part 7–Steel Ball Run",
|
||||
"englishTitle": null,
|
||||
"synopsis": "Riders from around the world gather in the Wild West for the race of the century...",
|
||||
"status": "Completed",
|
||||
"type": "Manga",
|
||||
"medium": "Comic",
|
||||
"rating": 9.03681428571429,
|
||||
"views": "330.5K",
|
||||
"authors": ["Hirohiko Araki"],
|
||||
"genres": ["Action", "Adventure", "Drama"],
|
||||
"tags": ["19th Century", "Seinen"],
|
||||
"poster": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52.jpg",
|
||||
"totalChapters": 95,
|
||||
"chapters": [
|
||||
{
|
||||
"id": "8KyKadCx",
|
||||
"number": 1,
|
||||
"title": "# 1",
|
||||
"pageCount": 48,
|
||||
"createdAt": "2026-04-18T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getMangaChapter(mangaId, chapterId)`
|
||||
|
||||
Resolves all page image assets for a given manga chapter.
|
||||
|
||||
```javascript
|
||||
const chapter = await sdk.getMangaChapter("aKXN8", "8KyKadCx");
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `mangaId` | `string` | **Yes** | - | Unique manga ID. |
|
||||
| `chapterId` | `string` | **Yes** | - | Unique chapter ID. |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "8KyKadCx",
|
||||
"number": 1,
|
||||
"title": "# 1",
|
||||
"scanlatorId": "cmo3e2t970003ktp80ip0n5hb",
|
||||
"pages": [
|
||||
{
|
||||
"number": 0,
|
||||
"url": "https://cdn.atsu.moe/static/pages/aKXN8/8KyKadCx/0.webp",
|
||||
"width": 1200,
|
||||
"height": 1722,
|
||||
"aspectRatio": 0.6968641114982579
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Provider & Health Management
|
||||
|
||||
### `getSources(excludeDisabled?)`
|
||||
|
||||
Retrieves provider configurations registered in the SDK.
|
||||
|
||||
```javascript
|
||||
// Get all sources including temporarily disabled ones
|
||||
const allSources = sdk.getSources(false);
|
||||
|
||||
// Get only active/enabled sources
|
||||
const activeSources = sdk.getSources(true);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `excludeDisabled` | `boolean` | **No** | `false` | When `true`, filters out providers marked as disabled (`disabled: true`). |
|
||||
|
||||
---
|
||||
|
||||
### `probeSource(key)`
|
||||
|
||||
Executes a health ping test against a single provider to determine current availability and latency using a probe payload (`TMDB ID: 155`).
|
||||
|
||||
```javascript
|
||||
const health = await sdk.probeSource("vidrock");
|
||||
console.log(health);
|
||||
// Output: { ok: true, ms: 840 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `probeAllSources()`
|
||||
|
||||
Executes health tests in parallel across all non-disabled provider modules.
|
||||
|
||||
```javascript
|
||||
const results = await sdk.probeAllSources();
|
||||
console.log(results);
|
||||
```
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"sourcename1": { "ok": true, "ms": 420 },
|
||||
"sourcename2": { "ok": true, "ms": 1150 },
|
||||
"sourcename3": { "ok": false, "ms": null }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Provider Metadata Properties
|
||||
|
||||
When calling `getSources()`, each provider entry returns metadata describing its features and streaming behavior:
|
||||
|
||||
```javascript
|
||||
{
|
||||
key: 'example',
|
||||
label: 'Example',
|
||||
sourceFile: 'example',
|
||||
proxyParam: 'ex',
|
||||
timeout: 20000,
|
||||
jitter: 800,
|
||||
retries: 3,
|
||||
multiUrl: true,
|
||||
skipProxy: false,
|
||||
cdnHeaders: [{
|
||||
pattern: /./,
|
||||
headers: {
|
||||
Referer: 'https://example.com/',
|
||||
Origin: 'https://example.com'
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Property Reference
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `key` | `string` | Unique identifier used in `getStream()` and `probeSource()`. |
|
||||
| `label` | `string` | Human-readable name for UI display. |
|
||||
| `skipProxy` | `boolean` | When `true`, stream URLs can be fed directly to web video players (e.g., HLS.js, Video.js) without needing an intermediate backend proxy. |
|
||||
| `multiUrl` | `boolean` | Indicates whether the source returns multiple stream resolution choices. |
|
||||
| `timeout` | `number` | Internal request timeout threshold in milliseconds. |
|
||||
| `retries` | `number` | Max automatic retry attempts executed by health/stream scrapers. |
|
||||
| `cdnHeaders` | `Array` | Regex pattern matching rules detailing required request headers (`Referer`, `Origin`) needed to playback stream segments without `403 Forbidden` errors. |
|
||||
| `disabled` | `boolean` | Set to `true` if a source is down or undergoing maintenance. |
|
||||
|
||||
---
|
||||
|
||||
## Practical Express.js Backend Example
|
||||
|
||||
Here is an example showing how to serve stream links to your client web application while handling custom CDN playback headers and subtitle merging:
|
||||
|
||||
```javascript
|
||||
import express from "express";
|
||||
import VylaSDK from "@vyla-entertainment/sdk";
|
||||
|
||||
const app = express();
|
||||
const sdk = new VylaSDK({ tmdbApiKey: process.env.TMDB_API_KEY });
|
||||
|
||||
app.get("/api/stream", async (req, res) => {
|
||||
const { provider, tmdbId, season, episode } = req.query;
|
||||
const clientIP = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
|
||||
|
||||
try {
|
||||
const streamData = await sdk.getStream(
|
||||
provider,
|
||||
tmdbId,
|
||||
season || null,
|
||||
episode || null,
|
||||
clientIP
|
||||
);
|
||||
|
||||
if (!streamData || !streamData.url) {
|
||||
return res.status(404).json({ error: "No stream found for this provider" });
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
provider,
|
||||
stream: streamData,
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
tmdbApiKey: process.env.TMDB_API_KEY
|
||||
});
|
||||
|
||||
app.get("/api/subtitles", async (req, res) => {
|
||||
const { tmdbId, season, episode } = req.query;
|
||||
const sources = sdk.getSources(true);
|
||||
console.log(`Active providers: ${sources.length}`);
|
||||
|
||||
try {
|
||||
const subtitles = await sdk.getSubtitles(
|
||||
tmdbId,
|
||||
season || null,
|
||||
episode || null
|
||||
);
|
||||
try {
|
||||
const stream = await sdk.getStream('vidlink', 155);
|
||||
console.log('Stream resolved:', stream.url);
|
||||
console.log('Format:', stream.type);
|
||||
} catch (err) {
|
||||
console.error('Resolution error:', err.message);
|
||||
}
|
||||
|
||||
return res.json({ subtitles });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ subtitles: [] });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/manga/search", async (req, res) => {
|
||||
const { q, page, perPage } = req.query;
|
||||
try {
|
||||
const results = await sdk.searchManga(q, page || 1, perPage || 12);
|
||||
return res.json(results);
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/manga/:mangaId/chapter/:chapterId", async (req, res) => {
|
||||
const { mangaId, chapterId } = req.params;
|
||||
try {
|
||||
const pages = await sdk.getMangaChapter(mangaId, chapterId);
|
||||
return res.json(pages);
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000, () => console.log("Vyla SDK Server running on port 3000"));
|
||||
const subtitles = await sdk.getSubtitles(155);
|
||||
console.log(`Found ${subtitles.length} subtitle tracks`);
|
||||
```
|
||||
|
||||
## SDK vs Self-Hosted API
|
||||
---
|
||||
|
||||
The SDK and the self-hosted API server provide the same streaming functionality but in different deployment models:
|
||||
## SDK Navigation
|
||||
|
||||
| Aspect | SDK | Self-Hosted API |
|
||||
| --- | --- | --- |
|
||||
| **Deployment** | Embedded in your Node.js app | Separate server process |
|
||||
| **Network** | Direct source CDN access | Built-in proxy for CORS |
|
||||
| **Rate Limiting** | None (you control it) | Per-API-key limits |
|
||||
| **Setup** | npm install + init | Clone repo + configure |
|
||||
| **Use Case** | Existing Node.js apps | Standalone streaming service |
|
||||
|
||||
Choose the SDK if you want to integrate streaming directly into an existing Node.js application. Choose the self-hosted API if you want a standalone streaming service that can be used by multiple clients.
|
||||
- **[SDK Quickstart](/sdk/vyla-sdk/quickstart)**: Practical, step-by-step tutorial for resolving movies, TV episodes, and handling errors.
|
||||
- **[Complete API Reference](/sdk/vyla-sdk/reference)**: Exhaustive documentation of all classes, methods, parameters, and TypeScript types.
|
||||
- **[Manga Module Guide](/sdk/vyla-sdk/manga)**: Guide for manga search, chapter page extraction, and aspect-ratio rendering.
|
||||
- **[Contributing a Source](/sdk/vyla-sdk/contributing)**: How to author, test, and contribute a new provider scraper.
|
||||
|
||||
@@ -1,51 +1,57 @@
|
||||
# Manga Integration
|
||||
# Manga Resolution Module
|
||||
|
||||
<p class="page-description">Search manga catalogs, retrieve chapter listings, and resolve reader-ready page images directly with Vyla SDK.</p>
|
||||
<p class="page-description">How to integrate manga title discovery, chapter listings, and high-resolution page resolution using the Vyla SDK.</p>
|
||||
|
||||
Vyla SDK provides built-in manga support to discover titles, fetch full chapter indices, and resolve high-resolution page image URLs with asset dimensions and aspect ratios.
|
||||
## Overview
|
||||
|
||||
In addition to video streaming, `@vyla-entertainment/sdk` includes a native Manga resolution module. Powered by `atsu.moe` and leveraging `node-tls-client`, the module bypasses anti-bot TLS fingerprinting by mimicking real Google Chrome 131 cryptographic handshakes.
|
||||
|
||||
This allows developers to build responsive, ad-free manga reading interfaces directly into their applications.
|
||||
|
||||
---
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
Upstream manga archives frequently block standard Node.js `fetch` or `axios` requests using JA3/JA4 TLS fingerprinting. To ensure reliable connectivity:
|
||||
|
||||
1. **TLS Emulation**: Uses `node-tls-client` configured with `ClientIdentifier.chrome_131`.
|
||||
2. **Session Persistence**: Reuses keep-alive TLS sessions across search and chapter page requests.
|
||||
3. **Structured Geometry**: Returns explicit image dimensions (`width`, `height`) and pre-calculated aspect ratios so mobile and desktop readers can prevent layout shifts during image loading.
|
||||
|
||||
---
|
||||
|
||||
## Manga Methods
|
||||
|
||||
### `searchManga(query, page?, perPage?)`
|
||||
### 1. Searching for Titles
|
||||
|
||||
Searches the manga catalog for titles matching a query string.
|
||||
Search the manga catalog by query string with pagination support:
|
||||
|
||||
```javascript
|
||||
const searchResults = await sdk.searchManga("Steel Ball Run", 1, 10);
|
||||
console.log(searchResults.results[0]);
|
||||
import VylaSDK from '@vyla-entertainment/sdk';
|
||||
|
||||
const sdk = new VylaSDK();
|
||||
|
||||
const searchResult = await sdk.searchManga('Chainsaw Man', 1, 10);
|
||||
|
||||
console.log(`Total results: ${searchResult.total}`);
|
||||
searchResult.items.forEach((manga) => {
|
||||
console.log(`[ID: ${manga.id}] ${manga.title} - ${manga.latestChapter}`);
|
||||
});
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `query` | `string` | **Yes** | - | Title, author, or acronym search query. |
|
||||
| `page` | `number` | **No** | `1` | Pagination page number. |
|
||||
| `perPage` | `number` | **No** | `12` | Results per page. |
|
||||
|
||||
#### Response Format
|
||||
|
||||
#### Search Response Structure
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"total": 2,
|
||||
"results": [
|
||||
"perPage": 10,
|
||||
"total": 42,
|
||||
"items": [
|
||||
{
|
||||
"id": "aKXN8",
|
||||
"title": "JoJo's Bizarre Adventure: Part 7–Steel Ball Run",
|
||||
"englishTitle": null,
|
||||
"authors": ["ARAKI Hirohiko"],
|
||||
"type": "Manga",
|
||||
"medium": "Comic",
|
||||
"status": "Completed",
|
||||
"year": 2004,
|
||||
"rating": 9.03681428571429,
|
||||
"popularity": "1.6M",
|
||||
"poster": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52.jpg",
|
||||
"posterSmall": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52-small.avif",
|
||||
"posterMedium": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52-medium.avif"
|
||||
"id": "chainsaw-man",
|
||||
"title": "Chainsaw Man",
|
||||
"cover": "https://cdn.atsu.moe/covers/chainsaw-man.webp",
|
||||
"status": "Ongoing",
|
||||
"latestChapter": "Chapter 180"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -53,85 +59,58 @@ console.log(searchResults.results[0]);
|
||||
|
||||
---
|
||||
|
||||
### `getManga(id)`
|
||||
### 2. Fetching Manga Details & Chapters
|
||||
|
||||
Retrieves comprehensive details for a manga entry, including synopsis, genres, authors, and full chapter list.
|
||||
Retrieve full metadata, descriptions, genres, and the complete chapter index:
|
||||
|
||||
```javascript
|
||||
const manga = await sdk.getManga("aKXN8");
|
||||
console.log(manga.title, manga.totalChapters);
|
||||
const mangaDetails = await sdk.getManga('chainsaw-man');
|
||||
|
||||
console.log('Title:', mangaDetails.title);
|
||||
console.log('Synopsis:', mangaDetails.description);
|
||||
console.log('Genres:', mangaDetails.genres.join(', '));
|
||||
console.log('Available Chapters:', mangaDetails.chapters.length);
|
||||
|
||||
const latest = mangaDetails.chapters[0];
|
||||
console.log(`Latest Chapter: ${latest.chapterNumber} (ID: ${latest.id})`);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
---
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | `string` | **Yes** | - | Unique manga identifier. |
|
||||
### 3. Resolving Chapter Pages
|
||||
|
||||
#### Response Format
|
||||
To render the reader view, fetch the ordered page image assets for a specific chapter:
|
||||
|
||||
```javascript
|
||||
const mangaId = 'chainsaw-man';
|
||||
const chapterId = 'chapter-1';
|
||||
|
||||
const pages = await sdk.getMangaChapter(mangaId, chapterId);
|
||||
|
||||
console.log(`Chapter contains ${pages.length} pages.`);
|
||||
|
||||
pages.forEach((page, index) => {
|
||||
console.log(`Page ${index + 1}: ${page.url} [${page.width}x${page.height}]`);
|
||||
});
|
||||
```
|
||||
|
||||
#### Page Object Structure
|
||||
```json
|
||||
{
|
||||
"id": "aKXN8",
|
||||
"title": "JoJo's Bizarre Adventure: Part 7–Steel Ball Run",
|
||||
"englishTitle": null,
|
||||
"synopsis": "Riders from around the world gather in the Wild West for the race of the century...",
|
||||
"status": "Completed",
|
||||
"type": "Manga",
|
||||
"medium": "Comic",
|
||||
"rating": 9.03681428571429,
|
||||
"views": "330.5K",
|
||||
"authors": ["Hirohiko Araki"],
|
||||
"genres": ["Action", "Adventure", "Drama", "Historical", "Supernatural"],
|
||||
"tags": ["19th Century", "Horses", "Seinen"],
|
||||
"poster": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52.jpg",
|
||||
"totalChapters": 95,
|
||||
"chapters": [
|
||||
{
|
||||
"id": "8KyKadCx",
|
||||
"number": 1,
|
||||
"title": "# 1",
|
||||
"pageCount": 48,
|
||||
"createdAt": "2026-04-18T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
"index": 1,
|
||||
"url": "https://cdn.atsu.moe/data/.../01.webp",
|
||||
"width": 1080,
|
||||
"height": 1600,
|
||||
"aspectRatio": 0.675
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getMangaChapter(mangaId, chapterId)`
|
||||
## Reader Layout Best Practices
|
||||
|
||||
Resolves all page images for a selected chapter, including pixel dimensions and aspect ratios for smooth responsive rendering.
|
||||
When rendering manga pages in a frontend reader:
|
||||
|
||||
```javascript
|
||||
const chapter = await sdk.getMangaChapter("aKXN8", "8KyKadCx");
|
||||
console.log(chapter.pages[0].url);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `mangaId` | `string` | **Yes** | - | Unique manga identifier. |
|
||||
| `chapterId` | `string` | **Yes** | - | Unique chapter identifier. |
|
||||
|
||||
#### Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "8KyKadCx",
|
||||
"number": 1,
|
||||
"title": "# 1",
|
||||
"scanlatorId": "cmo3e2t970003ktp80ip0n5hb",
|
||||
"pages": [
|
||||
{
|
||||
"number": 0,
|
||||
"url": "https://cdn.atsu.moe/static/pages/aKXN8/8KyKadCx/0.webp",
|
||||
"width": 1200,
|
||||
"height": 1722,
|
||||
"aspectRatio": 0.6968641114982579
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
1. **Prevent Cumulative Layout Shift (CLS)**: Use the provided `aspectRatio` or `width`/`height` in CSS (`aspect-ratio: attr(width) / attr(height);`) to reserve vertical scroll space before each image finishes downloading.
|
||||
2. **Preload Neighboring Pages**: Implement a 2-page lookahead preloader so viewers turning pages experience instantaneous transitions.
|
||||
3. **Session Cleanup**: In long-running background tasks, invoke `cleanup()` from the module when closing reader sessions to terminate idle TLS sockets.
|
||||
|
||||
@@ -1,97 +1,162 @@
|
||||
# SDK quickstart
|
||||
# Vyla SDK Quickstart
|
||||
|
||||
<p class="page-description">Install Vyla SDK, resolve a source, and build a safe server-side integration.</p>
|
||||
<p class="page-description">Get up and running with the Vyla SDK in under five minutes. Learn how to resolve movies, television episodes, subtitles, and downloads.</p>
|
||||
|
||||
## Before you start
|
||||
## Prerequisites
|
||||
|
||||
Vyla SDK is for **server-side Node.js**. It contains provider integrations and makes outbound requests from the process where you install it. Do not import it into a browser bundle or expose provider results, credentials, or internal source keys directly to untrusted clients.
|
||||
Ensure you have Node.js 20+ installed on your system. Create a new Node.js project:
|
||||
|
||||
You need Node.js 18 or later and, for sources that resolve title metadata, a TMDB API key.
|
||||
|
||||
## Install and initialize
|
||||
|
||||
:::code-group
|
||||
```bash [npm]
|
||||
npm install @vyla-entertainment/sdk
|
||||
```bash
|
||||
mkdir vyla-demo && cd vyla-demo
|
||||
npm init -y
|
||||
npm pkg set type="module"
|
||||
npm install @vyla-entertainment/sdk dotenv
|
||||
```
|
||||
```bash [pnpm]
|
||||
pnpm install @vyla-entertainment/sdk
|
||||
|
||||
Create a `.env` file containing your TMDB API key:
|
||||
|
||||
```ini
|
||||
TMDB_API_KEY=your_tmdb_api_key_here
|
||||
```
|
||||
```bash [yarn]
|
||||
yarn add @vyla-entertainment/sdk
|
||||
```
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 1. Initializing the SDK
|
||||
|
||||
Create an `index.js` file and initialize the `VylaSDK` class:
|
||||
|
||||
```javascript
|
||||
import VylaSDK from "@vyla-entertainment/sdk";
|
||||
import 'dotenv/config';
|
||||
import VylaSDK from '@vyla-entertainment/sdk';
|
||||
|
||||
const vyla = new VylaSDK({
|
||||
tmdbApiKey: process.env.TMDB_API_KEY,
|
||||
penguManifest: process.env.PENGU_MANIFEST
|
||||
const sdk = new VylaSDK({
|
||||
tmdbApiKey: process.env.TMDB_API_KEY
|
||||
});
|
||||
|
||||
console.log('Vyla SDK initialized successfully!');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Resolving a Movie Stream
|
||||
|
||||
To resolve a movie stream, supply the source key and the numeric TMDB movie ID:
|
||||
|
||||
```javascript
|
||||
const sourceKey = 'vidlink';
|
||||
const tmdbId = 155;
|
||||
|
||||
try {
|
||||
const result = await sdk.getStream(sourceKey, tmdbId);
|
||||
console.log('Stream Candidate URL:', result.url);
|
||||
console.log('Container Type:', result.type);
|
||||
console.log('Video Quality:', result.quality);
|
||||
console.log('Upstream Server:', result.server);
|
||||
} catch (error) {
|
||||
console.error(`Failed to resolve ${sourceKey}:`, error.message);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Resolving a Television Episode
|
||||
|
||||
For TV series, provide the TMDB show ID, season number, and episode number:
|
||||
|
||||
```javascript
|
||||
const showId = 1396;
|
||||
const season = 1;
|
||||
const episode = 1;
|
||||
|
||||
try {
|
||||
const tvStream = await sdk.getStream('vixsrc', showId, season, episode);
|
||||
console.log('TV Episode Stream:', tvStream.url);
|
||||
} catch (error) {
|
||||
console.error('TV stream resolution failed:', error.message);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Multi-Source Concurrent Resolution with Fallbacks
|
||||
|
||||
In production, you should query multiple active providers concurrently and select the fastest viable stream:
|
||||
|
||||
```javascript
|
||||
async function resolveFastestStream(tmdbId, s = null, e = null) {
|
||||
const activeSources = sdk.getSources(true);
|
||||
|
||||
const queryPromises = activeSources.map(async (cfg) => {
|
||||
try {
|
||||
const stream = await Promise.race([
|
||||
sdk.getStream(cfg.key, tmdbId, s, e),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), cfg.timeout || 10000)
|
||||
)
|
||||
]);
|
||||
return { source: cfg.key, label: cfg.label, stream };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(queryPromises);
|
||||
const successful = results.filter(Boolean);
|
||||
|
||||
if (successful.length === 0) {
|
||||
throw new Error('No viable streaming sources resolved.');
|
||||
}
|
||||
|
||||
console.log(`Resolved ${successful.length} valid streams!`);
|
||||
return successful;
|
||||
}
|
||||
|
||||
const sources = await resolveFastestStream(155);
|
||||
console.log('Primary Source:', sources[0].label, sources[0].stream.url);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Fetching Subtitles and Downloads
|
||||
|
||||
Subtitles and direct download links can be fetched in parallel:
|
||||
|
||||
```javascript
|
||||
const [subtitles, downloads] = await Promise.all([
|
||||
sdk.getSubtitles(155),
|
||||
sdk.getDownloads(155)
|
||||
]);
|
||||
|
||||
console.log('--- Subtitle Tracks ---');
|
||||
subtitles.slice(0, 5).forEach((sub) => {
|
||||
console.log(`[${sub.type.toUpperCase()}] ${sub.label}: ${sub.file}`);
|
||||
});
|
||||
|
||||
console.log('--- Download Packages ---');
|
||||
downloads.forEach((dl) => {
|
||||
console.log(`[${dl.quality}] Size: ${dl.size ? (dl.size / (1024**3)).toFixed(2) + ' GB' : 'Unknown'} -> ${dl.url}`);
|
||||
});
|
||||
```
|
||||
|
||||
:::callout-warning
|
||||
Keep `TMDB_API_KEY` and any deployment configuration in server-side environment variables. Never commit them or send them to a client.
|
||||
:::
|
||||
---
|
||||
|
||||
## Resolve a movie
|
||||
## 6. Live Source Health Probing
|
||||
|
||||
First inspect active providers. Provider availability changes over time, so select a key from the running configuration instead of hard-coding a placeholder.
|
||||
To check which providers are operational in your network environment:
|
||||
|
||||
```javascript
|
||||
const providers = vyla.getSources(true);
|
||||
console.table(providers.map(({ key, label }) => ({ key, label })));
|
||||
const healthReport = await sdk.probeAllSources();
|
||||
|
||||
const provider = providers[0];
|
||||
const result = await vyla.getStream(provider.key, 155);
|
||||
const candidates = result.allUrls ?? [result];
|
||||
|
||||
console.log(candidates.map(({ url, type, quality }) => ({ url, type, quality })));
|
||||
for (const [key, status] of Object.entries(healthReport)) {
|
||||
const icon = status.ok ? 'OK' : 'FAIL';
|
||||
console.log(`[${icon}] ${key}: ${status.ok ? status.ms + 'ms' : 'offline'}`);
|
||||
}
|
||||
```
|
||||
|
||||
`155` is a TMDB movie ID. For an episode, include season and episode:
|
||||
---
|
||||
|
||||
```javascript
|
||||
const result = await vyla.getStream(provider.key, 1396, 1, 1);
|
||||
```
|
||||
## Next Steps
|
||||
|
||||
## Read manga
|
||||
|
||||
Search manga titles, retrieve chapter listings, and resolve reader-ready page images directly:
|
||||
|
||||
```javascript
|
||||
const search = await vyla.searchManga("Steel Ball Run", 1, 5);
|
||||
const firstResult = search.results[0];
|
||||
|
||||
const manga = await vyla.getManga(firstResult.id);
|
||||
const firstChapter = manga.chapters[manga.chapters.length - 1];
|
||||
|
||||
const chapterData = await vyla.getMangaChapter(manga.id, firstChapter.id);
|
||||
console.log(chapterData.pages.map(p => p.url));
|
||||
```
|
||||
|
||||
## Make it resilient
|
||||
|
||||
Provider results are inherently variable: a source can fail, time out, return more than one candidate, or be temporarily disabled. In production:
|
||||
|
||||
1. Probe sources or maintain a recent availability signal before choosing one.
|
||||
2. Give every provider call a request deadline in your application.
|
||||
3. Treat `allUrls` as fallback candidates, not a guarantee that every URL will play.
|
||||
4. Return only the fields your client needs and validate requests at your own API boundary.
|
||||
5. Cache and rate-limit at your boundary to avoid repeatedly triggering upstream work.
|
||||
|
||||
```javascript
|
||||
const health = await vyla.probeAllSources();
|
||||
const usableKey = Object.entries(health).find(([, result]) => result.ok)?.[0];
|
||||
|
||||
if (!usableKey) throw new Error("No providers are currently available");
|
||||
|
||||
const stream = await vyla.getStream(usableKey, 155);
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- Read the complete [SDK reference](/sdk) for methods and return shapes.
|
||||
- Learn provider flags, timeout behavior, and source keys in [Providers](/concepts/providers).
|
||||
- Choose [Stream API](/stream-api/overview) only when an SSE HTTP interface better fits your client architecture.
|
||||
- Consult the [Complete API Reference](/sdk/vyla-sdk/reference) for exhaustive class and method parameters.
|
||||
- Learn how to integrate manga reading with the [Manga SDK Guide](/sdk/vyla-sdk/manga).
|
||||
- Deploy an HTTP/SSE bridge with the [Stream API Guide](/stream-api/overview).
|
||||
|
||||
276
sdk/vyla-sdk/reference.md
Normal file
276
sdk/vyla-sdk/reference.md
Normal file
@@ -0,0 +1,276 @@
|
||||
# Vyla SDK API Reference
|
||||
|
||||
<p class="page-description">Complete technical reference for the `@vyla-entertainment/sdk` package: classes, methods, parameters, interfaces, and TypeScript definitions.</p>
|
||||
|
||||
## Class: `VylaSDK`
|
||||
|
||||
The primary entry point of the package. Instantiated to manage scraper execution, health probing, subtitle retrieval, downloads, and manga discovery.
|
||||
|
||||
```typescript
|
||||
import VylaSDK from '@vyla-entertainment/sdk';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Constructor
|
||||
|
||||
```typescript
|
||||
constructor(options?: VylaSDKOptions)
|
||||
```
|
||||
|
||||
Creates a new instance of `VylaSDK`.
|
||||
|
||||
#### Parameters
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `options.tmdbApiKey` | `string` | Optional | `null` | Your TMDB v3 API Key. Highly recommended; required by sources that query metadata by IMDb ID or episode name. |
|
||||
| `options.penguManifest` | `any` | Optional | `null` | Custom manifest definition for the Pengu provider source. |
|
||||
|
||||
#### Example
|
||||
```javascript
|
||||
const sdk = new VylaSDK({
|
||||
tmdbApiKey: '9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d'
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getSources(excludeDisabled?)`
|
||||
|
||||
```typescript
|
||||
getSources(excludeDisabled?: boolean): SourceConfig[]
|
||||
```
|
||||
|
||||
Returns the registered list of provider scraper configurations.
|
||||
|
||||
#### Parameters
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `excludeDisabled` | `boolean` | Optional | `false` | When `true`, filters out providers marked with `disabled: true`. |
|
||||
|
||||
#### Returns
|
||||
Array of [`SourceConfig`](#interface-sourceconfig) objects.
|
||||
|
||||
#### Example
|
||||
```javascript
|
||||
const activeSources = sdk.getSources(true);
|
||||
console.log(`Available active providers: ${activeSources.map(s => s.key).join(', ')}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getStream(key, id, s?, e?, clientIP?)`
|
||||
|
||||
```typescript
|
||||
async getStream(
|
||||
key: string,
|
||||
id: string | number,
|
||||
s?: number | null,
|
||||
e?: number | null,
|
||||
clientIP?: string | null
|
||||
): Promise<StreamResult | { allUrls: StreamResult[] }>
|
||||
```
|
||||
|
||||
Executes the scraper module for a specified provider and resolves streaming candidate URLs.
|
||||
|
||||
#### Parameters
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `key` | `string` | Required | — | The source key identifier (e.g. `'vidlink'`, `'vixsrc'`). |
|
||||
| `id` | `string \| number` | Required | — | Numeric TMDB ID or string IMDb ID (for movies and TV shows). |
|
||||
| `s` | `number \| null` | Optional | `null` | Season number (required for TV episodes). |
|
||||
| `e` | `number \| null` | Optional | `null` | Episode number (required for TV episodes). |
|
||||
| `clientIP` | `string \| null` | Optional | `null` | Client IP address passed for upstream geo-token generation. |
|
||||
|
||||
#### Returns
|
||||
A Promise resolving to a [`StreamResult`](#interface-streamresult) object, or `{ allUrls: StreamResult[] }` if the source supports multi-URL extraction.
|
||||
|
||||
#### Throws
|
||||
- `Error('Source with key "..." not found')` if the key does not exist.
|
||||
- `Error('Source module for key "..." not found')` if the source module is disabled or failed to import.
|
||||
- Upstream network errors or scraper extraction errors if the title cannot be found.
|
||||
|
||||
#### Example
|
||||
```javascript
|
||||
const movieStream = await sdk.getStream('vidlink', 155);
|
||||
|
||||
const episodeStream = await sdk.getStream('vixsrc', 1396, 1, 1);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getSubtitles(id, s?, e?)`
|
||||
|
||||
```typescript
|
||||
async getSubtitles(
|
||||
id: string | number,
|
||||
s?: number | null,
|
||||
e?: number | null
|
||||
): Promise<SubtitleResult[]>
|
||||
```
|
||||
|
||||
Discovers and parses all available subtitle tracks for a movie or TV episode.
|
||||
|
||||
#### Parameters
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `id` | `string \| number` | Required | — | TMDB media identifier. |
|
||||
| `s` | `number \| null` | Optional | `null` | Season number (TV shows). |
|
||||
| `e` | `number \| null` | Optional | `null` | Episode number (TV shows). |
|
||||
|
||||
#### Returns
|
||||
A Promise resolving to an array of [`SubtitleResult`](#interface-subtitleresult) objects.
|
||||
|
||||
---
|
||||
|
||||
### `getDownloads(id, s?, e?)`
|
||||
|
||||
```typescript
|
||||
async getDownloads(
|
||||
id: string | number,
|
||||
s?: number | null,
|
||||
e?: number | null
|
||||
): Promise<DownloadResult[]>
|
||||
```
|
||||
|
||||
Resolves direct media download packages and torrent/debrid links.
|
||||
|
||||
#### Returns
|
||||
A Promise resolving to an array of [`DownloadResult`](#interface-downloadresult) objects.
|
||||
|
||||
---
|
||||
|
||||
### `probeSource(key)`
|
||||
|
||||
```typescript
|
||||
async probeSource(key: string): Promise<ProbeResult>
|
||||
```
|
||||
|
||||
Sends a lightweight probe request against the designated source to measure responsiveness and availability.
|
||||
|
||||
#### Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `key` | `string` | Required | The provider key to probe. |
|
||||
|
||||
#### Returns
|
||||
A Promise resolving to a [`ProbeResult`](#interface-proberesult) object: `{ ok: boolean, ms: number | null }`.
|
||||
|
||||
---
|
||||
|
||||
### `probeAllSources()`
|
||||
|
||||
```typescript
|
||||
async probeAllSources(): Promise<ProbeAllResult>
|
||||
```
|
||||
|
||||
Concurrently probes all active (non-disabled) sources and compiles a live health map.
|
||||
|
||||
#### Returns
|
||||
A Promise resolving to a [`ProbeAllResult`](#type-probeallresult) dictionary mapping source keys to their probe results.
|
||||
|
||||
---
|
||||
|
||||
### Manga Methods
|
||||
|
||||
#### `searchManga(query, page?, perPage?)`
|
||||
```typescript
|
||||
async searchManga(query: string, page?: number, perPage?: number): Promise<any>
|
||||
```
|
||||
Queries the manga catalog via `atsu.moe` using Chrome 131 TLS fingerprinting. Returns paginated title listings.
|
||||
|
||||
#### `getManga(id)`
|
||||
```typescript
|
||||
async getManga(id: string): Promise<any>
|
||||
```
|
||||
Retrieves full manga details including description, genres, author, status, and chapter list.
|
||||
|
||||
#### `getMangaChapter(mangaId, chapterId)`
|
||||
```typescript
|
||||
async getMangaChapter(mangaId: string, chapterId: string): Promise<any>
|
||||
```
|
||||
Resolves ordered image URLs, dimensions, and aspect ratios for all pages in a given chapter.
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Interfaces
|
||||
|
||||
### `Interface: SourceConfig`
|
||||
|
||||
```typescript
|
||||
export interface SourceConfig {
|
||||
key: string;
|
||||
label: string;
|
||||
sourceFile: string;
|
||||
proxyParam: string;
|
||||
timeout: number;
|
||||
jitter: number;
|
||||
retries: number;
|
||||
multiUrl: boolean;
|
||||
skipProxy?: boolean;
|
||||
skipVerify?: boolean;
|
||||
verifyHeaders?: boolean;
|
||||
disabled?: boolean;
|
||||
sourcesTimeout?: number;
|
||||
cdnHeaders?: {
|
||||
pattern: RegExp;
|
||||
headers: {
|
||||
Referer: string;
|
||||
Origin: string;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
```
|
||||
|
||||
### `Interface: StreamResult`
|
||||
|
||||
```typescript
|
||||
export interface StreamResult {
|
||||
url: string;
|
||||
headers: {
|
||||
"User-Agent": string;
|
||||
Referer: string;
|
||||
Origin: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
server: string;
|
||||
quality: string;
|
||||
type: "mp4" | "hls";
|
||||
skipVerify?: boolean;
|
||||
skipProxy?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### `Interface: SubtitleResult`
|
||||
|
||||
```typescript
|
||||
export interface SubtitleResult {
|
||||
label: string;
|
||||
file: string;
|
||||
type: "vtt" | "srt";
|
||||
source: string;
|
||||
}
|
||||
```
|
||||
|
||||
### `Interface: DownloadResult`
|
||||
|
||||
```typescript
|
||||
export interface DownloadResult {
|
||||
url: string;
|
||||
quality: string;
|
||||
size: number | null;
|
||||
type: "mkv";
|
||||
active: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### `Interface: ProbeResult` & `ProbeAllResult`
|
||||
|
||||
```typescript
|
||||
export interface ProbeResult {
|
||||
ok: boolean;
|
||||
ms: number | null;
|
||||
}
|
||||
|
||||
export type ProbeAllResult = Record<string, ProbeResult>;
|
||||
```
|
||||
Reference in New Issue
Block a user