huge rewrite

This commit is contained in:
2026-11-14 18:21:12 -08:00
parent f465d1ec0d
commit 2cec85a8f3
69 changed files with 11479 additions and 1576 deletions

View File

@@ -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(...)`).

View File

@@ -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.

View File

@@ -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.

View File

@@ -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
View 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>;
```