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} */