added manga and added sdk source

This commit is contained in:
2026-10-14 22:29:04 -07:00
parent efc332a9fa
commit 4b255c7ee0
6 changed files with 376 additions and 8 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@vyla-entertainment/sdk",
"version": "1.15.0",
"version": "1.16.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@vyla-entertainment/sdk",
"version": "1.15.0",
"version": "1.16.0",
"license": "CC-BY-NC-4.0",
"dependencies": {
"node-tls-client": "^2.1.0"

View File

@@ -279,6 +279,25 @@ export const SOURCES = [
}],
},
{
key: 'peestream',
label: 'PeeStream',
sourceFile: 'peestream',
proxyParam: 'pee',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /peestream\.in/i,
headers: {
Referer: 'https://peestream.in/',
Origin: 'https://peestream.in',
},
}],
},
{
key: 'pengu',
label: 'Pengu',

168
src/manga/atsu.js Normal file
View File

@@ -0,0 +1,168 @@
import { Session, ClientIdentifier, initTLS, destroyTLS } from "node-tls-client";
const BASE_URL = "https://atsu.moe";
const CDN_URL = "https://cdn.atsu.moe";
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
const HEADERS = {
"User-Agent": UA,
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Cache-Control": "no-cache",
"Pragma": "no-cache",
"Referer": `${BASE_URL}/`,
"Origin": BASE_URL,
"Sec-Ch-Ua": '"Chromium";v="131", "Not_A Brand";v="24"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"Priority": "u=1, i"
};
let tlsInitialized = false;
let session = null;
async function ensureSession() {
if (!tlsInitialized) {
await initTLS();
tlsInitialized = true;
}
if (!session) {
session = new Session({
clientIdentifier: ClientIdentifier.chrome_131,
timeout: 15000,
insecureSkipVerify: false,
});
}
return session;
}
const formatAssetUrl = (url) => {
if (!url) return null;
if (url.startsWith("http://") || url.startsWith("https://")) return url;
if (url.startsWith("/static/")) return `${CDN_URL}${url}`;
if (url.startsWith("/")) return `${CDN_URL}${url}`;
return `${CDN_URL}/static/${url}`;
};
async function atsuFetch(url) {
const sess = await ensureSession();
const res = await sess.get(url, { headers: HEADERS });
if (res.status !== 200) {
throw new Error(`Atsu request failed: ${res.status}`);
}
const text = await res.text();
return JSON.parse(text);
}
export async function searchManga(query, page = 1, perPage = 12) {
const params = new URLSearchParams({
q: query,
query_by: "title,englishTitle,otherNames,authors,acronyms",
query_by_weights: "4,3,2,2,1",
num_typos: "4,3,2,1,0",
prefix: "true,true,true,true,false",
include_fields: "id,title,englishTitle,authors,poster,posterSmall,posterMedium,type,medium,isAdult,status,year,mbRating,popularity,dateAdded",
filter_by: "isAdult:=false && views:>0 && hidden:!=true",
page: String(page),
per_page: String(perPage),
infix: "off,off,fallback,off,off"
});
const data = await atsuFetch(`${BASE_URL}/collections/manga/documents/search?${params.toString()}`);
const results = (data?.hits || []).map(hit => {
const doc = hit.document || {};
return {
id: doc.id,
title: doc.title,
englishTitle: doc.englishTitle || null,
authors: Array.isArray(doc.authors) ? doc.authors : [],
type: doc.type,
medium: doc.medium,
status: doc.status,
year: doc.year,
rating: doc.mbRating ?? null,
popularity: doc.popularity ?? null,
poster: formatAssetUrl(doc.poster),
posterSmall: formatAssetUrl(doc.posterSmall),
posterMedium: formatAssetUrl(doc.posterMedium)
};
});
return {
page: data.page,
total: data.found,
results
};
}
export async function getMangaDetails(id) {
const data = await atsuFetch(`${BASE_URL}/api/manga/page?id=${encodeURIComponent(id)}`);
const page = data?.mangaPage;
if (!page) throw new Error(`Manga with ID "${id}" not found`);
return {
id: page.id,
title: page.title,
englishTitle: page.englishTitle || null,
synopsis: page.synopsis || null,
status: page.status,
type: page.type,
medium: page.medium,
rating: page.avgRating ?? null,
views: page.views ?? null,
authors: Array.isArray(page.authors) ? page.authors.map(a => a.name) : [],
genres: Array.isArray(page.genres) ? page.genres.map(g => g.name) : [],
tags: Array.isArray(page.tags) ? page.tags.map(t => t.name) : [],
poster: formatAssetUrl(page.poster?.largeImage || page.poster?.image),
totalChapters: page.totalChapterCount || page.chapters?.length || 0,
chapters: (page.chapters || []).map(ch => ({
id: ch.id,
number: ch.number,
title: ch.title,
pageCount: ch.pageCount,
createdAt: ch.createdAt ? new Date(ch.createdAt).toISOString() : null
}))
};
}
export async function getChapterPages(mangaId, chapterId) {
const data = await atsuFetch(
`${BASE_URL}/api/read/chapter?mangaId=${encodeURIComponent(mangaId)}&chapterId=${encodeURIComponent(chapterId)}`
);
const chapter = data?.readChapter;
if (!chapter) throw new Error(`Chapter "${chapterId}" not found for manga "${mangaId}"`);
return {
id: chapter.id,
number: chapter.number,
title: chapter.title,
scanlatorId: chapter.scanlationMangaId || null,
pages: (chapter.pages || []).map(p => ({
number: p.number,
url: formatAssetUrl(p.image),
width: p.width,
height: p.height,
aspectRatio: p.aspectRatio
}))
};
}
export async function cleanup() {
if (session) {
try {
await session.close();
} catch { }
session = null;
}
if (tlsInitialized) {
try {
await destroyTLS();
} catch { }
tlsInitialized = false;
}
}

View File

@@ -1,6 +1,7 @@
import SOURCES, { SOURCE_MAP } from "./config.js";
import { fetchDownloads } from "./downloads/downloads.js";
import { probeSource } from "./health.js";
import { getChapterPages, getMangaDetails, searchManga } from "./manga/atsu.js";
import { fetchSubtitles, getSubPathsMovie, getSubPathsTv } from "./subtitles.js";
import { createStreamArgs } from "./utils/helpers.js";
@@ -65,4 +66,13 @@ export default class VylaSDK {
async getDownloads(id, s = null, e = null) {
return await fetchDownloads(this, id, s, e);
}
async searchManga(query, page = 1, perPage = 12) {
return await searchManga(query, page, perPage);
}
async getManga(id) {
return await getMangaDetails(id);
}
async getMangaChapter(mangaId, chapterId) {
return await getChapterPages(mangaId, chapterId);
}
}

139
src/sources/peestream.js Normal file
View File

@@ -0,0 +1,139 @@
import { USER_AGENT, fetchJson, getTmdbInfo } from '../utils/helpers.js';
const BASE_URL = 'https://providers.peestream.in';
const PROVIDERS = {
purstream: 'Athena',
vidrock: 'Thor',
vaplayer: 'Poseidon',
netmirror: 'Ra'
};
export async function getStream(args) {
const { id, s, e, server, tmdbApiKey } = args;
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
let title = args.title || '';
let releaseYear = args.year || '';
let imdbId = args.imdbId || '';
let seasonTmdbId = args.seasonTmdbId || '';
let episodeTmdbId = args.episodeTmdbId || '';
try {
const info = await getTmdbInfo(id, type, s, tmdbApiKey);
if (info) {
title = title || info.name || info.title || '';
if (!releaseYear) {
const date = info.release_date || info.first_air_date || '';
releaseYear = date ? date.split('-')[0] : (info.year || '');
}
if (!imdbId && info.external_ids?.imdb_id) {
imdbId = info.external_ids.imdb_id;
} else if (!imdbId && info.imdb_id) {
imdbId = info.imdb_id;
}
if (isTv) {
if (!seasonTmdbId && Array.isArray(info.seasons)) {
const matchedSeason = info.seasons.find(
item => item.season_number === Number(s)
);
if (matchedSeason) {
seasonTmdbId = String(matchedSeason.id);
}
}
if (!episodeTmdbId && tmdbApiKey) {
try {
const seasonData = await fetchJson(
`https://api.themoviedb.org/3/tv/${id}/season/${s}?api_key=${tmdbApiKey}`
);
if (seasonData?.episodes) {
const matchedEp = seasonData.episodes.find(
ep => ep.episode_number === Number(e)
);
if (matchedEp) {
episodeTmdbId = String(matchedEp.id);
}
}
} catch { }
}
}
}
} catch { }
const queryParams = new URLSearchParams({
type,
tmdbId: String(id),
title: title || String(id)
});
if (releaseYear) queryParams.set('releaseYear', String(releaseYear));
if (imdbId) queryParams.set('imdbId', String(imdbId));
if (isTv) {
queryParams.set('seasonNumber', String(s));
queryParams.set('episodeNumber', String(e));
if (seasonTmdbId) queryParams.set('seasonTmdbId', String(seasonTmdbId));
if (episodeTmdbId) queryParams.set('episodeTmdbId', String(episodeTmdbId));
}
const allUrls = [];
try {
const scrapeUrl = `${BASE_URL}/scrape?${queryParams.toString()}`;
const res = await fetch(scrapeUrl, {
headers: {
'User-Agent': USER_AGENT,
'Accept': 'text/event-stream',
'Referer': `${BASE_URL}/`
},
signal: AbortSignal.timeout(20000)
});
if (res.ok) {
const text = await res.text();
const events = text.split('\n\n');
for (const ev of events) {
if (ev.includes('event: completed')) {
const dataMatch = ev.match(/data:\s*(.+)/);
if (dataMatch) {
try {
const parsed = JSON.parse(dataMatch[1].trim());
const stream = parsed.stream;
const streamUrl = stream?.playlist || stream?.url || stream?.file;
if (streamUrl) {
const sourceName = PROVIDERS[parsed.sourceId] || parsed.sourceId || 'Poseidon';
allUrls.push({
url: streamUrl,
server: `PeeStream - ${sourceName}`,
quality: 'Auto',
type: stream.type === 'hls' || streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: stream.headers || {
'User-Agent': USER_AGENT
}
});
}
} catch { }
}
}
}
}
} catch { }
if (server && server !== 'all') {
const clean = server.toLowerCase().replace('peestream - ', '').trim();
const filtered = allUrls.filter(u => u.server.toLowerCase().includes(clean));
if (filtered.length) return { allUrls: filtered };
}
return allUrls.length ? { allUrls } : null;
}
export async function getSources() {
return Object.values(PROVIDERS).map(name => `PeeStream - ${name}`);
}

View File

@@ -13,17 +13,20 @@ const CONFIG = {
TV_ID: "37854", // TV TMDB ID to test, for anime I recommend using a random ID like 37854
TV_SEASON: "1", // TV Season to test
TV_EPISODE: "1", // TV Episode to test
STREAM_SOURCE: "", // Source key to test
STREAM_SOURCE: "atsu", // Source key to test
TEST_SUBTITLE: false, // Toggle subtitle testing
TEST_DOWNLOAD: false, // Toggle download testing
TEST_MOVIE: true, // Toggle movie testing
TEST_TV: true, // Toggle TV testing
TEST_SUBTITLE: false,
TEST_DOWNLOAD: false,
TEST_MOVIE: false,
TEST_TV: false,
TEST_MANGA: true,
MANGA_QUERY: "Steel Ball Run",
EXTRA_SDK_PARAMS: { // Additional parameters for the SDK constructor
EXTRA_SDK_PARAMS: {
// penguManifest: ""
}
};
dotenv.config({
debug: CONFIG.DEBUG
});
@@ -189,4 +192,33 @@ if (CONFIG.TEST_TV) {
}
} else {
console.log("\nSkipped Stream Test");
}
if (CONFIG.TEST_MANGA) {
try {
console.log(`\nSearching Manga for: "${CONFIG.MANGA_QUERY}"`);
const searchRes = await sdk.searchManga(CONFIG.MANGA_QUERY, 1, 5);
console.log("Manga Search Found:", searchRes.total);
console.log("Top Hit:", searchRes.results[0]);
if (searchRes.results.length > 0) {
const mangaId = searchRes.results[0].id;
console.log(`\nFetching Manga Details for ID: ${mangaId}`);
const mangaDetails = await sdk.getManga(mangaId);
console.log("Manga Title:", mangaDetails.title);
console.log("Total Chapters:", mangaDetails.totalChapters);
const firstChapter = mangaDetails.chapters[mangaDetails.chapters.length - 1] || mangaDetails.chapters[0];
if (firstChapter) {
console.log(`\nFetching Pages for Chapter: ${firstChapter.title} (${firstChapter.id})`);
const chapterPages = await sdk.getMangaChapter(mangaId, firstChapter.id);
console.log("Page Count:", chapterPages.pages.length);
console.log("Sample First Page:", chapterPages.pages[0]);
}
}
} catch (err) {
console.error("Manga Test Error:", err.message);
}
} else {
console.log("\nSkipped Manga Test");
}