Merge pull request 'Improve AniWorld module and update GlobalExtractor' (#5) from Cufiy/sources-fork:main into main

Reviewed-on: https://git.luna-app.eu/50n50/sources/pulls/5
This commit was merged in pull request #5.
This commit is contained in:
aka paul
2026-01-03 19:10:55 +00:00
21 changed files with 2467 additions and 1295 deletions
+2 -2
View File
@@ -5,7 +5,7 @@
"name": "50/50 & Cufiy",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.2.13",
"version": "1.2.14",
"language": "German (DUB/SUB)",
"streamType": "MP4",
"quality": "1080p",
@@ -17,4 +17,4 @@
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
}
+235 -123
View File
@@ -313,7 +313,7 @@ async function sendLog(message) {
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/* {VERSION: 1.2.0} */
/**
* @name global_extractor.js
@@ -321,8 +321,8 @@ async function sendLog(message) {
* @author Cufiy
* @url https://github.com/JMcrafter26/sora-global-extractor
* @license CUSTOM LICENSE - see https://github.com/JMcrafter26/sora-global-extractor/blob/main/LICENSE
* @date 2025-11-05 15:44:57
* @version 1.1.8
* @date 2026-01-03 19:28:28
* @version 1.2.0
* @note This file was generated automatically.
* The global extractor comes with an auto-updating feature, so you can always get the latest version. https://github.com/JMcrafter26/sora-global-extractor#-auto-updater
*/
@@ -332,12 +332,20 @@ function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is an object with streamUrl property
if (streamUrl && typeof streamUrl === "object" && !Array.isArray(streamUrl) && streamUrl.streamUrl) {
return streamUrl.streamUrl;
}
// check if streamUrl is not null, a string, and starts with http or https
if (streamUrl && typeof streamUrl === "string" && (streamUrl.startsWith("http"))) {
if (
streamUrl &&
typeof streamUrl === "string" &&
streamUrl.startsWith("http")
) {
return streamUrl;
// if its an array, get the value that starts with http
} else if (Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
const httpStream = streamUrl.find((url) => url.startsWith("http"));
if (httpStream) {
return httpStream;
}
@@ -345,7 +353,6 @@ function globalExtractor(providers) {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
@@ -357,16 +364,30 @@ async function multiExtractor(providers) {
/* this scheme should be returned as a JSON object
{
"streams": [
"FileMoon",
"https://filemoon.example/stream1.m3u8",
"StreamWish",
"https://streamwish.example/stream2.m3u8",
"Okru",
"https://okru.example/stream3.m3u8",
"MP4",
"https://mp4upload.example/stream4.mp4",
"Default",
"https://default.example/stream5.m3u8"
{
"title": "FileMoon",
"streamUrl": "https://filemoon.example/stream1.m3u8",
},
{
"title": "StreamWish",
"streamUrl": "https://streamwish.example/stream2.m3u8",
},
{
"title": "Okru",
"streamUrl": "https://okru.example/stream3.m3u8",
"headers": { // Optional headers for the stream
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Referer": "https://okru.example/",
},
},
{
"title": "MP4",
"streamUrl": "https://mp4upload.example/stream4.mp4",
},
{
"title": "Default",
"streamUrl": "https://default.example/stream5.m3u8"
}
]
}
*/
@@ -378,20 +399,21 @@ async function multiExtractor(providers) {
// if provider starts with "direct-", then add the url to the streams array directly
if (provider.startsWith("direct-")) {
const directName = provider.slice(7); // remove "direct-" prefix
if (directName && directName.length > 0) {
streams.push(directName, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (directName && directName.length > 0) ? directName : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
if (provider.startsWith("direct")) {
provider = provider.slice(7); // remove "direct-" prefix
if (provider && provider.length > 0) {
streams.push(provider, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (provider && provider.length > 0) ? provider : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
let customName = null; // to store the custom name if provided
@@ -408,15 +430,24 @@ async function multiExtractor(providers) {
console.log(`Skipping ${provider} as it has already 3 streams`);
continue;
}
let streamUrl = await extractStreamUrlByProvider(url, provider);
if (streamUrl && Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
let result = await extractStreamUrlByProvider(url, provider);
let streamUrl = null;
let headers = null;
// Check if result is an object with streamUrl and optional headers
if (result && typeof result === "object" && !Array.isArray(result) && result.streamUrl) {
streamUrl = result.streamUrl;
headers = result.headers || null;
} else if (result && Array.isArray(result)) {
const httpStream = result.find((url) => url.startsWith("http"));
if (httpStream) {
streamUrl = httpStream;
}
} else if (result && typeof result === "string") {
streamUrl = result;
}
// check if provider is already in streams, if it is, add a number to it
// check if streamUrl is valid
if (
!streamUrl ||
typeof streamUrl !== "string" ||
@@ -430,22 +461,29 @@ async function multiExtractor(providers) {
provider = customName;
}
let title;
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
title = provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
(providersCount[provider] - 1); // add a number to the provider name
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
title = provider.charAt(0).toUpperCase() + provider.slice(1);
}
const streamObject = {
title: title,
streamUrl: streamUrl
};
// Add headers if they exist
if (headers && typeof headers === "object" && Object.keys(headers).length > 0) {
streamObject.headers = headers;
}
streams.push(streamObject);
} catch (error) {
// Ignore the error and try the next provider
}
@@ -456,73 +494,98 @@ async function multiExtractor(providers) {
async function extractStreamUrlByProvider(url, provider) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
// skip if the extractor is not defined
console.log(`Extractor for provider ${provider} is not defined, skipping...`);
console.log(
`Extractor for provider ${provider} is not defined, skipping...`
);
return null;
}
let uas = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
];
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": uas[(url.length + provider.length) % uas.length], // use a different user agent based on the url and provider
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"x-Requested-With": "XMLHttpRequest"
"x-Requested-With": "XMLHttpRequest",
};
if(provider == 'bigwarp') {
delete headers["User-Agent"];
headers["x-requested-with"] = "XMLHttpRequest";
} else if (provider == 'vk') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'sibnet') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'supervideo') {
delete headers["User-Agent"];
switch (provider) {
case "bigwarp":
delete headers["User-Agent"];
break;
case "vk":
case "sibnet":
headers["encoding"] = "windows-1251"; // required
break;
case "supervideo":
case "savefiles":
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "EchoapiRuntime/1.1.0",
"Connection": "keep-alive",
"Cache-Control": "no-cache",
"Host": url.match(/https?:\/\/([^\/]+)/)[1],
};
break;
case "streamtape":
headers = {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
break;
}
// console.log("Using headers: " + JSON.stringify(headers));
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
headers
});
headers,
});
console.log("Response: " + response.status);
let html = response.text ? await response.text() : response;
// if title contains redirect, then get the redirect url
const title = html.match(/<title>(.*?)<\/title>/);
if (title && title[1].toLowerCase().includes("redirect")) {
const redirectUrl = html.match(/<meta http-equiv="refresh" content="0;url=(.*?)"/);
const redirectUrl2 = html.match(/window\.location\.href\s*=\s*["'](.*?)["']/);
const redirectUrl3 = html.match(/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/);
if (redirectUrl) {
console.log("Redirect URL: " + redirectUrl[1]);
url = redirectUrl[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl2) {
console.log("Redirect URL 2: " + redirectUrl2[1]);
url = redirectUrl2[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl3) {
console.log("Redirect URL 3: " + redirectUrl3[1]);
url = redirectUrl3[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else {
console.log("No redirect URL found");
const matches = [
/<meta http-equiv="refresh" content="0;url=(.*?)"/,
/window\.location\.href\s*=\s*["'](.*?)["']/,
/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
/window\.location\s*=\s*["'](.*?)["']/,
/window\.location\.assign\s*\(\s*["'](.*?)["']\s*\)/,
/top\.location\s*=\s*["'](.*?)["']/,
/top\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
];
for (const match of matches) {
const redirectUrl = html.match(match);
if (redirectUrl && redirectUrl[1] && typeof redirectUrl[1] === "string" && redirectUrl[1].startsWith("http")) {
console.log("Redirect URL found: " + redirectUrl[1]);
url = redirectUrl[1];
headers['Referer'] = url;
headers['Host'] = url.match(/https?:\/\/([^\/]+)/)[1];
html = await soraFetch(url, {
headers,
}).then((res) => res.text());
break;
}
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
@@ -571,6 +634,20 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "oneupload":
try {
return await oneuploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from oneupload:", error);
return null;
}
case "packer":
try {
return await packerExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from packer:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
@@ -585,6 +662,13 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "smoothpre":
try {
return await smoothpreExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from smoothpre:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
@@ -599,13 +683,6 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from streamup:", error);
return null;
}
case "supervideo":
try {
return await supervideoExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from supervideo:", error);
return null;
}
case "uploadcx":
try {
return await uploadcxExtractor(html, url);
@@ -654,7 +731,6 @@ async function extractStreamUrlByProvider(url, provider) {
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
@@ -1141,7 +1217,6 @@ async function megacloudExtractor(html, embedUrl) {
* @author Cufiy
*/
async function mp4uploadExtractor(html, url = null) {
// src: "https://a4.mp4upload.com:183/d/xkx3b4etz3b4quuo66rbmyqtjjoivahfxp27f35pti45rzapbvj5xwb4wuqtlpewdz4dirfp/video.mp4"
const regex = /src:\s*"([^"]+)"/;
const match = html.match(regex);
if (match) {
@@ -1151,6 +1226,32 @@ async function mp4uploadExtractor(html, url = null) {
return null;
}
}
/* --- oneupload --- */
/**
* @name oneuploadExtractor
* @author 50/50
*/
async function oneuploadExtractor(data, url = null) {
const match = data.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
}
/* --- packer --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name packerExtractor
* @author 50/50
*/
async function packerExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const m3u8Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const m3u8Url = m3u8Match[1];
return m3u8Url;
}
/* --- sendvid --- */
/**
@@ -1186,6 +1287,29 @@ async function sibnetExtractor(html, embedUrl) {
return null;
}
}
/* --- smoothpre --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SmoothPre Extractor
* @author 50/50
*/
async function smoothpreExtractor(data, url = null) {
console.log("Using SmoothPre Extractor");
console.log("Data Length: " + data.length);
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript || !obfuscatedScript[1]) {
console.log("No obfuscated script found");
return null;
}
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
}
/* --- streamtape --- */
/**
@@ -1269,26 +1393,6 @@ async function streamupExtractor(data, url = null) {
return null;
}
}
/* --- supervideo --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SuperVideo Extractor
* @author 50/50
*/
async function supervideoExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const regex = /file:\s*"([^"]+\.m3u8)"/;
const match = regex.exec(unpackedScript);
if (match) {
const fileUrl = match[1];
console.log("File URL:" + fileUrl);
return fileUrl;
}
return "No stream found";
}
/* --- uploadcx --- */
/**
@@ -1353,7 +1457,7 @@ async function vidmolyExtractor(html, url = null) {
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await soraFetch(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
@@ -1479,6 +1583,7 @@ function voeShiftChars(str, shift) {
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
@@ -1496,17 +1601,25 @@ function voeShiftChars(str, shift) {
* @returns {Promise<Response|null>} The response from the server, or null if the
* request failed.
*/
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null
);
} catch (e) {
try {
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
await console.log('soraFetch error: ' + error.message);
return null;
}
return await fetch(url, options);
} catch (error) {
await console.log("soraFetch error: " + error.message);
return null;
}
}
}
/***********************************************************
* UNPACKER MODULE
@@ -1610,6 +1723,5 @@ function unpack(source) {
}
}
/* {GE END} */
+5 -4
View File
@@ -2,10 +2,11 @@
"sourceName": "AniWorld (ENG SUB)",
"iconUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/aniworld/aniworld.png",
"author": {
"name": "Hamzo & Cufiy",
"icon": "https://cdn.discordapp.com/avatars/623644371819954226/591ecab10b0b4535e859bb0b9bbe62e5?size=1024"
"name": "Cufiy & Hamzo",
"icon": "https://files.catbox.moe/ttj4fc.gif",
"url": "https://github.com/JMcrafter26"
},
"version": "0.2.8",
"version": "0.3.1",
"language": "English (SUB)",
"streamType": "HLS",
"quality": "720p",
@@ -18,4 +19,4 @@
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
}
+4 -3
View File
@@ -3,15 +3,16 @@
"iconUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/aniworld/aniworld.png",
"author": {
"name": "Cufiy",
"icon": "https://cdn.discordapp.com/avatars/623644371819954226/591ecab10b0b4535e859bb0b9bbe62e5?size=1024"
"icon": "https://files.catbox.moe/ttj4fc.gif",
"url": "https://github.com/JMcrafter26"
},
"version": "0.2.52",
"version": "0.3.194",
"language": "German (DUB)",
"streamType": "HLS",
"quality": "720p",
"baseUrl": "https://vidmoly.to/",
"searchBaseUrl": "https://aniworld.to/ajax/seriesSearch?keyword=%s",
"scriptUrl": "http://192.168.2.130/sora-module-repos/sources/aniworld/v2/AniWorldGerDub_v2.js",
"scriptUrl": "http://192.168.2.130/sora-module-repos/sources-fork/aniworld/v2/AniWorldGerDub_v2.js",
"asyncJS": true,
"type": "anime",
"supportsMojuru": true,
+5 -4
View File
@@ -2,10 +2,11 @@
"sourceName": "AniWorld (GER DUB)",
"iconUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/aniworld/aniworld.png",
"author": {
"name": "Hamzo & Cufiy",
"icon": "https://cdn.discordapp.com/avatars/623644371819954226/591ecab10b0b4535e859bb0b9bbe62e5?size=1024"
"name": "Cufiy & Hamzo",
"icon": "https://files.catbox.moe/ttj4fc.gif",
"url": "https://github.com/JMcrafter26"
},
"version": "0.2.8",
"version": "0.3.1",
"language": "German (DUB)",
"streamType": "HLS",
"quality": "720p",
@@ -18,4 +19,4 @@
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
}
+5 -4
View File
@@ -2,10 +2,11 @@
"sourceName": "AniWorld (GER SUB)",
"iconUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/aniworld/aniworld.png",
"author": {
"name": "Hamzo & Cufiy",
"icon": "https://cdn.discordapp.com/avatars/623644371819954226/591ecab10b0b4535e859bb0b9bbe62e5?size=1024"
"name": "Cufiy & Hamzo",
"icon": "https://files.catbox.moe/ttj4fc.gif",
"url": "https://github.com/JMcrafter26"
},
"version": "0.2.8",
"version": "0.3.1",
"language": "German (SUB)",
"streamType": "HLS",
"quality": "720p",
@@ -18,4 +19,4 @@
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
}
+264 -137
View File
@@ -6,9 +6,9 @@ async function searchResults(keyword) {
try {
const encodedKeyword = encodeURIComponent(keyword);
const searchApiUrl = `https://aniworld.to/ajax/seriesSearch?keyword=${encodedKeyword}`;
const responseText = await fetch(searchApiUrl);
const responseText = await soraFetch(searchApiUrl);
// console.log("Search API Response: " + await responseText.text());
const data = await JSON.parse(responseText);
const data = await responseText.json() || await JSON.parse(responseText);
console.log("Search API Data: ", data);
const transformedResults = data.map((anime) => ({
@@ -84,9 +84,9 @@ async function extractEpisodes(url) {
}
// Replace the field "number" with the current index of each item, starting from 1
finishedList.forEach((item, index) => {
item.number = index + 1;
});
// finishedList.forEach((item, index) => {
// item.number = index + 1;
// });
return JSON.stringify(finishedList);
} catch (error) {
@@ -99,6 +99,7 @@ async function extractStreamUrl(url) {
try {
const baseUrl = "https://aniworld.to";
const fetchUrl = `${url}`;
sendLog("Fetching URL: " + fetchUrl);
const response = await fetch(fetchUrl);
const text = response.text ? await response.text() : response;
@@ -177,9 +178,7 @@ function selectHoster(finishedList) {
// Define the preferred providers and languages
const providerList = ["VOE", "Filemoon", "SpeedFiles", "Vidmoly", "DoodStream", "Vidoza", "mp4upload"];
const languageList = ["mit Untertitel Englisch", "mit Untertitel Deutsch", "Deutsch"];
const languageList = ["mit Untertitel Englisch", "Englisch", "mit Untertitel Deutsch", "Deutsch"];
for (const language of languageList) {
for (const providerName of providerList) {
@@ -249,23 +248,39 @@ async function fetchSeasonEpisodes(url) {
const response = await fetch(fetchUrl);
const text = response.text ? await response.text() : response;
// if is filme, e.g. https://aniworld.to/anime/stream/jujutsu-kaisen/filme
let isFilme = false;
if (url.endsWith("/filme") || url.includes("/filme/")) {
isFilme = true;
}
// Updated regex to allow empty <strong> content
const regex =
/<td class="seasonEpisodeTitle">\s*<a[^>]*href="([^"]+)"[^>]*>.*?<strong>([^<]*)<\/strong>.*?<span>([^<]+)<\/span>.*?<\/a>/g;
const matches = [];
let match;
let holderNumber = 0;
let number = 0;
while ((match = regex.exec(text)) !== null) {
const [_, link] = match;
matches.push({ number: holderNumber, href: `${baseUrl}${link}` });
const [_, link, titleRaw, span] = match;
number += 1;
// sendLog("Episode found:", { number, link, title, span });
let title = titleRaw.trim() || span.trim();
if (isFilme) {
title = `[FILM] ${title || span.trim() || "Untitled"}`;
}
matches.push({ number, href: `${baseUrl}${link}`, title});
}
sendLog("Season Episodes:" + JSON.stringify(matches));
return matches;
} catch (error) {
sendLog("FetchSeasonEpisodes helper function error:" + error);
return [{ number: "0", href: "https://error.org" }];
return [{ number: "0", href: "https://error.org", title: "Error" }];
}
}
@@ -337,7 +352,7 @@ function base64Decode(str) {
async function sendLog(message) {
// send http://192.168.2.130/sora-module/log.php?action=add&message=message
console.log(message);
return;
// return;
await fetch('http://192.168.2.130/sora-module/log.php?action=add&message=' + encodeURIComponent(message))
.catch(error => {
@@ -350,7 +365,7 @@ async function sendLog(message) {
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/* {VERSION: 1.2.0} */
/**
* @name global_extractor.js
@@ -358,8 +373,8 @@ async function sendLog(message) {
* @author Cufiy
* @url https://github.com/JMcrafter26/sora-global-extractor
* @license CUSTOM LICENSE - see https://github.com/JMcrafter26/sora-global-extractor/blob/main/LICENSE
* @date 2025-11-05 15:44:57
* @version 1.1.8
* @date 2026-01-03 19:28:28
* @version 1.2.0
* @note This file was generated automatically.
* The global extractor comes with an auto-updating feature, so you can always get the latest version. https://github.com/JMcrafter26/sora-global-extractor#-auto-updater
*/
@@ -369,12 +384,20 @@ function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is an object with streamUrl property
if (streamUrl && typeof streamUrl === "object" && !Array.isArray(streamUrl) && streamUrl.streamUrl) {
return streamUrl.streamUrl;
}
// check if streamUrl is not null, a string, and starts with http or https
if (streamUrl && typeof streamUrl === "string" && (streamUrl.startsWith("http"))) {
if (
streamUrl &&
typeof streamUrl === "string" &&
streamUrl.startsWith("http")
) {
return streamUrl;
// if its an array, get the value that starts with http
} else if (Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
const httpStream = streamUrl.find((url) => url.startsWith("http"));
if (httpStream) {
return httpStream;
}
@@ -382,7 +405,6 @@ function globalExtractor(providers) {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
@@ -394,16 +416,30 @@ async function multiExtractor(providers) {
/* this scheme should be returned as a JSON object
{
"streams": [
"FileMoon",
"https://filemoon.example/stream1.m3u8",
"StreamWish",
"https://streamwish.example/stream2.m3u8",
"Okru",
"https://okru.example/stream3.m3u8",
"MP4",
"https://mp4upload.example/stream4.mp4",
"Default",
"https://default.example/stream5.m3u8"
{
"title": "FileMoon",
"streamUrl": "https://filemoon.example/stream1.m3u8",
},
{
"title": "StreamWish",
"streamUrl": "https://streamwish.example/stream2.m3u8",
},
{
"title": "Okru",
"streamUrl": "https://okru.example/stream3.m3u8",
"headers": { // Optional headers for the stream
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Referer": "https://okru.example/",
},
},
{
"title": "MP4",
"streamUrl": "https://mp4upload.example/stream4.mp4",
},
{
"title": "Default",
"streamUrl": "https://default.example/stream5.m3u8"
}
]
}
*/
@@ -415,20 +451,21 @@ async function multiExtractor(providers) {
// if provider starts with "direct-", then add the url to the streams array directly
if (provider.startsWith("direct-")) {
const directName = provider.slice(7); // remove "direct-" prefix
if (directName && directName.length > 0) {
streams.push(directName, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (directName && directName.length > 0) ? directName : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
if (provider.startsWith("direct")) {
provider = provider.slice(7); // remove "direct-" prefix
if (provider && provider.length > 0) {
streams.push(provider, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (provider && provider.length > 0) ? provider : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
let customName = null; // to store the custom name if provided
@@ -445,15 +482,24 @@ async function multiExtractor(providers) {
console.log(`Skipping ${provider} as it has already 3 streams`);
continue;
}
let streamUrl = await extractStreamUrlByProvider(url, provider);
if (streamUrl && Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
let result = await extractStreamUrlByProvider(url, provider);
let streamUrl = null;
let headers = null;
// Check if result is an object with streamUrl and optional headers
if (result && typeof result === "object" && !Array.isArray(result) && result.streamUrl) {
streamUrl = result.streamUrl;
headers = result.headers || null;
} else if (result && Array.isArray(result)) {
const httpStream = result.find((url) => url.startsWith("http"));
if (httpStream) {
streamUrl = httpStream;
}
} else if (result && typeof result === "string") {
streamUrl = result;
}
// check if provider is already in streams, if it is, add a number to it
// check if streamUrl is valid
if (
!streamUrl ||
typeof streamUrl !== "string" ||
@@ -467,22 +513,29 @@ async function multiExtractor(providers) {
provider = customName;
}
let title;
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
title = provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
(providersCount[provider] - 1); // add a number to the provider name
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
title = provider.charAt(0).toUpperCase() + provider.slice(1);
}
const streamObject = {
title: title,
streamUrl: streamUrl
};
// Add headers if they exist
if (headers && typeof headers === "object" && Object.keys(headers).length > 0) {
streamObject.headers = headers;
}
streams.push(streamObject);
} catch (error) {
// Ignore the error and try the next provider
}
@@ -493,73 +546,98 @@ async function multiExtractor(providers) {
async function extractStreamUrlByProvider(url, provider) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
// skip if the extractor is not defined
console.log(`Extractor for provider ${provider} is not defined, skipping...`);
console.log(
`Extractor for provider ${provider} is not defined, skipping...`
);
return null;
}
let uas = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
];
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": uas[(url.length + provider.length) % uas.length], // use a different user agent based on the url and provider
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"x-Requested-With": "XMLHttpRequest"
"x-Requested-With": "XMLHttpRequest",
};
if(provider == 'bigwarp') {
delete headers["User-Agent"];
headers["x-requested-with"] = "XMLHttpRequest";
} else if (provider == 'vk') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'sibnet') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'supervideo') {
delete headers["User-Agent"];
switch (provider) {
case "bigwarp":
delete headers["User-Agent"];
break;
case "vk":
case "sibnet":
headers["encoding"] = "windows-1251"; // required
break;
case "supervideo":
case "savefiles":
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "EchoapiRuntime/1.1.0",
"Connection": "keep-alive",
"Cache-Control": "no-cache",
"Host": url.match(/https?:\/\/([^\/]+)/)[1],
};
break;
case "streamtape":
headers = {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
break;
}
// console.log("Using headers: " + JSON.stringify(headers));
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
headers
});
headers,
});
console.log("Response: " + response.status);
let html = response.text ? await response.text() : response;
// if title contains redirect, then get the redirect url
const title = html.match(/<title>(.*?)<\/title>/);
if (title && title[1].toLowerCase().includes("redirect")) {
const redirectUrl = html.match(/<meta http-equiv="refresh" content="0;url=(.*?)"/);
const redirectUrl2 = html.match(/window\.location\.href\s*=\s*["'](.*?)["']/);
const redirectUrl3 = html.match(/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/);
if (redirectUrl) {
console.log("Redirect URL: " + redirectUrl[1]);
url = redirectUrl[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl2) {
console.log("Redirect URL 2: " + redirectUrl2[1]);
url = redirectUrl2[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl3) {
console.log("Redirect URL 3: " + redirectUrl3[1]);
url = redirectUrl3[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else {
console.log("No redirect URL found");
const matches = [
/<meta http-equiv="refresh" content="0;url=(.*?)"/,
/window\.location\.href\s*=\s*["'](.*?)["']/,
/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
/window\.location\s*=\s*["'](.*?)["']/,
/window\.location\.assign\s*\(\s*["'](.*?)["']\s*\)/,
/top\.location\s*=\s*["'](.*?)["']/,
/top\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
];
for (const match of matches) {
const redirectUrl = html.match(match);
if (redirectUrl && redirectUrl[1] && typeof redirectUrl[1] === "string" && redirectUrl[1].startsWith("http")) {
console.log("Redirect URL found: " + redirectUrl[1]);
url = redirectUrl[1];
headers['Referer'] = url;
headers['Host'] = url.match(/https?:\/\/([^\/]+)/)[1];
html = await soraFetch(url, {
headers,
}).then((res) => res.text());
break;
}
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
@@ -608,6 +686,20 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "oneupload":
try {
return await oneuploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from oneupload:", error);
return null;
}
case "packer":
try {
return await packerExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from packer:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
@@ -622,6 +714,13 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "smoothpre":
try {
return await smoothpreExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from smoothpre:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
@@ -636,13 +735,6 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from streamup:", error);
return null;
}
case "supervideo":
try {
return await supervideoExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from supervideo:", error);
return null;
}
case "uploadcx":
try {
return await uploadcxExtractor(html, url);
@@ -691,7 +783,6 @@ async function extractStreamUrlByProvider(url, provider) {
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
@@ -1178,7 +1269,6 @@ async function megacloudExtractor(html, embedUrl) {
* @author Cufiy
*/
async function mp4uploadExtractor(html, url = null) {
// src: "https://a4.mp4upload.com:183/d/xkx3b4etz3b4quuo66rbmyqtjjoivahfxp27f35pti45rzapbvj5xwb4wuqtlpewdz4dirfp/video.mp4"
const regex = /src:\s*"([^"]+)"/;
const match = html.match(regex);
if (match) {
@@ -1188,6 +1278,32 @@ async function mp4uploadExtractor(html, url = null) {
return null;
}
}
/* --- oneupload --- */
/**
* @name oneuploadExtractor
* @author 50/50
*/
async function oneuploadExtractor(data, url = null) {
const match = data.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
}
/* --- packer --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name packerExtractor
* @author 50/50
*/
async function packerExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const m3u8Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const m3u8Url = m3u8Match[1];
return m3u8Url;
}
/* --- sendvid --- */
/**
@@ -1223,6 +1339,29 @@ async function sibnetExtractor(html, embedUrl) {
return null;
}
}
/* --- smoothpre --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SmoothPre Extractor
* @author 50/50
*/
async function smoothpreExtractor(data, url = null) {
console.log("Using SmoothPre Extractor");
console.log("Data Length: " + data.length);
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript || !obfuscatedScript[1]) {
console.log("No obfuscated script found");
return null;
}
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
}
/* --- streamtape --- */
/**
@@ -1306,26 +1445,6 @@ async function streamupExtractor(data, url = null) {
return null;
}
}
/* --- supervideo --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SuperVideo Extractor
* @author 50/50
*/
async function supervideoExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const regex = /file:\s*"([^"]+\.m3u8)"/;
const match = regex.exec(unpackedScript);
if (match) {
const fileUrl = match[1];
console.log("File URL:" + fileUrl);
return fileUrl;
}
return "No stream found";
}
/* --- uploadcx --- */
/**
@@ -1390,7 +1509,7 @@ async function vidmolyExtractor(html, url = null) {
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await soraFetch(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
@@ -1516,6 +1635,7 @@ function voeShiftChars(str, shift) {
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
@@ -1533,17 +1653,25 @@ function voeShiftChars(str, shift) {
* @returns {Promise<Response|null>} The response from the server, or null if the
* request failed.
*/
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null
);
} catch (e) {
try {
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
await console.log('soraFetch error: ' + error.message);
return null;
}
return await fetch(url, options);
} catch (error) {
await console.log("soraFetch error: " + error.message);
return null;
}
}
}
/***********************************************************
* UNPACKER MODULE
@@ -1647,6 +1775,5 @@ function unpack(source) {
}
}
/* {GE END} */
/* {GE END} */
+262 -134
View File
@@ -6,9 +6,9 @@ async function searchResults(keyword) {
try {
const encodedKeyword = encodeURIComponent(keyword);
const searchApiUrl = `https://aniworld.to/ajax/seriesSearch?keyword=${encodedKeyword}`;
const responseText = await fetch(searchApiUrl);
const responseText = await soraFetch(searchApiUrl);
// console.log("Search API Response: " + await responseText.text());
const data = await JSON.parse(responseText);
const data = await responseText.json() || await JSON.parse(responseText);
console.log("Search API Data: ", data);
const transformedResults = data.map((anime) => ({
@@ -84,9 +84,9 @@ async function extractEpisodes(url) {
}
// Replace the field "number" with the current index of each item, starting from 1
finishedList.forEach((item, index) => {
item.number = index + 1;
});
// finishedList.forEach((item, index) => {
// item.number = index + 1;
// });
return JSON.stringify(finishedList);
} catch (error) {
@@ -250,23 +250,39 @@ async function fetchSeasonEpisodes(url) {
const response = await fetch(fetchUrl);
const text = response.text ? await response.text() : response;
// if is filme, e.g. https://aniworld.to/anime/stream/jujutsu-kaisen/filme
let isFilme = false;
if (url.endsWith("/filme") || url.includes("/filme/")) {
isFilme = true;
}
// Updated regex to allow empty <strong> content
const regex =
/<td class="seasonEpisodeTitle">\s*<a[^>]*href="([^"]+)"[^>]*>.*?<strong>([^<]*)<\/strong>.*?<span>([^<]+)<\/span>.*?<\/a>/g;
const matches = [];
let match;
let holderNumber = 0;
let number = 0;
while ((match = regex.exec(text)) !== null) {
const [_, link] = match;
matches.push({ number: holderNumber, href: `${baseUrl}${link}` });
const [_, link, titleRaw, span] = match;
number += 1;
// sendLog("Episode found:", { number, link, title, span });
let title = titleRaw.trim() || span.trim();
if (isFilme) {
title = `[FILM] ${title || span.trim() || "Untitled"}`;
}
matches.push({ number, href: `${baseUrl}${link}`, title});
}
sendLog("Season Episodes:" + JSON.stringify(matches));
return matches;
} catch (error) {
sendLog("FetchSeasonEpisodes helper function error:" + error);
return [{ number: "0", href: "https://error.org" }];
return [{ number: "0", href: "https://error.org", title: "Error" }];
}
}
@@ -338,7 +354,7 @@ function base64Decode(str) {
async function sendLog(message) {
// send http://192.168.2.130/sora-module/log.php?action=add&message=message
console.log(message);
return;
// return;
await fetch('http://192.168.2.130/sora-module/log.php?action=add&message=' + encodeURIComponent(message))
.catch(error => {
@@ -351,7 +367,7 @@ async function sendLog(message) {
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/* {VERSION: 1.2.0} */
/**
* @name global_extractor.js
@@ -359,8 +375,8 @@ async function sendLog(message) {
* @author Cufiy
* @url https://github.com/JMcrafter26/sora-global-extractor
* @license CUSTOM LICENSE - see https://github.com/JMcrafter26/sora-global-extractor/blob/main/LICENSE
* @date 2025-11-05 15:44:57
* @version 1.1.8
* @date 2026-01-03 19:28:28
* @version 1.2.0
* @note This file was generated automatically.
* The global extractor comes with an auto-updating feature, so you can always get the latest version. https://github.com/JMcrafter26/sora-global-extractor#-auto-updater
*/
@@ -370,12 +386,20 @@ function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is an object with streamUrl property
if (streamUrl && typeof streamUrl === "object" && !Array.isArray(streamUrl) && streamUrl.streamUrl) {
return streamUrl.streamUrl;
}
// check if streamUrl is not null, a string, and starts with http or https
if (streamUrl && typeof streamUrl === "string" && (streamUrl.startsWith("http"))) {
if (
streamUrl &&
typeof streamUrl === "string" &&
streamUrl.startsWith("http")
) {
return streamUrl;
// if its an array, get the value that starts with http
} else if (Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
const httpStream = streamUrl.find((url) => url.startsWith("http"));
if (httpStream) {
return httpStream;
}
@@ -383,7 +407,6 @@ function globalExtractor(providers) {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
@@ -395,16 +418,30 @@ async function multiExtractor(providers) {
/* this scheme should be returned as a JSON object
{
"streams": [
"FileMoon",
"https://filemoon.example/stream1.m3u8",
"StreamWish",
"https://streamwish.example/stream2.m3u8",
"Okru",
"https://okru.example/stream3.m3u8",
"MP4",
"https://mp4upload.example/stream4.mp4",
"Default",
"https://default.example/stream5.m3u8"
{
"title": "FileMoon",
"streamUrl": "https://filemoon.example/stream1.m3u8",
},
{
"title": "StreamWish",
"streamUrl": "https://streamwish.example/stream2.m3u8",
},
{
"title": "Okru",
"streamUrl": "https://okru.example/stream3.m3u8",
"headers": { // Optional headers for the stream
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Referer": "https://okru.example/",
},
},
{
"title": "MP4",
"streamUrl": "https://mp4upload.example/stream4.mp4",
},
{
"title": "Default",
"streamUrl": "https://default.example/stream5.m3u8"
}
]
}
*/
@@ -416,20 +453,21 @@ async function multiExtractor(providers) {
// if provider starts with "direct-", then add the url to the streams array directly
if (provider.startsWith("direct-")) {
const directName = provider.slice(7); // remove "direct-" prefix
if (directName && directName.length > 0) {
streams.push(directName, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (directName && directName.length > 0) ? directName : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
if (provider.startsWith("direct")) {
provider = provider.slice(7); // remove "direct-" prefix
if (provider && provider.length > 0) {
streams.push(provider, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (provider && provider.length > 0) ? provider : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
let customName = null; // to store the custom name if provided
@@ -446,15 +484,24 @@ async function multiExtractor(providers) {
console.log(`Skipping ${provider} as it has already 3 streams`);
continue;
}
let streamUrl = await extractStreamUrlByProvider(url, provider);
if (streamUrl && Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
let result = await extractStreamUrlByProvider(url, provider);
let streamUrl = null;
let headers = null;
// Check if result is an object with streamUrl and optional headers
if (result && typeof result === "object" && !Array.isArray(result) && result.streamUrl) {
streamUrl = result.streamUrl;
headers = result.headers || null;
} else if (result && Array.isArray(result)) {
const httpStream = result.find((url) => url.startsWith("http"));
if (httpStream) {
streamUrl = httpStream;
}
} else if (result && typeof result === "string") {
streamUrl = result;
}
// check if provider is already in streams, if it is, add a number to it
// check if streamUrl is valid
if (
!streamUrl ||
typeof streamUrl !== "string" ||
@@ -468,22 +515,29 @@ async function multiExtractor(providers) {
provider = customName;
}
let title;
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
title = provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
(providersCount[provider] - 1); // add a number to the provider name
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
title = provider.charAt(0).toUpperCase() + provider.slice(1);
}
const streamObject = {
title: title,
streamUrl: streamUrl
};
// Add headers if they exist
if (headers && typeof headers === "object" && Object.keys(headers).length > 0) {
streamObject.headers = headers;
}
streams.push(streamObject);
} catch (error) {
// Ignore the error and try the next provider
}
@@ -494,73 +548,98 @@ async function multiExtractor(providers) {
async function extractStreamUrlByProvider(url, provider) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
// skip if the extractor is not defined
console.log(`Extractor for provider ${provider} is not defined, skipping...`);
console.log(
`Extractor for provider ${provider} is not defined, skipping...`
);
return null;
}
let uas = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
];
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": uas[(url.length + provider.length) % uas.length], // use a different user agent based on the url and provider
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"x-Requested-With": "XMLHttpRequest"
"x-Requested-With": "XMLHttpRequest",
};
if(provider == 'bigwarp') {
delete headers["User-Agent"];
headers["x-requested-with"] = "XMLHttpRequest";
} else if (provider == 'vk') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'sibnet') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'supervideo') {
delete headers["User-Agent"];
switch (provider) {
case "bigwarp":
delete headers["User-Agent"];
break;
case "vk":
case "sibnet":
headers["encoding"] = "windows-1251"; // required
break;
case "supervideo":
case "savefiles":
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "EchoapiRuntime/1.1.0",
"Connection": "keep-alive",
"Cache-Control": "no-cache",
"Host": url.match(/https?:\/\/([^\/]+)/)[1],
};
break;
case "streamtape":
headers = {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
break;
}
// console.log("Using headers: " + JSON.stringify(headers));
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
headers
});
headers,
});
console.log("Response: " + response.status);
let html = response.text ? await response.text() : response;
// if title contains redirect, then get the redirect url
const title = html.match(/<title>(.*?)<\/title>/);
if (title && title[1].toLowerCase().includes("redirect")) {
const redirectUrl = html.match(/<meta http-equiv="refresh" content="0;url=(.*?)"/);
const redirectUrl2 = html.match(/window\.location\.href\s*=\s*["'](.*?)["']/);
const redirectUrl3 = html.match(/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/);
if (redirectUrl) {
console.log("Redirect URL: " + redirectUrl[1]);
url = redirectUrl[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl2) {
console.log("Redirect URL 2: " + redirectUrl2[1]);
url = redirectUrl2[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl3) {
console.log("Redirect URL 3: " + redirectUrl3[1]);
url = redirectUrl3[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else {
console.log("No redirect URL found");
const matches = [
/<meta http-equiv="refresh" content="0;url=(.*?)"/,
/window\.location\.href\s*=\s*["'](.*?)["']/,
/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
/window\.location\s*=\s*["'](.*?)["']/,
/window\.location\.assign\s*\(\s*["'](.*?)["']\s*\)/,
/top\.location\s*=\s*["'](.*?)["']/,
/top\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
];
for (const match of matches) {
const redirectUrl = html.match(match);
if (redirectUrl && redirectUrl[1] && typeof redirectUrl[1] === "string" && redirectUrl[1].startsWith("http")) {
console.log("Redirect URL found: " + redirectUrl[1]);
url = redirectUrl[1];
headers['Referer'] = url;
headers['Host'] = url.match(/https?:\/\/([^\/]+)/)[1];
html = await soraFetch(url, {
headers,
}).then((res) => res.text());
break;
}
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
@@ -609,6 +688,20 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "oneupload":
try {
return await oneuploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from oneupload:", error);
return null;
}
case "packer":
try {
return await packerExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from packer:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
@@ -623,6 +716,13 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "smoothpre":
try {
return await smoothpreExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from smoothpre:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
@@ -637,13 +737,6 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from streamup:", error);
return null;
}
case "supervideo":
try {
return await supervideoExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from supervideo:", error);
return null;
}
case "uploadcx":
try {
return await uploadcxExtractor(html, url);
@@ -692,7 +785,6 @@ async function extractStreamUrlByProvider(url, provider) {
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
@@ -1179,7 +1271,6 @@ async function megacloudExtractor(html, embedUrl) {
* @author Cufiy
*/
async function mp4uploadExtractor(html, url = null) {
// src: "https://a4.mp4upload.com:183/d/xkx3b4etz3b4quuo66rbmyqtjjoivahfxp27f35pti45rzapbvj5xwb4wuqtlpewdz4dirfp/video.mp4"
const regex = /src:\s*"([^"]+)"/;
const match = html.match(regex);
if (match) {
@@ -1189,6 +1280,32 @@ async function mp4uploadExtractor(html, url = null) {
return null;
}
}
/* --- oneupload --- */
/**
* @name oneuploadExtractor
* @author 50/50
*/
async function oneuploadExtractor(data, url = null) {
const match = data.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
}
/* --- packer --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name packerExtractor
* @author 50/50
*/
async function packerExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const m3u8Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const m3u8Url = m3u8Match[1];
return m3u8Url;
}
/* --- sendvid --- */
/**
@@ -1224,6 +1341,29 @@ async function sibnetExtractor(html, embedUrl) {
return null;
}
}
/* --- smoothpre --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SmoothPre Extractor
* @author 50/50
*/
async function smoothpreExtractor(data, url = null) {
console.log("Using SmoothPre Extractor");
console.log("Data Length: " + data.length);
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript || !obfuscatedScript[1]) {
console.log("No obfuscated script found");
return null;
}
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
}
/* --- streamtape --- */
/**
@@ -1307,26 +1447,6 @@ async function streamupExtractor(data, url = null) {
return null;
}
}
/* --- supervideo --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SuperVideo Extractor
* @author 50/50
*/
async function supervideoExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const regex = /file:\s*"([^"]+\.m3u8)"/;
const match = regex.exec(unpackedScript);
if (match) {
const fileUrl = match[1];
console.log("File URL:" + fileUrl);
return fileUrl;
}
return "No stream found";
}
/* --- uploadcx --- */
/**
@@ -1391,7 +1511,7 @@ async function vidmolyExtractor(html, url = null) {
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await soraFetch(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
@@ -1517,6 +1637,7 @@ function voeShiftChars(str, shift) {
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
@@ -1534,17 +1655,25 @@ function voeShiftChars(str, shift) {
* @returns {Promise<Response|null>} The response from the server, or null if the
* request failed.
*/
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null
);
} catch (e) {
try {
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
await console.log('soraFetch error: ' + error.message);
return null;
}
return await fetch(url, options);
} catch (error) {
await console.log("soraFetch error: " + error.message);
return null;
}
}
}
/***********************************************************
* UNPACKER MODULE
@@ -1648,6 +1777,5 @@ function unpack(source) {
}
}
/* {GE END} */
/* {GE END} */
+263 -134
View File
@@ -6,9 +6,9 @@ async function searchResults(keyword) {
try {
const encodedKeyword = encodeURIComponent(keyword);
const searchApiUrl = `https://aniworld.to/ajax/seriesSearch?keyword=${encodedKeyword}`;
const responseText = await fetch(searchApiUrl);
const responseText = await soraFetch(searchApiUrl);
// console.log("Search API Response: " + await responseText.text());
const data = await JSON.parse(responseText);
const data = await responseText.json() || await JSON.parse(responseText);
console.log("Search API Data: ", data);
const transformedResults = data.map((anime) => ({
@@ -84,9 +84,9 @@ async function extractEpisodes(url) {
}
// Replace the field "number" with the current index of each item, starting from 1
finishedList.forEach((item, index) => {
item.number = index + 1;
});
// finishedList.forEach((item, index) => {
// item.number = index + 1;
// });
return JSON.stringify(finishedList);
} catch (error) {
@@ -99,6 +99,7 @@ async function extractStreamUrl(url) {
try {
const baseUrl = "https://aniworld.to";
const fetchUrl = `${url}`;
sendLog("Fetching URL: " + fetchUrl);
const response = await fetch(fetchUrl);
const text = response.text ? await response.text() : response;
@@ -249,23 +250,39 @@ async function fetchSeasonEpisodes(url) {
const response = await fetch(fetchUrl);
const text = response.text ? await response.text() : response;
// if is filme, e.g. https://aniworld.to/anime/stream/jujutsu-kaisen/filme
let isFilme = false;
if (url.endsWith("/filme") || url.includes("/filme/")) {
isFilme = true;
}
// Updated regex to allow empty <strong> content
const regex =
/<td class="seasonEpisodeTitle">\s*<a[^>]*href="([^"]+)"[^>]*>.*?<strong>([^<]*)<\/strong>.*?<span>([^<]+)<\/span>.*?<\/a>/g;
const matches = [];
let match;
let holderNumber = 0;
let number = 0;
while ((match = regex.exec(text)) !== null) {
const [_, link] = match;
matches.push({ number: holderNumber, href: `${baseUrl}${link}` });
const [_, link, titleRaw, span] = match;
number += 1;
// sendLog("Episode found:", { number, link, title, span });
let title = titleRaw.trim() || span.trim();
if (isFilme) {
title = `[FILM] ${title || span.trim() || "Untitled"}`;
}
matches.push({ number, href: `${baseUrl}${link}`, title});
}
sendLog("Season Episodes:" + JSON.stringify(matches));
return matches;
} catch (error) {
sendLog("FetchSeasonEpisodes helper function error:" + error);
return [{ number: "0", href: "https://error.org" }];
return [{ number: "0", href: "https://error.org", title: "Error" }];
}
}
@@ -337,7 +354,7 @@ function base64Decode(str) {
async function sendLog(message) {
// send http://192.168.2.130/sora-module/log.php?action=add&message=message
console.log(message);
return;
// return;
await fetch('http://192.168.2.130/sora-module/log.php?action=add&message=' + encodeURIComponent(message))
.catch(error => {
@@ -350,7 +367,7 @@ async function sendLog(message) {
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/* {VERSION: 1.2.0} */
/**
* @name global_extractor.js
@@ -358,8 +375,8 @@ async function sendLog(message) {
* @author Cufiy
* @url https://github.com/JMcrafter26/sora-global-extractor
* @license CUSTOM LICENSE - see https://github.com/JMcrafter26/sora-global-extractor/blob/main/LICENSE
* @date 2025-11-05 15:44:57
* @version 1.1.8
* @date 2026-01-03 19:28:28
* @version 1.2.0
* @note This file was generated automatically.
* The global extractor comes with an auto-updating feature, so you can always get the latest version. https://github.com/JMcrafter26/sora-global-extractor#-auto-updater
*/
@@ -369,12 +386,20 @@ function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is an object with streamUrl property
if (streamUrl && typeof streamUrl === "object" && !Array.isArray(streamUrl) && streamUrl.streamUrl) {
return streamUrl.streamUrl;
}
// check if streamUrl is not null, a string, and starts with http or https
if (streamUrl && typeof streamUrl === "string" && (streamUrl.startsWith("http"))) {
if (
streamUrl &&
typeof streamUrl === "string" &&
streamUrl.startsWith("http")
) {
return streamUrl;
// if its an array, get the value that starts with http
} else if (Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
const httpStream = streamUrl.find((url) => url.startsWith("http"));
if (httpStream) {
return httpStream;
}
@@ -382,7 +407,6 @@ function globalExtractor(providers) {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
@@ -394,16 +418,30 @@ async function multiExtractor(providers) {
/* this scheme should be returned as a JSON object
{
"streams": [
"FileMoon",
"https://filemoon.example/stream1.m3u8",
"StreamWish",
"https://streamwish.example/stream2.m3u8",
"Okru",
"https://okru.example/stream3.m3u8",
"MP4",
"https://mp4upload.example/stream4.mp4",
"Default",
"https://default.example/stream5.m3u8"
{
"title": "FileMoon",
"streamUrl": "https://filemoon.example/stream1.m3u8",
},
{
"title": "StreamWish",
"streamUrl": "https://streamwish.example/stream2.m3u8",
},
{
"title": "Okru",
"streamUrl": "https://okru.example/stream3.m3u8",
"headers": { // Optional headers for the stream
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Referer": "https://okru.example/",
},
},
{
"title": "MP4",
"streamUrl": "https://mp4upload.example/stream4.mp4",
},
{
"title": "Default",
"streamUrl": "https://default.example/stream5.m3u8"
}
]
}
*/
@@ -415,20 +453,21 @@ async function multiExtractor(providers) {
// if provider starts with "direct-", then add the url to the streams array directly
if (provider.startsWith("direct-")) {
const directName = provider.slice(7); // remove "direct-" prefix
if (directName && directName.length > 0) {
streams.push(directName, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (directName && directName.length > 0) ? directName : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
if (provider.startsWith("direct")) {
provider = provider.slice(7); // remove "direct-" prefix
if (provider && provider.length > 0) {
streams.push(provider, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (provider && provider.length > 0) ? provider : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
let customName = null; // to store the custom name if provided
@@ -445,15 +484,24 @@ async function multiExtractor(providers) {
console.log(`Skipping ${provider} as it has already 3 streams`);
continue;
}
let streamUrl = await extractStreamUrlByProvider(url, provider);
if (streamUrl && Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
let result = await extractStreamUrlByProvider(url, provider);
let streamUrl = null;
let headers = null;
// Check if result is an object with streamUrl and optional headers
if (result && typeof result === "object" && !Array.isArray(result) && result.streamUrl) {
streamUrl = result.streamUrl;
headers = result.headers || null;
} else if (result && Array.isArray(result)) {
const httpStream = result.find((url) => url.startsWith("http"));
if (httpStream) {
streamUrl = httpStream;
}
} else if (result && typeof result === "string") {
streamUrl = result;
}
// check if provider is already in streams, if it is, add a number to it
// check if streamUrl is valid
if (
!streamUrl ||
typeof streamUrl !== "string" ||
@@ -467,22 +515,29 @@ async function multiExtractor(providers) {
provider = customName;
}
let title;
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
title = provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
(providersCount[provider] - 1); // add a number to the provider name
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
title = provider.charAt(0).toUpperCase() + provider.slice(1);
}
const streamObject = {
title: title,
streamUrl: streamUrl
};
// Add headers if they exist
if (headers && typeof headers === "object" && Object.keys(headers).length > 0) {
streamObject.headers = headers;
}
streams.push(streamObject);
} catch (error) {
// Ignore the error and try the next provider
}
@@ -493,73 +548,98 @@ async function multiExtractor(providers) {
async function extractStreamUrlByProvider(url, provider) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
// skip if the extractor is not defined
console.log(`Extractor for provider ${provider} is not defined, skipping...`);
console.log(
`Extractor for provider ${provider} is not defined, skipping...`
);
return null;
}
let uas = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
];
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": uas[(url.length + provider.length) % uas.length], // use a different user agent based on the url and provider
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"x-Requested-With": "XMLHttpRequest"
"x-Requested-With": "XMLHttpRequest",
};
if(provider == 'bigwarp') {
delete headers["User-Agent"];
headers["x-requested-with"] = "XMLHttpRequest";
} else if (provider == 'vk') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'sibnet') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'supervideo') {
delete headers["User-Agent"];
switch (provider) {
case "bigwarp":
delete headers["User-Agent"];
break;
case "vk":
case "sibnet":
headers["encoding"] = "windows-1251"; // required
break;
case "supervideo":
case "savefiles":
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "EchoapiRuntime/1.1.0",
"Connection": "keep-alive",
"Cache-Control": "no-cache",
"Host": url.match(/https?:\/\/([^\/]+)/)[1],
};
break;
case "streamtape":
headers = {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
break;
}
// console.log("Using headers: " + JSON.stringify(headers));
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
headers
});
headers,
});
console.log("Response: " + response.status);
let html = response.text ? await response.text() : response;
// if title contains redirect, then get the redirect url
const title = html.match(/<title>(.*?)<\/title>/);
if (title && title[1].toLowerCase().includes("redirect")) {
const redirectUrl = html.match(/<meta http-equiv="refresh" content="0;url=(.*?)"/);
const redirectUrl2 = html.match(/window\.location\.href\s*=\s*["'](.*?)["']/);
const redirectUrl3 = html.match(/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/);
if (redirectUrl) {
console.log("Redirect URL: " + redirectUrl[1]);
url = redirectUrl[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl2) {
console.log("Redirect URL 2: " + redirectUrl2[1]);
url = redirectUrl2[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl3) {
console.log("Redirect URL 3: " + redirectUrl3[1]);
url = redirectUrl3[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else {
console.log("No redirect URL found");
const matches = [
/<meta http-equiv="refresh" content="0;url=(.*?)"/,
/window\.location\.href\s*=\s*["'](.*?)["']/,
/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
/window\.location\s*=\s*["'](.*?)["']/,
/window\.location\.assign\s*\(\s*["'](.*?)["']\s*\)/,
/top\.location\s*=\s*["'](.*?)["']/,
/top\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
];
for (const match of matches) {
const redirectUrl = html.match(match);
if (redirectUrl && redirectUrl[1] && typeof redirectUrl[1] === "string" && redirectUrl[1].startsWith("http")) {
console.log("Redirect URL found: " + redirectUrl[1]);
url = redirectUrl[1];
headers['Referer'] = url;
headers['Host'] = url.match(/https?:\/\/([^\/]+)/)[1];
html = await soraFetch(url, {
headers,
}).then((res) => res.text());
break;
}
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
@@ -608,6 +688,20 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "oneupload":
try {
return await oneuploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from oneupload:", error);
return null;
}
case "packer":
try {
return await packerExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from packer:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
@@ -622,6 +716,13 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "smoothpre":
try {
return await smoothpreExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from smoothpre:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
@@ -636,13 +737,6 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from streamup:", error);
return null;
}
case "supervideo":
try {
return await supervideoExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from supervideo:", error);
return null;
}
case "uploadcx":
try {
return await uploadcxExtractor(html, url);
@@ -691,7 +785,6 @@ async function extractStreamUrlByProvider(url, provider) {
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
@@ -1178,7 +1271,6 @@ async function megacloudExtractor(html, embedUrl) {
* @author Cufiy
*/
async function mp4uploadExtractor(html, url = null) {
// src: "https://a4.mp4upload.com:183/d/xkx3b4etz3b4quuo66rbmyqtjjoivahfxp27f35pti45rzapbvj5xwb4wuqtlpewdz4dirfp/video.mp4"
const regex = /src:\s*"([^"]+)"/;
const match = html.match(regex);
if (match) {
@@ -1188,6 +1280,32 @@ async function mp4uploadExtractor(html, url = null) {
return null;
}
}
/* --- oneupload --- */
/**
* @name oneuploadExtractor
* @author 50/50
*/
async function oneuploadExtractor(data, url = null) {
const match = data.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
}
/* --- packer --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name packerExtractor
* @author 50/50
*/
async function packerExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const m3u8Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const m3u8Url = m3u8Match[1];
return m3u8Url;
}
/* --- sendvid --- */
/**
@@ -1223,6 +1341,29 @@ async function sibnetExtractor(html, embedUrl) {
return null;
}
}
/* --- smoothpre --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SmoothPre Extractor
* @author 50/50
*/
async function smoothpreExtractor(data, url = null) {
console.log("Using SmoothPre Extractor");
console.log("Data Length: " + data.length);
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript || !obfuscatedScript[1]) {
console.log("No obfuscated script found");
return null;
}
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
}
/* --- streamtape --- */
/**
@@ -1306,26 +1447,6 @@ async function streamupExtractor(data, url = null) {
return null;
}
}
/* --- supervideo --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SuperVideo Extractor
* @author 50/50
*/
async function supervideoExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const regex = /file:\s*"([^"]+\.m3u8)"/;
const match = regex.exec(unpackedScript);
if (match) {
const fileUrl = match[1];
console.log("File URL:" + fileUrl);
return fileUrl;
}
return "No stream found";
}
/* --- uploadcx --- */
/**
@@ -1390,7 +1511,7 @@ async function vidmolyExtractor(html, url = null) {
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await soraFetch(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
@@ -1516,6 +1637,7 @@ function voeShiftChars(str, shift) {
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
@@ -1533,17 +1655,25 @@ function voeShiftChars(str, shift) {
* @returns {Promise<Response|null>} The response from the server, or null if the
* request failed.
*/
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null
);
} catch (e) {
try {
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
await console.log('soraFetch error: ' + error.message);
return null;
}
return await fetch(url, options);
} catch (error) {
await console.log("soraFetch error: " + error.message);
return null;
}
}
}
/***********************************************************
* UNPACKER MODULE
@@ -1647,6 +1777,5 @@ function unpack(source) {
}
}
/* {GE END} */
/* {GE END} */
+235 -123
View File
@@ -147,7 +147,7 @@ function cleanHtmlSymbols(string) {
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/* {VERSION: 1.2.0} */
/**
* @name global_extractor.js
@@ -155,8 +155,8 @@ function cleanHtmlSymbols(string) {
* @author Cufiy
* @url https://github.com/JMcrafter26/sora-global-extractor
* @license CUSTOM LICENSE - see https://github.com/JMcrafter26/sora-global-extractor/blob/main/LICENSE
* @date 2025-11-05 15:44:57
* @version 1.1.8
* @date 2026-01-03 19:28:28
* @version 1.2.0
* @note This file was generated automatically.
* The global extractor comes with an auto-updating feature, so you can always get the latest version. https://github.com/JMcrafter26/sora-global-extractor#-auto-updater
*/
@@ -166,12 +166,20 @@ function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is an object with streamUrl property
if (streamUrl && typeof streamUrl === "object" && !Array.isArray(streamUrl) && streamUrl.streamUrl) {
return streamUrl.streamUrl;
}
// check if streamUrl is not null, a string, and starts with http or https
if (streamUrl && typeof streamUrl === "string" && (streamUrl.startsWith("http"))) {
if (
streamUrl &&
typeof streamUrl === "string" &&
streamUrl.startsWith("http")
) {
return streamUrl;
// if its an array, get the value that starts with http
} else if (Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
const httpStream = streamUrl.find((url) => url.startsWith("http"));
if (httpStream) {
return httpStream;
}
@@ -179,7 +187,6 @@ function globalExtractor(providers) {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
@@ -191,16 +198,30 @@ async function multiExtractor(providers) {
/* this scheme should be returned as a JSON object
{
"streams": [
"FileMoon",
"https://filemoon.example/stream1.m3u8",
"StreamWish",
"https://streamwish.example/stream2.m3u8",
"Okru",
"https://okru.example/stream3.m3u8",
"MP4",
"https://mp4upload.example/stream4.mp4",
"Default",
"https://default.example/stream5.m3u8"
{
"title": "FileMoon",
"streamUrl": "https://filemoon.example/stream1.m3u8",
},
{
"title": "StreamWish",
"streamUrl": "https://streamwish.example/stream2.m3u8",
},
{
"title": "Okru",
"streamUrl": "https://okru.example/stream3.m3u8",
"headers": { // Optional headers for the stream
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Referer": "https://okru.example/",
},
},
{
"title": "MP4",
"streamUrl": "https://mp4upload.example/stream4.mp4",
},
{
"title": "Default",
"streamUrl": "https://default.example/stream5.m3u8"
}
]
}
*/
@@ -212,20 +233,21 @@ async function multiExtractor(providers) {
// if provider starts with "direct-", then add the url to the streams array directly
if (provider.startsWith("direct-")) {
const directName = provider.slice(7); // remove "direct-" prefix
if (directName && directName.length > 0) {
streams.push(directName, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (directName && directName.length > 0) ? directName : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
if (provider.startsWith("direct")) {
provider = provider.slice(7); // remove "direct-" prefix
if (provider && provider.length > 0) {
streams.push(provider, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (provider && provider.length > 0) ? provider : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
let customName = null; // to store the custom name if provided
@@ -242,15 +264,24 @@ async function multiExtractor(providers) {
console.log(`Skipping ${provider} as it has already 3 streams`);
continue;
}
let streamUrl = await extractStreamUrlByProvider(url, provider);
if (streamUrl && Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
let result = await extractStreamUrlByProvider(url, provider);
let streamUrl = null;
let headers = null;
// Check if result is an object with streamUrl and optional headers
if (result && typeof result === "object" && !Array.isArray(result) && result.streamUrl) {
streamUrl = result.streamUrl;
headers = result.headers || null;
} else if (result && Array.isArray(result)) {
const httpStream = result.find((url) => url.startsWith("http"));
if (httpStream) {
streamUrl = httpStream;
}
} else if (result && typeof result === "string") {
streamUrl = result;
}
// check if provider is already in streams, if it is, add a number to it
// check if streamUrl is valid
if (
!streamUrl ||
typeof streamUrl !== "string" ||
@@ -264,22 +295,29 @@ async function multiExtractor(providers) {
provider = customName;
}
let title;
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
title = provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
(providersCount[provider] - 1); // add a number to the provider name
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
title = provider.charAt(0).toUpperCase() + provider.slice(1);
}
const streamObject = {
title: title,
streamUrl: streamUrl
};
// Add headers if they exist
if (headers && typeof headers === "object" && Object.keys(headers).length > 0) {
streamObject.headers = headers;
}
streams.push(streamObject);
} catch (error) {
// Ignore the error and try the next provider
}
@@ -290,73 +328,98 @@ async function multiExtractor(providers) {
async function extractStreamUrlByProvider(url, provider) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
// skip if the extractor is not defined
console.log(`Extractor for provider ${provider} is not defined, skipping...`);
console.log(
`Extractor for provider ${provider} is not defined, skipping...`
);
return null;
}
let uas = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
];
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": uas[(url.length + provider.length) % uas.length], // use a different user agent based on the url and provider
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"x-Requested-With": "XMLHttpRequest"
"x-Requested-With": "XMLHttpRequest",
};
if(provider == 'bigwarp') {
delete headers["User-Agent"];
headers["x-requested-with"] = "XMLHttpRequest";
} else if (provider == 'vk') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'sibnet') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'supervideo') {
delete headers["User-Agent"];
switch (provider) {
case "bigwarp":
delete headers["User-Agent"];
break;
case "vk":
case "sibnet":
headers["encoding"] = "windows-1251"; // required
break;
case "supervideo":
case "savefiles":
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "EchoapiRuntime/1.1.0",
"Connection": "keep-alive",
"Cache-Control": "no-cache",
"Host": url.match(/https?:\/\/([^\/]+)/)[1],
};
break;
case "streamtape":
headers = {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
break;
}
// console.log("Using headers: " + JSON.stringify(headers));
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
headers
});
headers,
});
console.log("Response: " + response.status);
let html = response.text ? await response.text() : response;
// if title contains redirect, then get the redirect url
const title = html.match(/<title>(.*?)<\/title>/);
if (title && title[1].toLowerCase().includes("redirect")) {
const redirectUrl = html.match(/<meta http-equiv="refresh" content="0;url=(.*?)"/);
const redirectUrl2 = html.match(/window\.location\.href\s*=\s*["'](.*?)["']/);
const redirectUrl3 = html.match(/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/);
if (redirectUrl) {
console.log("Redirect URL: " + redirectUrl[1]);
url = redirectUrl[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl2) {
console.log("Redirect URL 2: " + redirectUrl2[1]);
url = redirectUrl2[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl3) {
console.log("Redirect URL 3: " + redirectUrl3[1]);
url = redirectUrl3[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else {
console.log("No redirect URL found");
const matches = [
/<meta http-equiv="refresh" content="0;url=(.*?)"/,
/window\.location\.href\s*=\s*["'](.*?)["']/,
/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
/window\.location\s*=\s*["'](.*?)["']/,
/window\.location\.assign\s*\(\s*["'](.*?)["']\s*\)/,
/top\.location\s*=\s*["'](.*?)["']/,
/top\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
];
for (const match of matches) {
const redirectUrl = html.match(match);
if (redirectUrl && redirectUrl[1] && typeof redirectUrl[1] === "string" && redirectUrl[1].startsWith("http")) {
console.log("Redirect URL found: " + redirectUrl[1]);
url = redirectUrl[1];
headers['Referer'] = url;
headers['Host'] = url.match(/https?:\/\/([^\/]+)/)[1];
html = await soraFetch(url, {
headers,
}).then((res) => res.text());
break;
}
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
@@ -405,6 +468,20 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "oneupload":
try {
return await oneuploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from oneupload:", error);
return null;
}
case "packer":
try {
return await packerExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from packer:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
@@ -419,6 +496,13 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "smoothpre":
try {
return await smoothpreExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from smoothpre:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
@@ -433,13 +517,6 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from streamup:", error);
return null;
}
case "supervideo":
try {
return await supervideoExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from supervideo:", error);
return null;
}
case "uploadcx":
try {
return await uploadcxExtractor(html, url);
@@ -488,7 +565,6 @@ async function extractStreamUrlByProvider(url, provider) {
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
@@ -975,7 +1051,6 @@ async function megacloudExtractor(html, embedUrl) {
* @author Cufiy
*/
async function mp4uploadExtractor(html, url = null) {
// src: "https://a4.mp4upload.com:183/d/xkx3b4etz3b4quuo66rbmyqtjjoivahfxp27f35pti45rzapbvj5xwb4wuqtlpewdz4dirfp/video.mp4"
const regex = /src:\s*"([^"]+)"/;
const match = html.match(regex);
if (match) {
@@ -985,6 +1060,32 @@ async function mp4uploadExtractor(html, url = null) {
return null;
}
}
/* --- oneupload --- */
/**
* @name oneuploadExtractor
* @author 50/50
*/
async function oneuploadExtractor(data, url = null) {
const match = data.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
}
/* --- packer --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name packerExtractor
* @author 50/50
*/
async function packerExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const m3u8Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const m3u8Url = m3u8Match[1];
return m3u8Url;
}
/* --- sendvid --- */
/**
@@ -1020,6 +1121,29 @@ async function sibnetExtractor(html, embedUrl) {
return null;
}
}
/* --- smoothpre --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SmoothPre Extractor
* @author 50/50
*/
async function smoothpreExtractor(data, url = null) {
console.log("Using SmoothPre Extractor");
console.log("Data Length: " + data.length);
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript || !obfuscatedScript[1]) {
console.log("No obfuscated script found");
return null;
}
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
}
/* --- streamtape --- */
/**
@@ -1103,26 +1227,6 @@ async function streamupExtractor(data, url = null) {
return null;
}
}
/* --- supervideo --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SuperVideo Extractor
* @author 50/50
*/
async function supervideoExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const regex = /file:\s*"([^"]+\.m3u8)"/;
const match = regex.exec(unpackedScript);
if (match) {
const fileUrl = match[1];
console.log("File URL:" + fileUrl);
return fileUrl;
}
return "No stream found";
}
/* --- uploadcx --- */
/**
@@ -1187,7 +1291,7 @@ async function vidmolyExtractor(html, url = null) {
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await soraFetch(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
@@ -1313,6 +1417,7 @@ function voeShiftChars(str, shift) {
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
@@ -1330,17 +1435,25 @@ function voeShiftChars(str, shift) {
* @returns {Promise<Response|null>} The response from the server, or null if the
* request failed.
*/
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null
);
} catch (e) {
try {
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
await console.log('soraFetch error: ' + error.message);
return null;
}
return await fetch(url, options);
} catch (error) {
await console.log("soraFetch error: " + error.message);
return null;
}
}
}
/***********************************************************
* UNPACKER MODULE
@@ -1444,6 +1557,5 @@ function unpack(source) {
}
}
/* {GE END} */
+2 -2
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.2",
"version": "1.0.3",
"language": "Hindi",
"streamType": "HLS",
"quality": "1080p",
@@ -19,4 +19,4 @@
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
}
+2 -2
View File
@@ -5,7 +5,7 @@
"name": "50/50 & Cufiy",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.1.3",
"version": "1.1.4",
"language": "German (SUB)",
"streamType": "HLS",
"quality": "1080p",
@@ -17,4 +17,4 @@
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
}
+2 -2
View File
@@ -5,7 +5,7 @@
"name": "50/50 & Cufiy",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.1.3",
"version": "1.1.4",
"language": "German (DUB)",
"streamType": "HLS",
"quality": "1080p",
@@ -17,4 +17,4 @@
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
}
+2 -2
View File
@@ -5,7 +5,7 @@
"name": "50/50 & Cufiy",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.1.3",
"version": "1.1.4",
"language": "English (SUB)",
"streamType": "HLS",
"quality": "1080p",
@@ -17,4 +17,4 @@
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
}
+235 -123
View File
@@ -190,7 +190,7 @@ async function sendLog(message) {
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/* {VERSION: 1.2.0} */
/**
* @name global_extractor.js
@@ -198,8 +198,8 @@ async function sendLog(message) {
* @author Cufiy
* @url https://github.com/JMcrafter26/sora-global-extractor
* @license CUSTOM LICENSE - see https://github.com/JMcrafter26/sora-global-extractor/blob/main/LICENSE
* @date 2025-11-05 15:44:57
* @version 1.1.8
* @date 2026-01-03 19:28:28
* @version 1.2.0
* @note This file was generated automatically.
* The global extractor comes with an auto-updating feature, so you can always get the latest version. https://github.com/JMcrafter26/sora-global-extractor#-auto-updater
*/
@@ -209,12 +209,20 @@ function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is an object with streamUrl property
if (streamUrl && typeof streamUrl === "object" && !Array.isArray(streamUrl) && streamUrl.streamUrl) {
return streamUrl.streamUrl;
}
// check if streamUrl is not null, a string, and starts with http or https
if (streamUrl && typeof streamUrl === "string" && (streamUrl.startsWith("http"))) {
if (
streamUrl &&
typeof streamUrl === "string" &&
streamUrl.startsWith("http")
) {
return streamUrl;
// if its an array, get the value that starts with http
} else if (Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
const httpStream = streamUrl.find((url) => url.startsWith("http"));
if (httpStream) {
return httpStream;
}
@@ -222,7 +230,6 @@ function globalExtractor(providers) {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
@@ -234,16 +241,30 @@ async function multiExtractor(providers) {
/* this scheme should be returned as a JSON object
{
"streams": [
"FileMoon",
"https://filemoon.example/stream1.m3u8",
"StreamWish",
"https://streamwish.example/stream2.m3u8",
"Okru",
"https://okru.example/stream3.m3u8",
"MP4",
"https://mp4upload.example/stream4.mp4",
"Default",
"https://default.example/stream5.m3u8"
{
"title": "FileMoon",
"streamUrl": "https://filemoon.example/stream1.m3u8",
},
{
"title": "StreamWish",
"streamUrl": "https://streamwish.example/stream2.m3u8",
},
{
"title": "Okru",
"streamUrl": "https://okru.example/stream3.m3u8",
"headers": { // Optional headers for the stream
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Referer": "https://okru.example/",
},
},
{
"title": "MP4",
"streamUrl": "https://mp4upload.example/stream4.mp4",
},
{
"title": "Default",
"streamUrl": "https://default.example/stream5.m3u8"
}
]
}
*/
@@ -255,20 +276,21 @@ async function multiExtractor(providers) {
// if provider starts with "direct-", then add the url to the streams array directly
if (provider.startsWith("direct-")) {
const directName = provider.slice(7); // remove "direct-" prefix
if (directName && directName.length > 0) {
streams.push(directName, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (directName && directName.length > 0) ? directName : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
if (provider.startsWith("direct")) {
provider = provider.slice(7); // remove "direct-" prefix
if (provider && provider.length > 0) {
streams.push(provider, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (provider && provider.length > 0) ? provider : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
let customName = null; // to store the custom name if provided
@@ -285,15 +307,24 @@ async function multiExtractor(providers) {
console.log(`Skipping ${provider} as it has already 3 streams`);
continue;
}
let streamUrl = await extractStreamUrlByProvider(url, provider);
if (streamUrl && Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
let result = await extractStreamUrlByProvider(url, provider);
let streamUrl = null;
let headers = null;
// Check if result is an object with streamUrl and optional headers
if (result && typeof result === "object" && !Array.isArray(result) && result.streamUrl) {
streamUrl = result.streamUrl;
headers = result.headers || null;
} else if (result && Array.isArray(result)) {
const httpStream = result.find((url) => url.startsWith("http"));
if (httpStream) {
streamUrl = httpStream;
}
} else if (result && typeof result === "string") {
streamUrl = result;
}
// check if provider is already in streams, if it is, add a number to it
// check if streamUrl is valid
if (
!streamUrl ||
typeof streamUrl !== "string" ||
@@ -307,22 +338,29 @@ async function multiExtractor(providers) {
provider = customName;
}
let title;
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
title = provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
(providersCount[provider] - 1); // add a number to the provider name
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
title = provider.charAt(0).toUpperCase() + provider.slice(1);
}
const streamObject = {
title: title,
streamUrl: streamUrl
};
// Add headers if they exist
if (headers && typeof headers === "object" && Object.keys(headers).length > 0) {
streamObject.headers = headers;
}
streams.push(streamObject);
} catch (error) {
// Ignore the error and try the next provider
}
@@ -333,73 +371,98 @@ async function multiExtractor(providers) {
async function extractStreamUrlByProvider(url, provider) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
// skip if the extractor is not defined
console.log(`Extractor for provider ${provider} is not defined, skipping...`);
console.log(
`Extractor for provider ${provider} is not defined, skipping...`
);
return null;
}
let uas = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
];
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": uas[(url.length + provider.length) % uas.length], // use a different user agent based on the url and provider
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"x-Requested-With": "XMLHttpRequest"
"x-Requested-With": "XMLHttpRequest",
};
if(provider == 'bigwarp') {
delete headers["User-Agent"];
headers["x-requested-with"] = "XMLHttpRequest";
} else if (provider == 'vk') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'sibnet') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'supervideo') {
delete headers["User-Agent"];
switch (provider) {
case "bigwarp":
delete headers["User-Agent"];
break;
case "vk":
case "sibnet":
headers["encoding"] = "windows-1251"; // required
break;
case "supervideo":
case "savefiles":
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "EchoapiRuntime/1.1.0",
"Connection": "keep-alive",
"Cache-Control": "no-cache",
"Host": url.match(/https?:\/\/([^\/]+)/)[1],
};
break;
case "streamtape":
headers = {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
break;
}
// console.log("Using headers: " + JSON.stringify(headers));
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
headers
});
headers,
});
console.log("Response: " + response.status);
let html = response.text ? await response.text() : response;
// if title contains redirect, then get the redirect url
const title = html.match(/<title>(.*?)<\/title>/);
if (title && title[1].toLowerCase().includes("redirect")) {
const redirectUrl = html.match(/<meta http-equiv="refresh" content="0;url=(.*?)"/);
const redirectUrl2 = html.match(/window\.location\.href\s*=\s*["'](.*?)["']/);
const redirectUrl3 = html.match(/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/);
if (redirectUrl) {
console.log("Redirect URL: " + redirectUrl[1]);
url = redirectUrl[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl2) {
console.log("Redirect URL 2: " + redirectUrl2[1]);
url = redirectUrl2[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl3) {
console.log("Redirect URL 3: " + redirectUrl3[1]);
url = redirectUrl3[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else {
console.log("No redirect URL found");
const matches = [
/<meta http-equiv="refresh" content="0;url=(.*?)"/,
/window\.location\.href\s*=\s*["'](.*?)["']/,
/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
/window\.location\s*=\s*["'](.*?)["']/,
/window\.location\.assign\s*\(\s*["'](.*?)["']\s*\)/,
/top\.location\s*=\s*["'](.*?)["']/,
/top\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
];
for (const match of matches) {
const redirectUrl = html.match(match);
if (redirectUrl && redirectUrl[1] && typeof redirectUrl[1] === "string" && redirectUrl[1].startsWith("http")) {
console.log("Redirect URL found: " + redirectUrl[1]);
url = redirectUrl[1];
headers['Referer'] = url;
headers['Host'] = url.match(/https?:\/\/([^\/]+)/)[1];
html = await soraFetch(url, {
headers,
}).then((res) => res.text());
break;
}
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
@@ -448,6 +511,20 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "oneupload":
try {
return await oneuploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from oneupload:", error);
return null;
}
case "packer":
try {
return await packerExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from packer:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
@@ -462,6 +539,13 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "smoothpre":
try {
return await smoothpreExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from smoothpre:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
@@ -476,13 +560,6 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from streamup:", error);
return null;
}
case "supervideo":
try {
return await supervideoExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from supervideo:", error);
return null;
}
case "uploadcx":
try {
return await uploadcxExtractor(html, url);
@@ -531,7 +608,6 @@ async function extractStreamUrlByProvider(url, provider) {
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
@@ -1018,7 +1094,6 @@ async function megacloudExtractor(html, embedUrl) {
* @author Cufiy
*/
async function mp4uploadExtractor(html, url = null) {
// src: "https://a4.mp4upload.com:183/d/xkx3b4etz3b4quuo66rbmyqtjjoivahfxp27f35pti45rzapbvj5xwb4wuqtlpewdz4dirfp/video.mp4"
const regex = /src:\s*"([^"]+)"/;
const match = html.match(regex);
if (match) {
@@ -1028,6 +1103,32 @@ async function mp4uploadExtractor(html, url = null) {
return null;
}
}
/* --- oneupload --- */
/**
* @name oneuploadExtractor
* @author 50/50
*/
async function oneuploadExtractor(data, url = null) {
const match = data.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
}
/* --- packer --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name packerExtractor
* @author 50/50
*/
async function packerExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const m3u8Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const m3u8Url = m3u8Match[1];
return m3u8Url;
}
/* --- sendvid --- */
/**
@@ -1063,6 +1164,29 @@ async function sibnetExtractor(html, embedUrl) {
return null;
}
}
/* --- smoothpre --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SmoothPre Extractor
* @author 50/50
*/
async function smoothpreExtractor(data, url = null) {
console.log("Using SmoothPre Extractor");
console.log("Data Length: " + data.length);
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript || !obfuscatedScript[1]) {
console.log("No obfuscated script found");
return null;
}
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
}
/* --- streamtape --- */
/**
@@ -1146,26 +1270,6 @@ async function streamupExtractor(data, url = null) {
return null;
}
}
/* --- supervideo --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SuperVideo Extractor
* @author 50/50
*/
async function supervideoExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const regex = /file:\s*"([^"]+\.m3u8)"/;
const match = regex.exec(unpackedScript);
if (match) {
const fileUrl = match[1];
console.log("File URL:" + fileUrl);
return fileUrl;
}
return "No stream found";
}
/* --- uploadcx --- */
/**
@@ -1230,7 +1334,7 @@ async function vidmolyExtractor(html, url = null) {
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await soraFetch(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
@@ -1356,6 +1460,7 @@ function voeShiftChars(str, shift) {
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
@@ -1373,17 +1478,25 @@ function voeShiftChars(str, shift) {
* @returns {Promise<Response|null>} The response from the server, or null if the
* request failed.
*/
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null
);
} catch (e) {
try {
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
await console.log('soraFetch error: ' + error.message);
return null;
}
return await fetch(url, options);
} catch (error) {
await console.log("soraFetch error: " + error.message);
return null;
}
}
}
/***********************************************************
* UNPACKER MODULE
@@ -1487,6 +1600,5 @@ function unpack(source) {
}
}
/* {GE END} */
+235 -123
View File
@@ -191,7 +191,7 @@ async function sendLog(message) {
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/* {VERSION: 1.2.0} */
/**
* @name global_extractor.js
@@ -199,8 +199,8 @@ async function sendLog(message) {
* @author Cufiy
* @url https://github.com/JMcrafter26/sora-global-extractor
* @license CUSTOM LICENSE - see https://github.com/JMcrafter26/sora-global-extractor/blob/main/LICENSE
* @date 2025-11-05 15:44:57
* @version 1.1.8
* @date 2026-01-03 19:28:28
* @version 1.2.0
* @note This file was generated automatically.
* The global extractor comes with an auto-updating feature, so you can always get the latest version. https://github.com/JMcrafter26/sora-global-extractor#-auto-updater
*/
@@ -210,12 +210,20 @@ function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is an object with streamUrl property
if (streamUrl && typeof streamUrl === "object" && !Array.isArray(streamUrl) && streamUrl.streamUrl) {
return streamUrl.streamUrl;
}
// check if streamUrl is not null, a string, and starts with http or https
if (streamUrl && typeof streamUrl === "string" && (streamUrl.startsWith("http"))) {
if (
streamUrl &&
typeof streamUrl === "string" &&
streamUrl.startsWith("http")
) {
return streamUrl;
// if its an array, get the value that starts with http
} else if (Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
const httpStream = streamUrl.find((url) => url.startsWith("http"));
if (httpStream) {
return httpStream;
}
@@ -223,7 +231,6 @@ function globalExtractor(providers) {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
@@ -235,16 +242,30 @@ async function multiExtractor(providers) {
/* this scheme should be returned as a JSON object
{
"streams": [
"FileMoon",
"https://filemoon.example/stream1.m3u8",
"StreamWish",
"https://streamwish.example/stream2.m3u8",
"Okru",
"https://okru.example/stream3.m3u8",
"MP4",
"https://mp4upload.example/stream4.mp4",
"Default",
"https://default.example/stream5.m3u8"
{
"title": "FileMoon",
"streamUrl": "https://filemoon.example/stream1.m3u8",
},
{
"title": "StreamWish",
"streamUrl": "https://streamwish.example/stream2.m3u8",
},
{
"title": "Okru",
"streamUrl": "https://okru.example/stream3.m3u8",
"headers": { // Optional headers for the stream
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Referer": "https://okru.example/",
},
},
{
"title": "MP4",
"streamUrl": "https://mp4upload.example/stream4.mp4",
},
{
"title": "Default",
"streamUrl": "https://default.example/stream5.m3u8"
}
]
}
*/
@@ -256,20 +277,21 @@ async function multiExtractor(providers) {
// if provider starts with "direct-", then add the url to the streams array directly
if (provider.startsWith("direct-")) {
const directName = provider.slice(7); // remove "direct-" prefix
if (directName && directName.length > 0) {
streams.push(directName, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (directName && directName.length > 0) ? directName : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
if (provider.startsWith("direct")) {
provider = provider.slice(7); // remove "direct-" prefix
if (provider && provider.length > 0) {
streams.push(provider, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (provider && provider.length > 0) ? provider : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
let customName = null; // to store the custom name if provided
@@ -286,15 +308,24 @@ async function multiExtractor(providers) {
console.log(`Skipping ${provider} as it has already 3 streams`);
continue;
}
let streamUrl = await extractStreamUrlByProvider(url, provider);
if (streamUrl && Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
let result = await extractStreamUrlByProvider(url, provider);
let streamUrl = null;
let headers = null;
// Check if result is an object with streamUrl and optional headers
if (result && typeof result === "object" && !Array.isArray(result) && result.streamUrl) {
streamUrl = result.streamUrl;
headers = result.headers || null;
} else if (result && Array.isArray(result)) {
const httpStream = result.find((url) => url.startsWith("http"));
if (httpStream) {
streamUrl = httpStream;
}
} else if (result && typeof result === "string") {
streamUrl = result;
}
// check if provider is already in streams, if it is, add a number to it
// check if streamUrl is valid
if (
!streamUrl ||
typeof streamUrl !== "string" ||
@@ -308,22 +339,29 @@ async function multiExtractor(providers) {
provider = customName;
}
let title;
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
title = provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
(providersCount[provider] - 1); // add a number to the provider name
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
title = provider.charAt(0).toUpperCase() + provider.slice(1);
}
const streamObject = {
title: title,
streamUrl: streamUrl
};
// Add headers if they exist
if (headers && typeof headers === "object" && Object.keys(headers).length > 0) {
streamObject.headers = headers;
}
streams.push(streamObject);
} catch (error) {
// Ignore the error and try the next provider
}
@@ -334,73 +372,98 @@ async function multiExtractor(providers) {
async function extractStreamUrlByProvider(url, provider) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
// skip if the extractor is not defined
console.log(`Extractor for provider ${provider} is not defined, skipping...`);
console.log(
`Extractor for provider ${provider} is not defined, skipping...`
);
return null;
}
let uas = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
];
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": uas[(url.length + provider.length) % uas.length], // use a different user agent based on the url and provider
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"x-Requested-With": "XMLHttpRequest"
"x-Requested-With": "XMLHttpRequest",
};
if(provider == 'bigwarp') {
delete headers["User-Agent"];
headers["x-requested-with"] = "XMLHttpRequest";
} else if (provider == 'vk') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'sibnet') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'supervideo') {
delete headers["User-Agent"];
switch (provider) {
case "bigwarp":
delete headers["User-Agent"];
break;
case "vk":
case "sibnet":
headers["encoding"] = "windows-1251"; // required
break;
case "supervideo":
case "savefiles":
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "EchoapiRuntime/1.1.0",
"Connection": "keep-alive",
"Cache-Control": "no-cache",
"Host": url.match(/https?:\/\/([^\/]+)/)[1],
};
break;
case "streamtape":
headers = {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
break;
}
// console.log("Using headers: " + JSON.stringify(headers));
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
headers
});
headers,
});
console.log("Response: " + response.status);
let html = response.text ? await response.text() : response;
// if title contains redirect, then get the redirect url
const title = html.match(/<title>(.*?)<\/title>/);
if (title && title[1].toLowerCase().includes("redirect")) {
const redirectUrl = html.match(/<meta http-equiv="refresh" content="0;url=(.*?)"/);
const redirectUrl2 = html.match(/window\.location\.href\s*=\s*["'](.*?)["']/);
const redirectUrl3 = html.match(/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/);
if (redirectUrl) {
console.log("Redirect URL: " + redirectUrl[1]);
url = redirectUrl[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl2) {
console.log("Redirect URL 2: " + redirectUrl2[1]);
url = redirectUrl2[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl3) {
console.log("Redirect URL 3: " + redirectUrl3[1]);
url = redirectUrl3[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else {
console.log("No redirect URL found");
const matches = [
/<meta http-equiv="refresh" content="0;url=(.*?)"/,
/window\.location\.href\s*=\s*["'](.*?)["']/,
/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
/window\.location\s*=\s*["'](.*?)["']/,
/window\.location\.assign\s*\(\s*["'](.*?)["']\s*\)/,
/top\.location\s*=\s*["'](.*?)["']/,
/top\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
];
for (const match of matches) {
const redirectUrl = html.match(match);
if (redirectUrl && redirectUrl[1] && typeof redirectUrl[1] === "string" && redirectUrl[1].startsWith("http")) {
console.log("Redirect URL found: " + redirectUrl[1]);
url = redirectUrl[1];
headers['Referer'] = url;
headers['Host'] = url.match(/https?:\/\/([^\/]+)/)[1];
html = await soraFetch(url, {
headers,
}).then((res) => res.text());
break;
}
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
@@ -449,6 +512,20 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "oneupload":
try {
return await oneuploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from oneupload:", error);
return null;
}
case "packer":
try {
return await packerExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from packer:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
@@ -463,6 +540,13 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "smoothpre":
try {
return await smoothpreExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from smoothpre:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
@@ -477,13 +561,6 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from streamup:", error);
return null;
}
case "supervideo":
try {
return await supervideoExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from supervideo:", error);
return null;
}
case "uploadcx":
try {
return await uploadcxExtractor(html, url);
@@ -532,7 +609,6 @@ async function extractStreamUrlByProvider(url, provider) {
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
@@ -1019,7 +1095,6 @@ async function megacloudExtractor(html, embedUrl) {
* @author Cufiy
*/
async function mp4uploadExtractor(html, url = null) {
// src: "https://a4.mp4upload.com:183/d/xkx3b4etz3b4quuo66rbmyqtjjoivahfxp27f35pti45rzapbvj5xwb4wuqtlpewdz4dirfp/video.mp4"
const regex = /src:\s*"([^"]+)"/;
const match = html.match(regex);
if (match) {
@@ -1029,6 +1104,32 @@ async function mp4uploadExtractor(html, url = null) {
return null;
}
}
/* --- oneupload --- */
/**
* @name oneuploadExtractor
* @author 50/50
*/
async function oneuploadExtractor(data, url = null) {
const match = data.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
}
/* --- packer --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name packerExtractor
* @author 50/50
*/
async function packerExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const m3u8Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const m3u8Url = m3u8Match[1];
return m3u8Url;
}
/* --- sendvid --- */
/**
@@ -1064,6 +1165,29 @@ async function sibnetExtractor(html, embedUrl) {
return null;
}
}
/* --- smoothpre --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SmoothPre Extractor
* @author 50/50
*/
async function smoothpreExtractor(data, url = null) {
console.log("Using SmoothPre Extractor");
console.log("Data Length: " + data.length);
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript || !obfuscatedScript[1]) {
console.log("No obfuscated script found");
return null;
}
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
}
/* --- streamtape --- */
/**
@@ -1147,26 +1271,6 @@ async function streamupExtractor(data, url = null) {
return null;
}
}
/* --- supervideo --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SuperVideo Extractor
* @author 50/50
*/
async function supervideoExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const regex = /file:\s*"([^"]+\.m3u8)"/;
const match = regex.exec(unpackedScript);
if (match) {
const fileUrl = match[1];
console.log("File URL:" + fileUrl);
return fileUrl;
}
return "No stream found";
}
/* --- uploadcx --- */
/**
@@ -1231,7 +1335,7 @@ async function vidmolyExtractor(html, url = null) {
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await soraFetch(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
@@ -1357,6 +1461,7 @@ function voeShiftChars(str, shift) {
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
@@ -1374,17 +1479,25 @@ function voeShiftChars(str, shift) {
* @returns {Promise<Response|null>} The response from the server, or null if the
* request failed.
*/
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null
);
} catch (e) {
try {
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
await console.log('soraFetch error: ' + error.message);
return null;
}
return await fetch(url, options);
} catch (error) {
await console.log("soraFetch error: " + error.message);
return null;
}
}
}
/***********************************************************
* UNPACKER MODULE
@@ -1488,6 +1601,5 @@ function unpack(source) {
}
}
/* {GE END} */
+235 -123
View File
@@ -190,7 +190,7 @@ async function sendLog(message) {
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/* {VERSION: 1.2.0} */
/**
* @name global_extractor.js
@@ -198,8 +198,8 @@ async function sendLog(message) {
* @author Cufiy
* @url https://github.com/JMcrafter26/sora-global-extractor
* @license CUSTOM LICENSE - see https://github.com/JMcrafter26/sora-global-extractor/blob/main/LICENSE
* @date 2025-11-05 15:44:57
* @version 1.1.8
* @date 2026-01-03 19:28:28
* @version 1.2.0
* @note This file was generated automatically.
* The global extractor comes with an auto-updating feature, so you can always get the latest version. https://github.com/JMcrafter26/sora-global-extractor#-auto-updater
*/
@@ -209,12 +209,20 @@ function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is an object with streamUrl property
if (streamUrl && typeof streamUrl === "object" && !Array.isArray(streamUrl) && streamUrl.streamUrl) {
return streamUrl.streamUrl;
}
// check if streamUrl is not null, a string, and starts with http or https
if (streamUrl && typeof streamUrl === "string" && (streamUrl.startsWith("http"))) {
if (
streamUrl &&
typeof streamUrl === "string" &&
streamUrl.startsWith("http")
) {
return streamUrl;
// if its an array, get the value that starts with http
} else if (Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
const httpStream = streamUrl.find((url) => url.startsWith("http"));
if (httpStream) {
return httpStream;
}
@@ -222,7 +230,6 @@ function globalExtractor(providers) {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
@@ -234,16 +241,30 @@ async function multiExtractor(providers) {
/* this scheme should be returned as a JSON object
{
"streams": [
"FileMoon",
"https://filemoon.example/stream1.m3u8",
"StreamWish",
"https://streamwish.example/stream2.m3u8",
"Okru",
"https://okru.example/stream3.m3u8",
"MP4",
"https://mp4upload.example/stream4.mp4",
"Default",
"https://default.example/stream5.m3u8"
{
"title": "FileMoon",
"streamUrl": "https://filemoon.example/stream1.m3u8",
},
{
"title": "StreamWish",
"streamUrl": "https://streamwish.example/stream2.m3u8",
},
{
"title": "Okru",
"streamUrl": "https://okru.example/stream3.m3u8",
"headers": { // Optional headers for the stream
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Referer": "https://okru.example/",
},
},
{
"title": "MP4",
"streamUrl": "https://mp4upload.example/stream4.mp4",
},
{
"title": "Default",
"streamUrl": "https://default.example/stream5.m3u8"
}
]
}
*/
@@ -255,20 +276,21 @@ async function multiExtractor(providers) {
// if provider starts with "direct-", then add the url to the streams array directly
if (provider.startsWith("direct-")) {
const directName = provider.slice(7); // remove "direct-" prefix
if (directName && directName.length > 0) {
streams.push(directName, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (directName && directName.length > 0) ? directName : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
if (provider.startsWith("direct")) {
provider = provider.slice(7); // remove "direct-" prefix
if (provider && provider.length > 0) {
streams.push(provider, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (provider && provider.length > 0) ? provider : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
let customName = null; // to store the custom name if provided
@@ -285,15 +307,24 @@ async function multiExtractor(providers) {
console.log(`Skipping ${provider} as it has already 3 streams`);
continue;
}
let streamUrl = await extractStreamUrlByProvider(url, provider);
if (streamUrl && Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
let result = await extractStreamUrlByProvider(url, provider);
let streamUrl = null;
let headers = null;
// Check if result is an object with streamUrl and optional headers
if (result && typeof result === "object" && !Array.isArray(result) && result.streamUrl) {
streamUrl = result.streamUrl;
headers = result.headers || null;
} else if (result && Array.isArray(result)) {
const httpStream = result.find((url) => url.startsWith("http"));
if (httpStream) {
streamUrl = httpStream;
}
} else if (result && typeof result === "string") {
streamUrl = result;
}
// check if provider is already in streams, if it is, add a number to it
// check if streamUrl is valid
if (
!streamUrl ||
typeof streamUrl !== "string" ||
@@ -307,22 +338,29 @@ async function multiExtractor(providers) {
provider = customName;
}
let title;
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
title = provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
(providersCount[provider] - 1); // add a number to the provider name
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
title = provider.charAt(0).toUpperCase() + provider.slice(1);
}
const streamObject = {
title: title,
streamUrl: streamUrl
};
// Add headers if they exist
if (headers && typeof headers === "object" && Object.keys(headers).length > 0) {
streamObject.headers = headers;
}
streams.push(streamObject);
} catch (error) {
// Ignore the error and try the next provider
}
@@ -333,73 +371,98 @@ async function multiExtractor(providers) {
async function extractStreamUrlByProvider(url, provider) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
// skip if the extractor is not defined
console.log(`Extractor for provider ${provider} is not defined, skipping...`);
console.log(
`Extractor for provider ${provider} is not defined, skipping...`
);
return null;
}
let uas = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
];
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": uas[(url.length + provider.length) % uas.length], // use a different user agent based on the url and provider
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"x-Requested-With": "XMLHttpRequest"
"x-Requested-With": "XMLHttpRequest",
};
if(provider == 'bigwarp') {
delete headers["User-Agent"];
headers["x-requested-with"] = "XMLHttpRequest";
} else if (provider == 'vk') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'sibnet') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'supervideo') {
delete headers["User-Agent"];
switch (provider) {
case "bigwarp":
delete headers["User-Agent"];
break;
case "vk":
case "sibnet":
headers["encoding"] = "windows-1251"; // required
break;
case "supervideo":
case "savefiles":
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "EchoapiRuntime/1.1.0",
"Connection": "keep-alive",
"Cache-Control": "no-cache",
"Host": url.match(/https?:\/\/([^\/]+)/)[1],
};
break;
case "streamtape":
headers = {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
break;
}
// console.log("Using headers: " + JSON.stringify(headers));
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
headers
});
headers,
});
console.log("Response: " + response.status);
let html = response.text ? await response.text() : response;
// if title contains redirect, then get the redirect url
const title = html.match(/<title>(.*?)<\/title>/);
if (title && title[1].toLowerCase().includes("redirect")) {
const redirectUrl = html.match(/<meta http-equiv="refresh" content="0;url=(.*?)"/);
const redirectUrl2 = html.match(/window\.location\.href\s*=\s*["'](.*?)["']/);
const redirectUrl3 = html.match(/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/);
if (redirectUrl) {
console.log("Redirect URL: " + redirectUrl[1]);
url = redirectUrl[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl2) {
console.log("Redirect URL 2: " + redirectUrl2[1]);
url = redirectUrl2[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl3) {
console.log("Redirect URL 3: " + redirectUrl3[1]);
url = redirectUrl3[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else {
console.log("No redirect URL found");
const matches = [
/<meta http-equiv="refresh" content="0;url=(.*?)"/,
/window\.location\.href\s*=\s*["'](.*?)["']/,
/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
/window\.location\s*=\s*["'](.*?)["']/,
/window\.location\.assign\s*\(\s*["'](.*?)["']\s*\)/,
/top\.location\s*=\s*["'](.*?)["']/,
/top\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
];
for (const match of matches) {
const redirectUrl = html.match(match);
if (redirectUrl && redirectUrl[1] && typeof redirectUrl[1] === "string" && redirectUrl[1].startsWith("http")) {
console.log("Redirect URL found: " + redirectUrl[1]);
url = redirectUrl[1];
headers['Referer'] = url;
headers['Host'] = url.match(/https?:\/\/([^\/]+)/)[1];
html = await soraFetch(url, {
headers,
}).then((res) => res.text());
break;
}
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
@@ -448,6 +511,20 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "oneupload":
try {
return await oneuploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from oneupload:", error);
return null;
}
case "packer":
try {
return await packerExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from packer:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
@@ -462,6 +539,13 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "smoothpre":
try {
return await smoothpreExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from smoothpre:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
@@ -476,13 +560,6 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from streamup:", error);
return null;
}
case "supervideo":
try {
return await supervideoExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from supervideo:", error);
return null;
}
case "uploadcx":
try {
return await uploadcxExtractor(html, url);
@@ -531,7 +608,6 @@ async function extractStreamUrlByProvider(url, provider) {
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
@@ -1018,7 +1094,6 @@ async function megacloudExtractor(html, embedUrl) {
* @author Cufiy
*/
async function mp4uploadExtractor(html, url = null) {
// src: "https://a4.mp4upload.com:183/d/xkx3b4etz3b4quuo66rbmyqtjjoivahfxp27f35pti45rzapbvj5xwb4wuqtlpewdz4dirfp/video.mp4"
const regex = /src:\s*"([^"]+)"/;
const match = html.match(regex);
if (match) {
@@ -1028,6 +1103,32 @@ async function mp4uploadExtractor(html, url = null) {
return null;
}
}
/* --- oneupload --- */
/**
* @name oneuploadExtractor
* @author 50/50
*/
async function oneuploadExtractor(data, url = null) {
const match = data.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
}
/* --- packer --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name packerExtractor
* @author 50/50
*/
async function packerExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const m3u8Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const m3u8Url = m3u8Match[1];
return m3u8Url;
}
/* --- sendvid --- */
/**
@@ -1063,6 +1164,29 @@ async function sibnetExtractor(html, embedUrl) {
return null;
}
}
/* --- smoothpre --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SmoothPre Extractor
* @author 50/50
*/
async function smoothpreExtractor(data, url = null) {
console.log("Using SmoothPre Extractor");
console.log("Data Length: " + data.length);
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript || !obfuscatedScript[1]) {
console.log("No obfuscated script found");
return null;
}
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
}
/* --- streamtape --- */
/**
@@ -1146,26 +1270,6 @@ async function streamupExtractor(data, url = null) {
return null;
}
}
/* --- supervideo --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SuperVideo Extractor
* @author 50/50
*/
async function supervideoExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const regex = /file:\s*"([^"]+\.m3u8)"/;
const match = regex.exec(unpackedScript);
if (match) {
const fileUrl = match[1];
console.log("File URL:" + fileUrl);
return fileUrl;
}
return "No stream found";
}
/* --- uploadcx --- */
/**
@@ -1230,7 +1334,7 @@ async function vidmolyExtractor(html, url = null) {
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await soraFetch(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
@@ -1356,6 +1460,7 @@ function voeShiftChars(str, shift) {
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
@@ -1373,17 +1478,25 @@ function voeShiftChars(str, shift) {
* @returns {Promise<Response|null>} The response from the server, or null if the
* request failed.
*/
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null
);
} catch (e) {
try {
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
await console.log('soraFetch error: ' + error.message);
return null;
}
return await fetch(url, options);
} catch (error) {
await console.log("soraFetch error: " + error.message);
return null;
}
}
}
/***********************************************************
* UNPACKER MODULE
@@ -1487,6 +1600,5 @@ function unpack(source) {
}
}
/* {GE END} */
+2 -2
View File
@@ -5,7 +5,7 @@
"name": "Cufiy",
"icon": "https://files.catbox.moe/ttj4fc.gif"
},
"version": "0.3.16",
"version": "0.3.17",
"language": "English (DUB)",
"streamType": "HLS",
"quality": "720p",
@@ -17,4 +17,4 @@
"type": "shows",
"supportsSora": true,
"supportsLuna": true
}
}
+235 -123
View File
@@ -376,7 +376,7 @@ function base64Decode(str) {
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/* {VERSION: 1.2.0} */
/**
* @name global_extractor.js
@@ -384,8 +384,8 @@ function base64Decode(str) {
* @author Cufiy
* @url https://github.com/JMcrafter26/sora-global-extractor
* @license CUSTOM LICENSE - see https://github.com/JMcrafter26/sora-global-extractor/blob/main/LICENSE
* @date 2025-11-05 15:44:57
* @version 1.1.8
* @date 2026-01-03 19:28:28
* @version 1.2.0
* @note This file was generated automatically.
* The global extractor comes with an auto-updating feature, so you can always get the latest version. https://github.com/JMcrafter26/sora-global-extractor#-auto-updater
*/
@@ -395,12 +395,20 @@ function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is an object with streamUrl property
if (streamUrl && typeof streamUrl === "object" && !Array.isArray(streamUrl) && streamUrl.streamUrl) {
return streamUrl.streamUrl;
}
// check if streamUrl is not null, a string, and starts with http or https
if (streamUrl && typeof streamUrl === "string" && (streamUrl.startsWith("http"))) {
if (
streamUrl &&
typeof streamUrl === "string" &&
streamUrl.startsWith("http")
) {
return streamUrl;
// if its an array, get the value that starts with http
} else if (Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
const httpStream = streamUrl.find((url) => url.startsWith("http"));
if (httpStream) {
return httpStream;
}
@@ -408,7 +416,6 @@ function globalExtractor(providers) {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
@@ -420,16 +427,30 @@ async function multiExtractor(providers) {
/* this scheme should be returned as a JSON object
{
"streams": [
"FileMoon",
"https://filemoon.example/stream1.m3u8",
"StreamWish",
"https://streamwish.example/stream2.m3u8",
"Okru",
"https://okru.example/stream3.m3u8",
"MP4",
"https://mp4upload.example/stream4.mp4",
"Default",
"https://default.example/stream5.m3u8"
{
"title": "FileMoon",
"streamUrl": "https://filemoon.example/stream1.m3u8",
},
{
"title": "StreamWish",
"streamUrl": "https://streamwish.example/stream2.m3u8",
},
{
"title": "Okru",
"streamUrl": "https://okru.example/stream3.m3u8",
"headers": { // Optional headers for the stream
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Referer": "https://okru.example/",
},
},
{
"title": "MP4",
"streamUrl": "https://mp4upload.example/stream4.mp4",
},
{
"title": "Default",
"streamUrl": "https://default.example/stream5.m3u8"
}
]
}
*/
@@ -441,20 +462,21 @@ async function multiExtractor(providers) {
// if provider starts with "direct-", then add the url to the streams array directly
if (provider.startsWith("direct-")) {
const directName = provider.slice(7); // remove "direct-" prefix
if (directName && directName.length > 0) {
streams.push(directName, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (directName && directName.length > 0) ? directName : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
if (provider.startsWith("direct")) {
provider = provider.slice(7); // remove "direct-" prefix
if (provider && provider.length > 0) {
streams.push(provider, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (provider && provider.length > 0) ? provider : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
let customName = null; // to store the custom name if provided
@@ -471,15 +493,24 @@ async function multiExtractor(providers) {
console.log(`Skipping ${provider} as it has already 3 streams`);
continue;
}
let streamUrl = await extractStreamUrlByProvider(url, provider);
if (streamUrl && Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
let result = await extractStreamUrlByProvider(url, provider);
let streamUrl = null;
let headers = null;
// Check if result is an object with streamUrl and optional headers
if (result && typeof result === "object" && !Array.isArray(result) && result.streamUrl) {
streamUrl = result.streamUrl;
headers = result.headers || null;
} else if (result && Array.isArray(result)) {
const httpStream = result.find((url) => url.startsWith("http"));
if (httpStream) {
streamUrl = httpStream;
}
} else if (result && typeof result === "string") {
streamUrl = result;
}
// check if provider is already in streams, if it is, add a number to it
// check if streamUrl is valid
if (
!streamUrl ||
typeof streamUrl !== "string" ||
@@ -493,22 +524,29 @@ async function multiExtractor(providers) {
provider = customName;
}
let title;
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
title = provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
(providersCount[provider] - 1); // add a number to the provider name
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
title = provider.charAt(0).toUpperCase() + provider.slice(1);
}
const streamObject = {
title: title,
streamUrl: streamUrl
};
// Add headers if they exist
if (headers && typeof headers === "object" && Object.keys(headers).length > 0) {
streamObject.headers = headers;
}
streams.push(streamObject);
} catch (error) {
// Ignore the error and try the next provider
}
@@ -519,73 +557,98 @@ async function multiExtractor(providers) {
async function extractStreamUrlByProvider(url, provider) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
// skip if the extractor is not defined
console.log(`Extractor for provider ${provider} is not defined, skipping...`);
console.log(
`Extractor for provider ${provider} is not defined, skipping...`
);
return null;
}
let uas = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
];
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": uas[(url.length + provider.length) % uas.length], // use a different user agent based on the url and provider
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"x-Requested-With": "XMLHttpRequest"
"x-Requested-With": "XMLHttpRequest",
};
if(provider == 'bigwarp') {
delete headers["User-Agent"];
headers["x-requested-with"] = "XMLHttpRequest";
} else if (provider == 'vk') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'sibnet') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'supervideo') {
delete headers["User-Agent"];
switch (provider) {
case "bigwarp":
delete headers["User-Agent"];
break;
case "vk":
case "sibnet":
headers["encoding"] = "windows-1251"; // required
break;
case "supervideo":
case "savefiles":
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "EchoapiRuntime/1.1.0",
"Connection": "keep-alive",
"Cache-Control": "no-cache",
"Host": url.match(/https?:\/\/([^\/]+)/)[1],
};
break;
case "streamtape":
headers = {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
break;
}
// console.log("Using headers: " + JSON.stringify(headers));
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
headers
});
headers,
});
console.log("Response: " + response.status);
let html = response.text ? await response.text() : response;
// if title contains redirect, then get the redirect url
const title = html.match(/<title>(.*?)<\/title>/);
if (title && title[1].toLowerCase().includes("redirect")) {
const redirectUrl = html.match(/<meta http-equiv="refresh" content="0;url=(.*?)"/);
const redirectUrl2 = html.match(/window\.location\.href\s*=\s*["'](.*?)["']/);
const redirectUrl3 = html.match(/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/);
if (redirectUrl) {
console.log("Redirect URL: " + redirectUrl[1]);
url = redirectUrl[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl2) {
console.log("Redirect URL 2: " + redirectUrl2[1]);
url = redirectUrl2[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl3) {
console.log("Redirect URL 3: " + redirectUrl3[1]);
url = redirectUrl3[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else {
console.log("No redirect URL found");
const matches = [
/<meta http-equiv="refresh" content="0;url=(.*?)"/,
/window\.location\.href\s*=\s*["'](.*?)["']/,
/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
/window\.location\s*=\s*["'](.*?)["']/,
/window\.location\.assign\s*\(\s*["'](.*?)["']\s*\)/,
/top\.location\s*=\s*["'](.*?)["']/,
/top\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
];
for (const match of matches) {
const redirectUrl = html.match(match);
if (redirectUrl && redirectUrl[1] && typeof redirectUrl[1] === "string" && redirectUrl[1].startsWith("http")) {
console.log("Redirect URL found: " + redirectUrl[1]);
url = redirectUrl[1];
headers['Referer'] = url;
headers['Host'] = url.match(/https?:\/\/([^\/]+)/)[1];
html = await soraFetch(url, {
headers,
}).then((res) => res.text());
break;
}
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
@@ -634,6 +697,20 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "oneupload":
try {
return await oneuploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from oneupload:", error);
return null;
}
case "packer":
try {
return await packerExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from packer:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
@@ -648,6 +725,13 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "smoothpre":
try {
return await smoothpreExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from smoothpre:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
@@ -662,13 +746,6 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from streamup:", error);
return null;
}
case "supervideo":
try {
return await supervideoExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from supervideo:", error);
return null;
}
case "uploadcx":
try {
return await uploadcxExtractor(html, url);
@@ -717,7 +794,6 @@ async function extractStreamUrlByProvider(url, provider) {
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
@@ -1204,7 +1280,6 @@ async function megacloudExtractor(html, embedUrl) {
* @author Cufiy
*/
async function mp4uploadExtractor(html, url = null) {
// src: "https://a4.mp4upload.com:183/d/xkx3b4etz3b4quuo66rbmyqtjjoivahfxp27f35pti45rzapbvj5xwb4wuqtlpewdz4dirfp/video.mp4"
const regex = /src:\s*"([^"]+)"/;
const match = html.match(regex);
if (match) {
@@ -1214,6 +1289,32 @@ async function mp4uploadExtractor(html, url = null) {
return null;
}
}
/* --- oneupload --- */
/**
* @name oneuploadExtractor
* @author 50/50
*/
async function oneuploadExtractor(data, url = null) {
const match = data.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
}
/* --- packer --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name packerExtractor
* @author 50/50
*/
async function packerExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const m3u8Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const m3u8Url = m3u8Match[1];
return m3u8Url;
}
/* --- sendvid --- */
/**
@@ -1249,6 +1350,29 @@ async function sibnetExtractor(html, embedUrl) {
return null;
}
}
/* --- smoothpre --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SmoothPre Extractor
* @author 50/50
*/
async function smoothpreExtractor(data, url = null) {
console.log("Using SmoothPre Extractor");
console.log("Data Length: " + data.length);
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript || !obfuscatedScript[1]) {
console.log("No obfuscated script found");
return null;
}
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
}
/* --- streamtape --- */
/**
@@ -1332,26 +1456,6 @@ async function streamupExtractor(data, url = null) {
return null;
}
}
/* --- supervideo --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SuperVideo Extractor
* @author 50/50
*/
async function supervideoExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const regex = /file:\s*"([^"]+\.m3u8)"/;
const match = regex.exec(unpackedScript);
if (match) {
const fileUrl = match[1];
console.log("File URL:" + fileUrl);
return fileUrl;
}
return "No stream found";
}
/* --- uploadcx --- */
/**
@@ -1416,7 +1520,7 @@ async function vidmolyExtractor(html, url = null) {
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await soraFetch(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
@@ -1542,6 +1646,7 @@ function voeShiftChars(str, shift) {
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
@@ -1559,17 +1664,25 @@ function voeShiftChars(str, shift) {
* @returns {Promise<Response|null>} The response from the server, or null if the
* request failed.
*/
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null
);
} catch (e) {
try {
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
await console.log('soraFetch error: ' + error.message);
return null;
}
return await fetch(url, options);
} catch (error) {
await console.log("soraFetch error: " + error.message);
return null;
}
}
}
/***********************************************************
* UNPACKER MODULE
@@ -1673,7 +1786,6 @@ function unpack(source) {
}
}
/* {GE END} */
+2 -2
View File
@@ -5,7 +5,7 @@
"name": "Hamzo & Cufiy",
"icon": "https://cdn.discordapp.com/avatars/623644371819954226/591ecab10b0b4535e859bb0b9bbe62e5?size=1024"
},
"version": "0.3.16",
"version": "0.3.17",
"language": "German (DUB)",
"streamType": "HLS",
"quality": "720p",
@@ -17,4 +17,4 @@
"type": "shows",
"supportsSora": true,
"supportsLuna": true
}
}
+235 -123
View File
@@ -376,7 +376,7 @@ function base64Decode(str) {
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/* {VERSION: 1.2.0} */
/**
* @name global_extractor.js
@@ -384,8 +384,8 @@ function base64Decode(str) {
* @author Cufiy
* @url https://github.com/JMcrafter26/sora-global-extractor
* @license CUSTOM LICENSE - see https://github.com/JMcrafter26/sora-global-extractor/blob/main/LICENSE
* @date 2025-11-05 15:44:57
* @version 1.1.8
* @date 2026-01-03 19:28:28
* @version 1.2.0
* @note This file was generated automatically.
* The global extractor comes with an auto-updating feature, so you can always get the latest version. https://github.com/JMcrafter26/sora-global-extractor#-auto-updater
*/
@@ -395,12 +395,20 @@ function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is an object with streamUrl property
if (streamUrl && typeof streamUrl === "object" && !Array.isArray(streamUrl) && streamUrl.streamUrl) {
return streamUrl.streamUrl;
}
// check if streamUrl is not null, a string, and starts with http or https
if (streamUrl && typeof streamUrl === "string" && (streamUrl.startsWith("http"))) {
if (
streamUrl &&
typeof streamUrl === "string" &&
streamUrl.startsWith("http")
) {
return streamUrl;
// if its an array, get the value that starts with http
} else if (Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
const httpStream = streamUrl.find((url) => url.startsWith("http"));
if (httpStream) {
return httpStream;
}
@@ -408,7 +416,6 @@ function globalExtractor(providers) {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
@@ -420,16 +427,30 @@ async function multiExtractor(providers) {
/* this scheme should be returned as a JSON object
{
"streams": [
"FileMoon",
"https://filemoon.example/stream1.m3u8",
"StreamWish",
"https://streamwish.example/stream2.m3u8",
"Okru",
"https://okru.example/stream3.m3u8",
"MP4",
"https://mp4upload.example/stream4.mp4",
"Default",
"https://default.example/stream5.m3u8"
{
"title": "FileMoon",
"streamUrl": "https://filemoon.example/stream1.m3u8",
},
{
"title": "StreamWish",
"streamUrl": "https://streamwish.example/stream2.m3u8",
},
{
"title": "Okru",
"streamUrl": "https://okru.example/stream3.m3u8",
"headers": { // Optional headers for the stream
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Referer": "https://okru.example/",
},
},
{
"title": "MP4",
"streamUrl": "https://mp4upload.example/stream4.mp4",
},
{
"title": "Default",
"streamUrl": "https://default.example/stream5.m3u8"
}
]
}
*/
@@ -441,20 +462,21 @@ async function multiExtractor(providers) {
// if provider starts with "direct-", then add the url to the streams array directly
if (provider.startsWith("direct-")) {
const directName = provider.slice(7); // remove "direct-" prefix
if (directName && directName.length > 0) {
streams.push(directName, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (directName && directName.length > 0) ? directName : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
if (provider.startsWith("direct")) {
provider = provider.slice(7); // remove "direct-" prefix
if (provider && provider.length > 0) {
streams.push(provider, url);
} else {
streams.push("Direct", url); // fallback to "Direct" if no name is provided
}
const title = (provider && provider.length > 0) ? provider : "Direct";
streams.push({
title: title,
streamUrl: url
});
continue; // skip to the next provider
}
let customName = null; // to store the custom name if provided
@@ -471,15 +493,24 @@ async function multiExtractor(providers) {
console.log(`Skipping ${provider} as it has already 3 streams`);
continue;
}
let streamUrl = await extractStreamUrlByProvider(url, provider);
if (streamUrl && Array.isArray(streamUrl)) {
const httpStream = streamUrl.find(url => url.startsWith("http"));
let result = await extractStreamUrlByProvider(url, provider);
let streamUrl = null;
let headers = null;
// Check if result is an object with streamUrl and optional headers
if (result && typeof result === "object" && !Array.isArray(result) && result.streamUrl) {
streamUrl = result.streamUrl;
headers = result.headers || null;
} else if (result && Array.isArray(result)) {
const httpStream = result.find((url) => url.startsWith("http"));
if (httpStream) {
streamUrl = httpStream;
}
} else if (result && typeof result === "string") {
streamUrl = result;
}
// check if provider is already in streams, if it is, add a number to it
// check if streamUrl is valid
if (
!streamUrl ||
typeof streamUrl !== "string" ||
@@ -493,22 +524,29 @@ async function multiExtractor(providers) {
provider = customName;
}
let title;
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
title = provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
(providersCount[provider] - 1); // add a number to the provider name
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
title = provider.charAt(0).toUpperCase() + provider.slice(1);
}
const streamObject = {
title: title,
streamUrl: streamUrl
};
// Add headers if they exist
if (headers && typeof headers === "object" && Object.keys(headers).length > 0) {
streamObject.headers = headers;
}
streams.push(streamObject);
} catch (error) {
// Ignore the error and try the next provider
}
@@ -519,73 +557,98 @@ async function multiExtractor(providers) {
async function extractStreamUrlByProvider(url, provider) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
// skip if the extractor is not defined
console.log(`Extractor for provider ${provider} is not defined, skipping...`);
console.log(
`Extractor for provider ${provider} is not defined, skipping...`
);
return null;
}
let uas = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36",
];
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": uas[(url.length + provider.length) % uas.length], // use a different user agent based on the url and provider
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"x-Requested-With": "XMLHttpRequest"
"x-Requested-With": "XMLHttpRequest",
};
if(provider == 'bigwarp') {
delete headers["User-Agent"];
headers["x-requested-with"] = "XMLHttpRequest";
} else if (provider == 'vk') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'sibnet') {
headers["encoding"] = "windows-1251"; // required
} else if (provider == 'supervideo') {
delete headers["User-Agent"];
switch (provider) {
case "bigwarp":
delete headers["User-Agent"];
break;
case "vk":
case "sibnet":
headers["encoding"] = "windows-1251"; // required
break;
case "supervideo":
case "savefiles":
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "EchoapiRuntime/1.1.0",
"Connection": "keep-alive",
"Cache-Control": "no-cache",
"Host": url.match(/https?:\/\/([^\/]+)/)[1],
};
break;
case "streamtape":
headers = {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
break;
}
// console.log("Using headers: " + JSON.stringify(headers));
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
headers
});
headers,
});
console.log("Response: " + response.status);
let html = response.text ? await response.text() : response;
// if title contains redirect, then get the redirect url
const title = html.match(/<title>(.*?)<\/title>/);
if (title && title[1].toLowerCase().includes("redirect")) {
const redirectUrl = html.match(/<meta http-equiv="refresh" content="0;url=(.*?)"/);
const redirectUrl2 = html.match(/window\.location\.href\s*=\s*["'](.*?)["']/);
const redirectUrl3 = html.match(/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/);
if (redirectUrl) {
console.log("Redirect URL: " + redirectUrl[1]);
url = redirectUrl[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl2) {
console.log("Redirect URL 2: " + redirectUrl2[1]);
url = redirectUrl2[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else if (redirectUrl3) {
console.log("Redirect URL 3: " + redirectUrl3[1]);
url = redirectUrl3[1];
html = await soraFetch(url, {
headers
});
html = html.text ? await html.text() : html;
} else {
console.log("No redirect URL found");
const matches = [
/<meta http-equiv="refresh" content="0;url=(.*?)"/,
/window\.location\.href\s*=\s*["'](.*?)["']/,
/window\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
/window\.location\s*=\s*["'](.*?)["']/,
/window\.location\.assign\s*\(\s*["'](.*?)["']\s*\)/,
/top\.location\s*=\s*["'](.*?)["']/,
/top\.location\.replace\s*\(\s*["'](.*?)["']\s*\)/,
];
for (const match of matches) {
const redirectUrl = html.match(match);
if (redirectUrl && redirectUrl[1] && typeof redirectUrl[1] === "string" && redirectUrl[1].startsWith("http")) {
console.log("Redirect URL found: " + redirectUrl[1]);
url = redirectUrl[1];
headers['Referer'] = url;
headers['Host'] = url.match(/https?:\/\/([^\/]+)/)[1];
html = await soraFetch(url, {
headers,
}).then((res) => res.text());
break;
}
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
@@ -634,6 +697,20 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "oneupload":
try {
return await oneuploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from oneupload:", error);
return null;
}
case "packer":
try {
return await packerExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from packer:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
@@ -648,6 +725,13 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "smoothpre":
try {
return await smoothpreExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from smoothpre:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
@@ -662,13 +746,6 @@ async function extractStreamUrlByProvider(url, provider) {
console.log("Error extracting stream URL from streamup:", error);
return null;
}
case "supervideo":
try {
return await supervideoExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from supervideo:", error);
return null;
}
case "uploadcx":
try {
return await uploadcxExtractor(html, url);
@@ -717,7 +794,6 @@ async function extractStreamUrlByProvider(url, provider) {
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
@@ -1204,7 +1280,6 @@ async function megacloudExtractor(html, embedUrl) {
* @author Cufiy
*/
async function mp4uploadExtractor(html, url = null) {
// src: "https://a4.mp4upload.com:183/d/xkx3b4etz3b4quuo66rbmyqtjjoivahfxp27f35pti45rzapbvj5xwb4wuqtlpewdz4dirfp/video.mp4"
const regex = /src:\s*"([^"]+)"/;
const match = html.match(regex);
if (match) {
@@ -1214,6 +1289,32 @@ async function mp4uploadExtractor(html, url = null) {
return null;
}
}
/* --- oneupload --- */
/**
* @name oneuploadExtractor
* @author 50/50
*/
async function oneuploadExtractor(data, url = null) {
const match = data.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
}
/* --- packer --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name packerExtractor
* @author 50/50
*/
async function packerExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const m3u8Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const m3u8Url = m3u8Match[1];
return m3u8Url;
}
/* --- sendvid --- */
/**
@@ -1249,6 +1350,29 @@ async function sibnetExtractor(html, embedUrl) {
return null;
}
}
/* --- smoothpre --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SmoothPre Extractor
* @author 50/50
*/
async function smoothpreExtractor(data, url = null) {
console.log("Using SmoothPre Extractor");
console.log("Data Length: " + data.length);
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript || !obfuscatedScript[1]) {
console.log("No obfuscated script found");
return null;
}
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
}
/* --- streamtape --- */
/**
@@ -1332,26 +1456,6 @@ async function streamupExtractor(data, url = null) {
return null;
}
}
/* --- supervideo --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name SuperVideo Extractor
* @author 50/50
*/
async function supervideoExtractor(data, url = null) {
const obfuscatedScript = data.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const regex = /file:\s*"([^"]+\.m3u8)"/;
const match = regex.exec(unpackedScript);
if (match) {
const fileUrl = match[1];
console.log("File URL:" + fileUrl);
return fileUrl;
}
return "No stream found";
}
/* --- uploadcx --- */
/**
@@ -1416,7 +1520,7 @@ async function vidmolyExtractor(html, url = null) {
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await soraFetch(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
@@ -1542,6 +1646,7 @@ function voeShiftChars(str, shift) {
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
@@ -1559,17 +1664,25 @@ function voeShiftChars(str, shift) {
* @returns {Promise<Response|null>} The response from the server, or null if the
* request failed.
*/
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null
);
} catch (e) {
try {
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
await console.log('soraFetch error: ' + error.message);
return null;
}
return await fetch(url, options);
} catch (error) {
await console.log("soraFetch error: " + error.message);
return null;
}
}
}
/***********************************************************
* UNPACKER MODULE
@@ -1673,7 +1786,6 @@ function unpack(source) {
}
}
/* {GE END} */