mirror of
https://github.com/Inside4ndroid/TMDB-Embed-API.git
synced 2026-10-22 17:45:11 +00:00
full revision added 2 more providers.
This commit is contained in:
+102
@@ -0,0 +1,102 @@
|
||||
import { getTwoEmbed } from "./workers/sites/2Embed.js";
|
||||
import { getAutoembed } from "./workers/sites/AutoEmbed.js";
|
||||
import { getEmbedSu } from "./workers/sites/Embedsu.js";
|
||||
import { getVidSrcSu } from "./workers/sites/VidsrcSU.js";
|
||||
|
||||
export async function getMovie(media, provider = null) {
|
||||
const combinedResponse = [];
|
||||
const tmdb_id = media.tmdbId;
|
||||
|
||||
if (provider) {
|
||||
try {
|
||||
let result;
|
||||
switch (provider) {
|
||||
case '2embed':
|
||||
result = await getTwoEmbed(tmdb_id);
|
||||
break;
|
||||
case 'embedsu':
|
||||
result = await getEmbedSu(tmdb_id);
|
||||
break;
|
||||
case 'autoembed':
|
||||
result = await getAutoembed(tmdb_id);
|
||||
break;
|
||||
case 'vidsrcsu':
|
||||
result = await getVidSrcSu(tmdb_id);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown provider: ${provider}`);
|
||||
}
|
||||
if (result) combinedResponse.push(result);
|
||||
} catch (e) {
|
||||
console.error(`Error fetching from ${provider}:`, e);
|
||||
}
|
||||
return combinedResponse;
|
||||
}
|
||||
|
||||
const providers = [
|
||||
{ name: '2embed', func: getTwoEmbed },
|
||||
{ name: 'embedsu', func: getEmbedSu },
|
||||
{ name: 'autoembed', func: getAutoembed },
|
||||
{ name: 'vidsrcsu', func: getVidSrcSu }
|
||||
];
|
||||
|
||||
for (const { name, func } of providers) {
|
||||
try {
|
||||
const result = await func(tmdb_id);
|
||||
if (result) combinedResponse.push(result);
|
||||
} catch (e) {
|
||||
console.error(`Error fetching from ${name}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
return combinedResponse;
|
||||
}
|
||||
|
||||
export async function getTv(media, season, episode, provider = null) {
|
||||
const combinedResponse = [];
|
||||
const tmdb_id = media.tmdbId;
|
||||
|
||||
if (provider) {
|
||||
try {
|
||||
let result;
|
||||
switch (provider) {
|
||||
case '2embed':
|
||||
result = await getTwoEmbed(tmdb_id, season, episode);
|
||||
break;
|
||||
case 'embedsu':
|
||||
result = await getEmbedSu(tmdb_id, season, episode);
|
||||
break;
|
||||
case 'autoembed':
|
||||
result = await getAutoembed(tmdb_id, season, episode);
|
||||
break;
|
||||
case 'vidsrcsu':
|
||||
result = await getVidSrcSu(tmdb_id, season, episode);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown provider: ${provider}`);
|
||||
}
|
||||
if (result) combinedResponse.push(result);
|
||||
} catch (e) {
|
||||
console.error(`Error fetching from ${provider}:`, e);
|
||||
}
|
||||
return combinedResponse;
|
||||
}
|
||||
|
||||
const providers = [
|
||||
{ name: '2embed', func: getTwoEmbed },
|
||||
{ name: 'embedsu', func: getEmbedSu },
|
||||
{ name: 'autoembed', func: getAutoembed },
|
||||
{ name: 'vidsrcsu', func: getVidSrcSu }
|
||||
];
|
||||
|
||||
for (const { name, func } of providers) {
|
||||
try {
|
||||
const result = await func(tmdb_id, season, episode);
|
||||
if (result) combinedResponse.push(result);
|
||||
} catch (e) {
|
||||
console.error(`Error fetching from ${name}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
return combinedResponse;
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
import fetch from "node-fetch";
|
||||
import JsUnpacker from '../utils/jsunpack.js';
|
||||
import * as cheerio from 'cheerio';
|
||||
|
||||
const DOMAIN = "https://www.2embed.cc";
|
||||
const PLAYER_URL = "https://uqloads.xyz";
|
||||
|
||||
let subtitles = [];
|
||||
let sources = [];
|
||||
|
||||
const headers = {
|
||||
'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
'Referer': DOMAIN,
|
||||
'Origin': DOMAIN,
|
||||
};
|
||||
|
||||
function parseQuality(qualityString) {
|
||||
if (!qualityString) return 0;
|
||||
const q = qualityString.toUpperCase();
|
||||
if (q === '4K') return 4000;
|
||||
if (q.includes('1080P')) return 1080;
|
||||
if (q.includes('720P')) return 720;
|
||||
const numMatch = q.match(/(\d+)/);
|
||||
return numMatch ? parseInt(numMatch[1], 10) : 0;
|
||||
}
|
||||
|
||||
function createErrorObject(errorMessage) {
|
||||
return {
|
||||
provider: "2Embed",
|
||||
ERROR: [{
|
||||
error: `ERROR`,
|
||||
what_happened: errorMessage,
|
||||
report_issue: 'https://github.com/Inside4ndroid/vidsrc-api-js/issues'
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
export async function getTwoEmbed(tmdb_id, s, e) {
|
||||
subtitles = [];
|
||||
sources = [];
|
||||
|
||||
const tmdbId = tmdb_id;
|
||||
const initialReferer = s && e ? `${DOMAIN}/embedtv/${tmdbId}&s=${s}&e=${e}` : `${DOMAIN}/embed/${tmdbId}`;
|
||||
const url = initialReferer;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Referer: url,
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"User-Agent": headers['User-Agent'],
|
||||
},
|
||||
body: "pls=pls"
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return { sources: createErrorObject(`HTTP error fetching initial URL! Status: ${response.status}`) };
|
||||
}
|
||||
|
||||
const data = await response.text();
|
||||
let extractedValue = null;
|
||||
let isSwishId = false;
|
||||
|
||||
let match = data.match(/swish\?id=(?<id>[\w\d]+)/);
|
||||
|
||||
if (match && match.groups && match.groups.id) {
|
||||
extractedValue = match.groups.id;
|
||||
isSwishId = true;
|
||||
} else {
|
||||
match = data.match(/'(.*?player4u.*?)'/);
|
||||
if (match && match[1]) {
|
||||
extractedValue = match[1];
|
||||
} else {
|
||||
return { sources: createErrorObject("No relevant swish ID or player4u URL found in initial data.") };
|
||||
}
|
||||
}
|
||||
|
||||
if (extractedValue) {
|
||||
if (isSwishId) {
|
||||
const streamUrl = await resolve(`${PLAYER_URL}/e/${extractedValue}`, DOMAIN);
|
||||
if (!streamUrl) {
|
||||
return { sources: createErrorObject(`Could not resolve stream URL for swish ID: ${extractedValue}`) };
|
||||
}
|
||||
|
||||
const parsedSources = await parseM3U8(streamUrl, PLAYER_URL);
|
||||
|
||||
if (parsedSources.length > 0) {
|
||||
return {
|
||||
source: {
|
||||
provider: "2Embed/Swish",
|
||||
files: parsedSources,
|
||||
subtitles: subtitles,
|
||||
headers: {
|
||||
"Referer": PLAYER_URL,
|
||||
"User-Agent": headers['User-Agent'],
|
||||
"Origin": PLAYER_URL
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return { sources: createErrorObject("No valid sources found in M3U8 for swish ID") };
|
||||
}
|
||||
} else {
|
||||
const listPageResponse = await fetch(extractedValue, {
|
||||
headers: {
|
||||
Referer: url,
|
||||
"User-Agent": headers['User-Agent']
|
||||
}
|
||||
});
|
||||
|
||||
if (!listPageResponse.ok) {
|
||||
return { sources: createErrorObject(`Failed to fetch player4u list page (${extractedValue}): ${listPageResponse.status}`) };
|
||||
}
|
||||
|
||||
const listPageHtml = await listPageResponse.text();
|
||||
|
||||
const $ = cheerio.load(listPageHtml);
|
||||
let highestQuality = -1;
|
||||
let bestPartialUrl = null;
|
||||
|
||||
$('li.slide-toggle a.playbtnx').each((index, element) => {
|
||||
const linkText = $(element).text();
|
||||
const onclickAttr = $(element).attr('onclick');
|
||||
|
||||
if (!linkText || !onclickAttr) return;
|
||||
|
||||
const qualityMatch = linkText.match(/\s*(\d+p|4K)\s*/i);
|
||||
const qualityString = qualityMatch ? qualityMatch[1].toUpperCase() : null;
|
||||
|
||||
const urlMatch = onclickAttr.match(/go\('([^']+)'\)/);
|
||||
const partialUrl = urlMatch ? urlMatch[1] : null;
|
||||
|
||||
if (!qualityString || !partialUrl) return;
|
||||
|
||||
const qualityValue = parseQuality(qualityString);
|
||||
|
||||
if (qualityValue > highestQuality) {
|
||||
highestQuality = qualityValue;
|
||||
bestPartialUrl = partialUrl;
|
||||
}
|
||||
});
|
||||
|
||||
if (bestPartialUrl) {
|
||||
const idMatch = bestPartialUrl.match(/\?id=([\w\d]+)/);
|
||||
if (idMatch && idMatch[1]) {
|
||||
const player4uId = idMatch[1];
|
||||
const resolveUrl = `${PLAYER_URL}/e/${player4uId}`;
|
||||
|
||||
const streamUrl = await resolve(resolveUrl, extractedValue);
|
||||
if (!streamUrl) {
|
||||
return { sources: createErrorObject(`Could not resolve stream URL for player4u ID: ${player4uId}`) };
|
||||
}
|
||||
|
||||
const parsedSources = await parseM3U8(streamUrl, PLAYER_URL);
|
||||
|
||||
if (parsedSources.length > 0) {
|
||||
return {
|
||||
source: {
|
||||
provider: "2Embed/Player4u",
|
||||
files: parsedSources,
|
||||
subtitles: subtitles,
|
||||
headers: {
|
||||
"Referer": PLAYER_URL,
|
||||
"User-Agent": headers['User-Agent'],
|
||||
"Origin": PLAYER_URL
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return { sources: createErrorObject("No valid sources found in M3U8 for player4u ID") };
|
||||
}
|
||||
} else {
|
||||
return { sources: createErrorObject("Could not extract player4u ID from best quality URL") };
|
||||
}
|
||||
} else {
|
||||
return { sources: createErrorObject("No valid quality options found on player4u page") };
|
||||
}
|
||||
}
|
||||
}
|
||||
return { sources: createErrorObject("No sources found after processing") };
|
||||
|
||||
} catch (error) {
|
||||
return { sources: createErrorObject(`Unexpected error: ${error.message}`) };
|
||||
}
|
||||
}
|
||||
|
||||
async function resolve(url, referer) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Referer: referer,
|
||||
"User-Agent": headers['User-Agent']
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Resolve failed for ${url}: Status ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.text();
|
||||
const packedDataRegex = /eval\(function(.*?)split.*\)\)\)/s;
|
||||
const packedDataMatch = data.match(packedDataRegex);
|
||||
|
||||
if (packedDataMatch) {
|
||||
const packedJS = packedDataMatch[0];
|
||||
const unpacker = new JsUnpacker(packedJS);
|
||||
|
||||
if (unpacker.detect()) {
|
||||
const unpackedJS = unpacker.unpack();
|
||||
if (!unpackedJS) {
|
||||
console.error("JsUnpacker failed to unpack");
|
||||
return null;
|
||||
}
|
||||
|
||||
await parseSubs(unpackedJS);
|
||||
|
||||
const docheck = unpackedJS.includes("\"hls2\":\"https");
|
||||
|
||||
if (docheck) {
|
||||
const fileRegex = /links=.*hls2\":\"(.*?)\"};/;
|
||||
|
||||
const matchUri = unpackedJS.match(fileRegex);
|
||||
|
||||
if (matchUri && matchUri[1]) {
|
||||
return matchUri[1];
|
||||
} else {
|
||||
console.error("Could not find file URL in unpacked JS");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
const fileRegex = /sources\s*:\s*\[\s*\{\s*file\s*:\s*"([^"]+)"/;
|
||||
|
||||
const matchUri = unpackedJS.match(fileRegex);
|
||||
|
||||
if (matchUri && matchUri[1]) {
|
||||
return matchUri[1];
|
||||
} else {
|
||||
console.error("Could not find file URL in unpacked JS");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
console.error("JsUnpacker could not detect packed data in resolve response");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
console.error("No packed JS data found in resolve response for:", url);
|
||||
if (url.includes('.m3u8')) return url;
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error during resolve for ${url}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function parseM3U8(m3u8Url, referer) {
|
||||
const parsedSources = [];
|
||||
try {
|
||||
const response = await fetch(m3u8Url, {
|
||||
headers: {
|
||||
"User-Agent": headers['User-Agent'],
|
||||
"Referer": referer
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch M3U8 ${m3u8Url}: Status ${response.status}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const m3u8Content = await response.text();
|
||||
const lines = m3u8Content.split('\n');
|
||||
let currentQuality = null;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
||||
currentQuality = null;
|
||||
const resolutionMatch = line.match(/RESOLUTION=\d+x(\d+)/);
|
||||
if (resolutionMatch && resolutionMatch[1]) {
|
||||
currentQuality = resolutionMatch[1] + "p";
|
||||
} else {
|
||||
const bwMatch = line.match(/BANDWIDTH=(\d+)/);
|
||||
if (bwMatch) {
|
||||
currentQuality = `bw${bwMatch[1]}`;
|
||||
}
|
||||
}
|
||||
} else if (currentQuality && (line.startsWith('http') || (line.includes('.m3u8') && !line.startsWith('#')))) {
|
||||
let resolvedUrl = line;
|
||||
if (!line.startsWith('http')) {
|
||||
const baseUrl = new URL(m3u8Url);
|
||||
resolvedUrl = new URL(line, baseUrl.origin + baseUrl.pathname.substring(0, baseUrl.pathname.lastIndexOf('/') + 1)).href;
|
||||
}
|
||||
|
||||
parsedSources.push({
|
||||
file: resolvedUrl,
|
||||
type: 'hls',
|
||||
quality: currentQuality,
|
||||
lang: 'en'
|
||||
});
|
||||
currentQuality = null;
|
||||
}
|
||||
}
|
||||
parsedSources.sort((a, b) => parseQuality(b.quality) - parseQuality(a.quality));
|
||||
|
||||
return parsedSources;
|
||||
} catch (error) {
|
||||
console.error(`Error parsing M3U8 ${m3u8Url}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function parseSubs(scriptstring) {
|
||||
if (typeof subtitles === 'undefined') {
|
||||
console.error("Subtitles array is not accessible in parseSubs");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const linksMatch = scriptstring.match(/var links\s*=\s*({[^;]*});/);
|
||||
if (!linksMatch) {
|
||||
console.error("Could not find links object in script");
|
||||
return;
|
||||
}
|
||||
|
||||
let linksStr = linksMatch[1]
|
||||
.replace(/'/g, '"')
|
||||
.replace(/([{,]\s*)([a-zA-Z0-9_$]+)(\s*:)/g, '$1"$2"$3');
|
||||
|
||||
const links = JSON.parse(linksStr);
|
||||
const videoUrl = links.hls2;
|
||||
|
||||
const setupMatch = scriptstring.match(/jwplayer\(["']vplayer["']\)\.setup\((\{[\s\S]*?\})\);[\s\S]*?$/);
|
||||
if (!setupMatch || !setupMatch[1]) {
|
||||
console.error("Could not find JWPlayer setup configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
let setupStr = setupMatch[1];
|
||||
|
||||
setupStr = setupStr.replace(/links\.hls4\s*\|\|\s*links\.hls2/g, `"${videoUrl}"`);
|
||||
|
||||
setupStr = setupStr
|
||||
.replace(/\\'/g, "'")
|
||||
.replace(/([{,]\s*)([a-zA-Z0-9_$]+)(\s*:)/g, '$1"$2"$3')
|
||||
.replace(/'/g, '"')
|
||||
.replace(/,\s*([}\]])/g, '$1')
|
||||
.replace(/"true"/g, 'true')
|
||||
.replace(/"false"/g, 'false')
|
||||
.replace(/"null"/g, 'null');
|
||||
|
||||
let setupConfig;
|
||||
try {
|
||||
setupConfig = JSON.parse(setupStr);
|
||||
} catch (err) {
|
||||
console.error("JSON parse error:", err, "in string:", setupStr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!setupConfig.tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subtitleTracks = (setupConfig.tracks || []).filter(track =>
|
||||
track && (track.kind === "captions" || track.kind === "subtitles") && track.file
|
||||
).map(track => ({
|
||||
url: track.file,
|
||||
lang: track.label || track.kind
|
||||
}));
|
||||
|
||||
subtitleTracks.forEach(newSub => {
|
||||
if (!subtitles.some(existingSub => existingSub.url === newSub.url && existingSub.lang === newSub.lang)) {
|
||||
subtitles.push(newSub);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error during subtitle parsing:", error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import fetch from "node-fetch";
|
||||
|
||||
const DOMAIN = "https://autoembed.cc/";
|
||||
|
||||
const headers = {
|
||||
'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
'Referer': DOMAIN,
|
||||
'Origin': DOMAIN,
|
||||
};
|
||||
|
||||
function createErrorObject(errorMessage) {
|
||||
return {
|
||||
provider: "AutoEmbed",
|
||||
ERROR: [{
|
||||
error: `ERROR`,
|
||||
what_happened: errorMessage,
|
||||
report_issue: 'https://github.com/Inside4ndroid/vidsrc-api-js/issues'
|
||||
}]
|
||||
};
|
||||
};
|
||||
|
||||
export async function getAutoembed(tmdb_id, s, e) {
|
||||
const providerName = "AutoEmbed";
|
||||
const url = s && e
|
||||
? `https://tom.autoembed.cc/api/getVideoSource?type=tv&id=${tmdb_id}/${s}/${e}`
|
||||
: `https://tom.autoembed.cc/api/getVideoSource?type=movie&id=${tmdb_id}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Referer': DOMAIN,
|
||||
'User-Agent': headers['User-Agent']
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return createErrorObject(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
return createErrorObject(data.error || "No stream with ID found");
|
||||
}
|
||||
|
||||
if (!data.videoSource) {
|
||||
return createErrorObject("No video source found in response");
|
||||
}
|
||||
|
||||
const m3u8Url = data.videoSource;
|
||||
|
||||
try {
|
||||
const m3u8Response = await fetch(m3u8Url);
|
||||
if (!m3u8Response.ok) {
|
||||
return createErrorObject(`Failed to fetch m3u8: HTTP ${m3u8Response.status}`);
|
||||
}
|
||||
|
||||
const m3u8Content = await m3u8Response.text();
|
||||
const sources = parseM3U8(m3u8Content);
|
||||
|
||||
if (!sources || sources.length === 0) {
|
||||
return createErrorObject("No valid streams found in m3u8 file");
|
||||
}
|
||||
|
||||
const formattedSources = sources.map(source => ({
|
||||
file: source.url,
|
||||
type: "hls",
|
||||
quality: source.quality || 'unknown',
|
||||
lang: 'en'
|
||||
}));
|
||||
|
||||
const subs = data.subtitles ? mapSubtitles(data.subtitles) : [];
|
||||
|
||||
return {
|
||||
source: {
|
||||
provider: providerName,
|
||||
files: formattedSources,
|
||||
subtitles: subs,
|
||||
headers: {
|
||||
"Referer": DOMAIN,
|
||||
"User-Agent": headers['User-Agent'],
|
||||
"Origin": DOMAIN
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} catch (m3u8Error) {
|
||||
return createErrorObject(`Failed to process m3u8: ${m3u8Error.message}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
return createErrorObject(`Network/API error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseM3U8(m3u8Content) {
|
||||
try {
|
||||
const lines = m3u8Content.split('\n');
|
||||
const sources = [];
|
||||
let currentSource = {};
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
||||
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
|
||||
if (resolutionMatch) {
|
||||
const resolution = resolutionMatch[1];
|
||||
const quality = resolution.split('x')[1];
|
||||
currentSource.quality = quality + 'p';
|
||||
}
|
||||
} else if (line.startsWith('http')) {
|
||||
currentSource.url = line;
|
||||
sources.push({ ...currentSource });
|
||||
currentSource = {};
|
||||
}
|
||||
}
|
||||
|
||||
return sources;
|
||||
} catch (error) {
|
||||
console.error('Error parsing m3u8:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function mapSubtitles(subtitles) {
|
||||
try {
|
||||
return subtitles.map(subtitle => {
|
||||
const lang = (subtitle.label || 'unknown').split(' ')[0].toLowerCase();
|
||||
const fileUrl = subtitle.file || '';
|
||||
const fileExtension = fileUrl.split('.').pop().toLowerCase();
|
||||
const type = fileExtension === 'vtt' ? 'vtt' : 'srt';
|
||||
|
||||
return {
|
||||
url: fileUrl,
|
||||
lang: lang,
|
||||
type: type
|
||||
};
|
||||
}).filter(sub => sub.url);
|
||||
} catch (error) {
|
||||
console.error('Error mapping subtitles:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
function createErrorObject(errorMessage) {
|
||||
return {
|
||||
provider: "EmbedSU",
|
||||
ERROR: [{
|
||||
error: `ERROR`,
|
||||
what_happened: errorMessage,
|
||||
report_issue: 'https://github.com/Inside4ndroid/vidsrc-api-js/issues'
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
export async function getEmbedSu(tmdb_id, s, e) {
|
||||
const DOMAIN = "https://embed.su";
|
||||
const headers = {
|
||||
'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
'Referer': DOMAIN,
|
||||
'Origin': DOMAIN,
|
||||
};
|
||||
|
||||
try {
|
||||
const urlSearch = s && e ? `${DOMAIN}/embed/tv/${tmdb_id}/${s}/${e}` : `${DOMAIN}/embed/movie/${tmdb_id}`;
|
||||
const htmlSearch = await fetch(urlSearch, { method: 'GET', headers });
|
||||
|
||||
if (!htmlSearch.ok) {
|
||||
return { sources: [createErrorObject(`Failed to fetch initial page: HTTP ${htmlSearch.status}`)] };
|
||||
}
|
||||
|
||||
const textSearch = await htmlSearch.text();
|
||||
const hashEncodeMatch = textSearch.match(/JSON\.parse\(atob\(\`([^\`]+)/i);
|
||||
const hashEncode = hashEncodeMatch ? hashEncodeMatch[1] : "";
|
||||
|
||||
if (!hashEncode) {
|
||||
return { sources: [createErrorObject("No encoded hash found in initial page")] };
|
||||
}
|
||||
|
||||
let hashDecode;
|
||||
try {
|
||||
hashDecode = JSON.parse(await stringAtob(hashEncode));
|
||||
} catch (e) {
|
||||
return { sources: [createErrorObject(`Failed to decode initial hash: ${e.message}`)] };
|
||||
}
|
||||
|
||||
const mEncrypt = hashDecode.hash;
|
||||
if (!mEncrypt) {
|
||||
return { sources: [createErrorObject("No encrypted hash found in decoded data")] };
|
||||
}
|
||||
|
||||
let firstDecode;
|
||||
try {
|
||||
firstDecode = (await stringAtob(mEncrypt)).split(".").map(item => item.split("").reverse().join(""));
|
||||
} catch (e) {
|
||||
return { sources: [createErrorObject(`Failed to decode first layer: ${e.message}`)] };
|
||||
}
|
||||
|
||||
let secondDecode;
|
||||
try {
|
||||
secondDecode = JSON.parse(await stringAtob(firstDecode.join("").split("").reverse().join("")));
|
||||
} catch (e) {
|
||||
return { sources: [createErrorObject(`Failed to decode second layer: ${e.message}`)] };
|
||||
}
|
||||
|
||||
if (!secondDecode || secondDecode.length === 0) {
|
||||
return { sources: [createErrorObject("No valid sources found after decoding")] };
|
||||
}
|
||||
|
||||
const sources = [];
|
||||
const subtitles = [];
|
||||
|
||||
for (const item of secondDecode) {
|
||||
try {
|
||||
const urlDirect = `${DOMAIN}/api/e/${item.hash}`;
|
||||
const dataDirect = await requestGet(urlDirect, {
|
||||
"Referer": DOMAIN,
|
||||
"User-Agent": headers['User-Agent'],
|
||||
"Origin": DOMAIN
|
||||
});
|
||||
|
||||
if (!dataDirect || !dataDirect.source) {
|
||||
console.warn(`No source found for hash ${item.hash}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const tracks = (dataDirect.subtitles || []).map(sub => ({
|
||||
url: sub.file,
|
||||
lang: sub.label ? sub.label.split('-')[0].trim() : 'en'
|
||||
})).filter(track => track.url);
|
||||
|
||||
const requestDirectSize = await fetch(dataDirect.source, { headers, method: "GET" });
|
||||
if (!requestDirectSize.ok) {
|
||||
console.warn(`Failed to fetch source ${dataDirect.source}: HTTP ${requestDirectSize.status}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const parseRequest = await requestDirectSize.text();
|
||||
const patternSize = parseRequest.split('\n').filter(item => item.includes('/proxy/'));
|
||||
|
||||
const directQuality = patternSize.map(patternItem => {
|
||||
try {
|
||||
const sizeQuality = getSizeQuality(patternItem);
|
||||
let dURL = `${DOMAIN}${patternItem}`;
|
||||
dURL = dURL.replace(".png", ".m3u8");
|
||||
return { file: dURL, type: 'hls', quality: `${sizeQuality}p`, lang: 'en' };
|
||||
} catch (e) {
|
||||
console.warn(`Failed to process quality for pattern: ${patternItem}`, e);
|
||||
return null;
|
||||
}
|
||||
}).filter(item => item !== null);
|
||||
|
||||
if (!directQuality.length) {
|
||||
console.warn(`No valid qualities found for source ${dataDirect.source}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
return {
|
||||
source: {
|
||||
provider: "EmbedSu",
|
||||
files: directQuality,
|
||||
subtitles: tracks,
|
||||
headers: {
|
||||
"Referer": DOMAIN,
|
||||
"User-Agent": headers['User-Agent'],
|
||||
"Origin": DOMAIN
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(`Error processing item ${item.hash}:`, e);
|
||||
// Continue to next item instead of failing entire request
|
||||
}
|
||||
}
|
||||
|
||||
if (sources.length === 0) {
|
||||
return { sources: [createErrorObject("No valid sources found after processing all items")] };
|
||||
}
|
||||
|
||||
return { sources };
|
||||
} catch (e) {
|
||||
return { sources: [createErrorObject(`Unexpected error: ${e.message}`)] };
|
||||
}
|
||||
}
|
||||
|
||||
function getSizeQuality(url) {
|
||||
try {
|
||||
const parts = url.split('/');
|
||||
const base64Part = parts[parts.length - 2];
|
||||
const decodedPart = atob(base64Part);
|
||||
return Number(decodedPart) || 1080;
|
||||
} catch (e) {
|
||||
console.warn(`Failed to get size quality for URL ${url}:`, e);
|
||||
return 720; // Default quality if parsing fails
|
||||
}
|
||||
}
|
||||
|
||||
async function stringAtob(input) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
let str = input.replace(/=+$/, '');
|
||||
let output = '';
|
||||
|
||||
if (str.length % 4 === 1) {
|
||||
throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
|
||||
}
|
||||
|
||||
for (let bc = 0, bs = 0, buffer, i = 0; buffer = str.charAt(i++); ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0) {
|
||||
buffer = chars.indexOf(buffer);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
async function requestGet(url, headers = {}) {
|
||||
try {
|
||||
const response = await fetch(url, { method: 'GET', headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`Request failed for ${url}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const DOMAIN = "https://vidsrc.su/";
|
||||
|
||||
const headers = {
|
||||
'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
'Referer': DOMAIN,
|
||||
'Origin': DOMAIN,
|
||||
};
|
||||
|
||||
function createErrorObject(errorMessage) {
|
||||
return {
|
||||
provider: "EmbedSU",
|
||||
ERROR: [{
|
||||
error: `ERROR`,
|
||||
what_happened: errorMessage,
|
||||
report_issue: 'https://github.com/Inside4ndroid/vidsrc-api-js/issues'
|
||||
}]
|
||||
};
|
||||
};
|
||||
|
||||
export async function getVidSrcSu(tmdb_id, s, e) {
|
||||
|
||||
const embedUrl = s && e ? `https://vidsrc.su/embed/tv/${tmdb_id}/${s}/${e}` : `https://vidsrc.su/embed/movie/${tmdb_id}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(embedUrl, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const html = await response.text();
|
||||
|
||||
let subtitles = [];
|
||||
|
||||
const servers = [...html.matchAll(/label: 'Server (3|4|5|7|8|9|10|12|13|15|17|18|19)', url: '(https.*)'/g)].map(match => ({
|
||||
file: match[2],
|
||||
type: "hls",
|
||||
lang: "en"
|
||||
}));
|
||||
|
||||
const subtitlesMatch = html.match(/const subtitles = \[(.*)\];/g);
|
||||
if (subtitlesMatch && subtitlesMatch[0]) {
|
||||
try {
|
||||
subtitles = JSON.parse(subtitlesMatch[0].replace('const subtitles = ', '').replaceAll(';', ''));
|
||||
subtitles.shift();
|
||||
subtitles = subtitles.map(subtitle => ({
|
||||
url: subtitle.url,
|
||||
lang: subtitle.language,
|
||||
type: subtitle.format
|
||||
}));
|
||||
} catch (parseError) {
|
||||
console.error("Error parsing subtitles:", parseError);
|
||||
subtitles = [];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
servers.forEach(server => {
|
||||
// smth
|
||||
});
|
||||
} catch (error) {
|
||||
return createErrorObject("No valid video streams found after parsing all servers");
|
||||
}
|
||||
|
||||
return {
|
||||
source: {
|
||||
provider: "VidsrcSU",
|
||||
files: servers,
|
||||
subtitles: subtitles,
|
||||
headers: {
|
||||
"Referer": DOMAIN,
|
||||
"User-Agent": headers['User-Agent'],
|
||||
"Origin": DOMAIN
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return createErrorObject(`Unexpected error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
const apiKey = process.env.TMDB_API_KEY;
|
||||
|
||||
export async function getMovieFromTmdb(tmdb_id) {
|
||||
try {
|
||||
const url = `https://api.themoviedb.org/3/movie/${tmdb_id}?api_key=${apiKey}`;
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
if (new Date(data.release_date) > new Date().getTime()) {
|
||||
return new Error("Media not released yet");
|
||||
}
|
||||
let info = {
|
||||
type: "movie",
|
||||
title: data.original_title,
|
||||
releaseYear: Number(data.release_date.split("-")[0]),
|
||||
tmdbId: tmdb_id,
|
||||
imdbId: data.imdb_id
|
||||
}
|
||||
return info;
|
||||
} catch (e) {
|
||||
return new Error("An error occurred" + e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTvFromTmdb(tmdb_id, season, episode) {
|
||||
try {
|
||||
const url = `https://api.themoviedb.org/3/tv/${tmdb_id}/season/${season}/episode/${episode}?api_key=${apiKey}&append_to_response=external_ids`;
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
if (new Date(data.air_date) > new Date().getTime()) {
|
||||
return new Error("Not released yet");
|
||||
}
|
||||
let secondData = await fetch(`https://api.themoviedb.org/3/tv/${tmdb_id}?api_key=${apiKey}`);
|
||||
secondData = await secondData.json();
|
||||
let title = secondData.name;
|
||||
let info = {
|
||||
type: "tv",
|
||||
title: title,
|
||||
releaseYear: data.air_date.split("-")[0],
|
||||
tmdbId: tmdb_id,
|
||||
imdbId: data.external_ids.imdb_id,
|
||||
season: season,
|
||||
episode: episode,
|
||||
episodeName: data.name
|
||||
}
|
||||
return info;
|
||||
} catch (e) {
|
||||
return new Error("An error occurred" + e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
export default class JsUnpacker {
|
||||
static Unbase = class {
|
||||
constructor(radix) {
|
||||
this.ALPHABET_62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
this.ALPHABET_95 = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
|
||||
this.alphabet = null;
|
||||
this.dictionary = {};
|
||||
this.radix = radix;
|
||||
|
||||
if (radix > 36) {
|
||||
if (radix < 62) {
|
||||
this.alphabet = this.ALPHABET_62.substring(0, radix);
|
||||
} else if (radix > 62 && radix < 95) {
|
||||
this.alphabet = this.ALPHABET_95.substring(0, radix);
|
||||
} else if (radix === 62) {
|
||||
this.alphabet = this.ALPHABET_62;
|
||||
} else if (radix === 95) {
|
||||
this.alphabet = this.ALPHABET_95;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.alphabet.length; i++) {
|
||||
this.dictionary[this.alphabet[i]] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unbase(str) {
|
||||
let ret = 0;
|
||||
|
||||
if (this.alphabet === null) {
|
||||
ret = parseInt(str, this.radix);
|
||||
} else {
|
||||
const tmp = str.split('').reverse().join('');
|
||||
for (let i = 0; i < tmp.length; i++) {
|
||||
ret += Math.pow(this.radix, i) * this.dictionary[tmp[i]];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
constructor(packedJS) {
|
||||
this.packedJS = packedJS;
|
||||
}
|
||||
|
||||
detect() {
|
||||
const js = this.packedJS.replace(/\s/g, '');
|
||||
const regex = /eval\(function\(p,a,c,k,e,(?:r|d)/;
|
||||
return regex.test(js);
|
||||
}
|
||||
|
||||
unpack() {
|
||||
let js = this.packedJS;
|
||||
try {
|
||||
const regex = /\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)/s;
|
||||
const match = js.match(regex);
|
||||
if (match && match.length === 5) {
|
||||
let payload = match[1].replace(/\\'/g, "'");
|
||||
const radixStr = match[2];
|
||||
const countStr = match[3];
|
||||
const symtab = match[4].split('|');
|
||||
|
||||
let radix = 36;
|
||||
let count = 0;
|
||||
try {
|
||||
radix = parseInt(radixStr);
|
||||
} catch (e) {
|
||||
}
|
||||
try {
|
||||
count = parseInt(countStr);
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
if (symtab.length !== count) {
|
||||
throw new Error("Unknown p.a.c.k.e.r. encoding");
|
||||
}
|
||||
|
||||
const unbase = new this.constructor.Unbase(radix);
|
||||
const wordRegex = /\b\w+\b/g;
|
||||
let decoded = payload;
|
||||
let replaceOffset = 0;
|
||||
let wordMatch;
|
||||
|
||||
while ((wordMatch = wordRegex.exec(payload)) !== null) {
|
||||
const word = wordMatch[0];
|
||||
const x = unbase.unbase(word);
|
||||
let value = null;
|
||||
if (x < symtab.length) {
|
||||
value = symtab[x];
|
||||
}
|
||||
|
||||
if (value && value.length > 0) {
|
||||
const start = wordMatch.index + replaceOffset;
|
||||
const end = start + word.length;
|
||||
decoded = decoded.substring(0, start) + value + decoded.substring(end);
|
||||
replaceOffset += (value.length - word.length);
|
||||
}
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user