From 92f3a20556b3c7d2c3ade84b5c1d315594cdd638 Mon Sep 17 00:00:00 2001 From: JMcrafter26 <77780772+JMcrafter26@users.noreply.github.com> Date: Sat, 3 Jan 2026 19:42:27 +0100 Subject: [PATCH] Bump versions to 0.3.17 for English and German Dubs; update global extractor to version 1.2.0 with enhanced stream URL extraction and new extractor functions for oneupload, packer, and smoothpre. Refactor stream handling to support headers and improve error handling in fetch requests. --- animetoast/animetoast.json | 4 +- animetoast/animetoast_v2.js | 358 +++++++++++++++++---------- aniworld/AniWorldEngSub.json | 9 +- aniworld/AniWorldGerDub.dev.json | 7 +- aniworld/AniWorldGerDub.json | 9 +- aniworld/AniWorldGerSub.json | 9 +- aniworld/v2/AniWorldEngSub_v2.js | 401 ++++++++++++++++++++----------- aniworld/v2/AniWorldGerDub_v2.js | 396 +++++++++++++++++++----------- aniworld/v2/AniWorldGerSub_v2.js | 397 +++++++++++++++++++----------- dorabash/dorabash.js | 358 +++++++++++++++++---------- dorabash/dorabash.json | 4 +- fireanime/FireAnimeGer.json | 4 +- fireanime/FireAnimeGerDub.json | 4 +- fireanime/fireanime.json | 4 +- fireanime/v2/FireAnimeEngSub.js | 358 +++++++++++++++++---------- fireanime/v2/FireAnimeGerDub.js | 358 +++++++++++++++++---------- fireanime/v2/FireAnimeGerSub.js | 358 +++++++++++++++++---------- s.to/sToEngDub.json | 4 +- s.to/sToEngDub_v2.js | 358 +++++++++++++++++---------- s.to/sToGerDub.json | 4 +- s.to/sToGerDub_v2.js | 358 +++++++++++++++++---------- 21 files changed, 2467 insertions(+), 1295 deletions(-) diff --git a/animetoast/animetoast.json b/animetoast/animetoast.json index f513aea..20e90eb 100644 --- a/animetoast/animetoast.json +++ b/animetoast/animetoast.json @@ -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 -} +} \ No newline at end of file diff --git a/animetoast/animetoast_v2.js b/animetoast/animetoast_v2.js index 0cd2cc8..23d2a68 100644 --- a/animetoast/animetoast_v2.js +++ b/animetoast/animetoast_v2.js @@ -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>/); 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} */ diff --git a/aniworld/AniWorldEngSub.json b/aniworld/AniWorldEngSub.json index b9600ea..4212d06 100644 --- a/aniworld/AniWorldEngSub.json +++ b/aniworld/AniWorldEngSub.json @@ -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 -} +} \ No newline at end of file diff --git a/aniworld/AniWorldGerDub.dev.json b/aniworld/AniWorldGerDub.dev.json index 61b04d6..598f977 100644 --- a/aniworld/AniWorldGerDub.dev.json +++ b/aniworld/AniWorldGerDub.dev.json @@ -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, diff --git a/aniworld/AniWorldGerDub.json b/aniworld/AniWorldGerDub.json index 2354a0b..ff0be4a 100644 --- a/aniworld/AniWorldGerDub.json +++ b/aniworld/AniWorldGerDub.json @@ -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 -} +} \ No newline at end of file diff --git a/aniworld/AniWorldGerSub.json b/aniworld/AniWorldGerSub.json index 0484cb4..62df763 100644 --- a/aniworld/AniWorldGerSub.json +++ b/aniworld/AniWorldGerSub.json @@ -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 -} +} \ No newline at end of file diff --git a/aniworld/v2/AniWorldEngSub_v2.js b/aniworld/v2/AniWorldEngSub_v2.js index 3d1e322..7a0dd8b 100644 --- a/aniworld/v2/AniWorldEngSub_v2.js +++ b/aniworld/v2/AniWorldEngSub_v2.js @@ -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} */ \ No newline at end of file +/* {GE END} */ diff --git a/aniworld/v2/AniWorldGerDub_v2.js b/aniworld/v2/AniWorldGerDub_v2.js index b44c5d5..eca332b 100644 --- a/aniworld/v2/AniWorldGerDub_v2.js +++ b/aniworld/v2/AniWorldGerDub_v2.js @@ -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} */ \ No newline at end of file +/* {GE END} */ diff --git a/aniworld/v2/AniWorldGerSub_v2.js b/aniworld/v2/AniWorldGerSub_v2.js index 5508406..6411808 100644 --- a/aniworld/v2/AniWorldGerSub_v2.js +++ b/aniworld/v2/AniWorldGerSub_v2.js @@ -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} */ \ No newline at end of file +/* {GE END} */ diff --git a/dorabash/dorabash.js b/dorabash/dorabash.js index 8daaf19..7f453d4 100644 --- a/dorabash/dorabash.js +++ b/dorabash/dorabash.js @@ -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} */ diff --git a/dorabash/dorabash.json b/dorabash/dorabash.json index 9a1a62c..8d74a52 100644 --- a/dorabash/dorabash.json +++ b/dorabash/dorabash.json @@ -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 -} +} \ No newline at end of file diff --git a/fireanime/FireAnimeGer.json b/fireanime/FireAnimeGer.json index ac953fe..7d81c66 100644 --- a/fireanime/FireAnimeGer.json +++ b/fireanime/FireAnimeGer.json @@ -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 -} +} \ No newline at end of file diff --git a/fireanime/FireAnimeGerDub.json b/fireanime/FireAnimeGerDub.json index 86c993b..6a8b687 100644 --- a/fireanime/FireAnimeGerDub.json +++ b/fireanime/FireAnimeGerDub.json @@ -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 -} +} \ No newline at end of file diff --git a/fireanime/fireanime.json b/fireanime/fireanime.json index efd8daa..aaabc6f 100644 --- a/fireanime/fireanime.json +++ b/fireanime/fireanime.json @@ -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 -} +} \ No newline at end of file diff --git a/fireanime/v2/FireAnimeEngSub.js b/fireanime/v2/FireAnimeEngSub.js index a581be0..6fcd4f3 100644 --- a/fireanime/v2/FireAnimeEngSub.js +++ b/fireanime/v2/FireAnimeEngSub.js @@ -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} */ diff --git a/fireanime/v2/FireAnimeGerDub.js b/fireanime/v2/FireAnimeGerDub.js index fef42ce..f6879c0 100644 --- a/fireanime/v2/FireAnimeGerDub.js +++ b/fireanime/v2/FireAnimeGerDub.js @@ -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} */ \ No newline at end of file diff --git a/fireanime/v2/FireAnimeGerSub.js b/fireanime/v2/FireAnimeGerSub.js index eb9a3ed..ca94205 100644 --- a/fireanime/v2/FireAnimeGerSub.js +++ b/fireanime/v2/FireAnimeGerSub.js @@ -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} */ \ No newline at end of file diff --git a/s.to/sToEngDub.json b/s.to/sToEngDub.json index 951f272..ebac88e 100644 --- a/s.to/sToEngDub.json +++ b/s.to/sToEngDub.json @@ -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 -} +} \ No newline at end of file diff --git a/s.to/sToEngDub_v2.js b/s.to/sToEngDub_v2.js index a8eeaa0..66a7e75 100644 --- a/s.to/sToEngDub_v2.js +++ b/s.to/sToEngDub_v2.js @@ -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} */ diff --git a/s.to/sToGerDub.json b/s.to/sToGerDub.json index 6c74f77..ab30050 100644 --- a/s.to/sToGerDub.json +++ b/s.to/sToGerDub.json @@ -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 -} +} \ No newline at end of file diff --git a/s.to/sToGerDub_v2.js b/s.to/sToGerDub_v2.js index 283f223..8055c7d 100644 --- a/s.to/sToGerDub_v2.js +++ b/s.to/sToGerDub_v2.js @@ -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} */