mirror of
https://github.com/Inside4ndroid/TMDB-Embed-API.git
synced 2026-10-22 17:45:11 +00:00
Major Update : Added 2Embed, Update api routes, update json response format.
This commit is contained in:
+116
-53
@@ -1,5 +1,16 @@
|
||||
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 = {
|
||||
@@ -11,82 +22,133 @@ export async function getEmbedSu(tmdb_id, s, e) {
|
||||
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 });
|
||||
const textSearch = await htmlSearch.text();
|
||||
|
||||
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;
|
||||
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 hashDecode = JSON.parse(await stringAtob(hashEncode));
|
||||
const mEncrypt = hashDecode.hash;
|
||||
if (!mEncrypt) return;
|
||||
if (!mEncrypt) {
|
||||
return { sources: [createErrorObject("No encrypted hash found in decoded data")] };
|
||||
}
|
||||
|
||||
const firstDecode = (await stringAtob(mEncrypt)).split(".").map(item => item.split("").reverse().join(""));
|
||||
const secondDecode = JSON.parse(await stringAtob(firstDecode.join("").split("").reverse().join("")));
|
||||
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}`)] };
|
||||
}
|
||||
|
||||
if (!secondDecode || secondDecode.length === 0) return;
|
||||
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) {
|
||||
const urlDirect = `${DOMAIN}/api/e/${item.hash}`;
|
||||
const dataDirect = await requestGet(urlDirect, {
|
||||
"Referer": DOMAIN,
|
||||
"User-Agent": headers['User-Agent'],
|
||||
"Origin": DOMAIN
|
||||
});
|
||||
|
||||
if (!dataDirect.source) continue;
|
||||
|
||||
const tracks = dataDirect.subtitles.map(sub => ({
|
||||
url: sub.file,
|
||||
lang: sub.label.split('-')[0].trim()
|
||||
})).filter(track => track.lang);
|
||||
|
||||
const requestDirectSize = await fetch(dataDirect.source, { headers, method: "GET" });
|
||||
const parseRequest = await requestDirectSize.text();
|
||||
|
||||
const patternSize = parseRequest.split('\n').filter(item => item.includes('/proxy/'));
|
||||
|
||||
const directQuality = patternSize.map(patternItem => {
|
||||
const sizeQuality = getSizeQuality(patternItem);
|
||||
let dURL = `${DOMAIN}${patternItem}`;
|
||||
dURL = dURL.replace(".png", ".m3u8");
|
||||
return { file: dURL, type: 'hls', quality: `${sizeQuality}p`, lang: 'en' };
|
||||
});
|
||||
|
||||
if (!directQuality.length) continue;
|
||||
|
||||
sources.push({
|
||||
provider: "EmbedSu",
|
||||
files: directQuality,
|
||||
headers: {
|
||||
try {
|
||||
const urlDirect = `${DOMAIN}/api/e/${item.hash}`;
|
||||
const dataDirect = await requestGet(urlDirect, {
|
||||
"Referer": DOMAIN,
|
||||
"User-Agent": headers['User-Agent'],
|
||||
"Origin": DOMAIN
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
subtitles.push(...tracks);
|
||||
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;
|
||||
}
|
||||
|
||||
sources.push({
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sources,
|
||||
subtitles
|
||||
};
|
||||
if (sources.length === 0) {
|
||||
return { sources: [createErrorObject("No valid sources found after processing all items")] };
|
||||
}
|
||||
|
||||
return { sources };
|
||||
} catch (e) {
|
||||
return { provider: "EmbedSu", sources: [], subtitles: [] };
|
||||
return { sources: [createErrorObject(`Unexpected error: ${e.message}`)] };
|
||||
}
|
||||
}
|
||||
|
||||
function getSizeQuality(url) {
|
||||
const parts = url.split('/');
|
||||
const base64Part = parts[parts.length - 2];
|
||||
const decodedPart = atob(base64Part);
|
||||
const sizeQuality = Number(decodedPart) || 1080;
|
||||
return sizeQuality;
|
||||
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) {
|
||||
@@ -115,6 +177,7 @@ async function requestGet(url, headers = {}) {
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return "";
|
||||
console.error(`Request failed for ${url}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user