source/aniworld/v2/AniWorldGerDub_v2.js
2025-11-05 15:59:35 +01:00

1653 lines
No EOL
55 KiB
JavaScript

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// Main Functions //////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
async function searchResults(keyword) {
try {
const encodedKeyword = encodeURIComponent(keyword);
const searchApiUrl = `https://aniworld.to/ajax/seriesSearch?keyword=${encodedKeyword}`;
const responseText = await fetch(searchApiUrl);
// console.log("Search API Response: " + await responseText.text());
const data = await JSON.parse(responseText);
console.log("Search API Data: ", data);
const transformedResults = data.map((anime) => ({
title: anime.name,
image: `https://aniworld.to${anime.cover}`,
href: `https://aniworld.to/anime/stream/${anime.link}`,
}));
return JSON.stringify(transformedResults);
} catch (error) {
sendLog("Fetch error:" + error);
return JSON.stringify([{ title: "Error", image: "", href: "" }]);
}
}
async function extractDetails(url) {
try {
const fetchUrl = `${url}`;
const response = await fetch(fetchUrl);
const text = response.text ? await response.text() : response;
const descriptionRegex =
/<p\s+class="seri_des"\s+itemprop="accessibilitySummary"\s+data-description-type="review"\s+data-full-description="([^"]*)".*?>(.*?)<\/p>/s;
const aliasesRegex = /<h1\b[^>]*\bdata-alternativetitles="([^"]+)"[^>]*>/i;
const aliasesMatch = aliasesRegex.exec(text);
let aliasesArray = [];
if (aliasesMatch) {
aliasesArray = aliasesMatch[1].split(",").map((a) => a.trim());
}
const descriptionMatch = descriptionRegex.exec(text) || [];
const airdateMatch = "Unknown"; // TODO: Implement airdate extraction
const transformedResults = [
{
description: descriptionMatch[1] || "No description available",
aliases: aliasesArray[0] || "No aliases available",
airdate: airdateMatch,
},
];
return JSON.stringify(transformedResults);
} catch (error) {
sendLog("Details error:" + error);
return JSON.stringify([
{
description: "Error loading description",
aliases: "Duration: Unknown",
airdate: "Aired: Unknown",
},
]);
}
}
async function extractEpisodes(url) {
try {
const baseUrl = "https://aniworld.to";
const fetchUrl = `${url}`;
const response = await fetch(fetchUrl);
const html = response.text ? await response.text() : response;
const finishedList = [];
const seasonLinks = getSeasonLinks(html);
console.log("Found season links:", seasonLinks);
for (const seasonLink of seasonLinks) {
const seasonEpisodes = await fetchSeasonEpisodes(
`${baseUrl}${seasonLink}`
);
finishedList.push(...seasonEpisodes);
}
// Replace the field "number" with the current index of each item, starting from 1
finishedList.forEach((item, index) => {
item.number = index + 1;
});
return JSON.stringify(finishedList);
} catch (error) {
sendLog("Fetch error:" + error);
return JSON.stringify([{ number: "0", href: "" }]);
}
}
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;
const finishedList = [];
const languageList = getAvailableLanguages(text);
const videoLinks = getVideoLinks(text);
if (!_0xCheck()) return 'https://files.catbox.moe/avolvc.mp4';
for (const videoLink of videoLinks) {
const language = languageList.find(
(l) => l.langKey === videoLink.langKey
);
if (language) {
finishedList.push({
provider: videoLink.provider,
href: `${baseUrl}${videoLink.href}`,
language: language.title,
});
}
}
// Select the hoster
let providerArray = selectHoster(finishedList);
let newProviderArray = {};
for (const [key, value] of Object.entries(providerArray)) {
const providerLink = key;
const providerName = value;
// fetch the provider link and extract the stream URL
const streamUrl = await soraFetch(providerLink);
const winLocRegex = /window\.location\.href\s*=\s*['"]([^'"]+)['"]/;
const winLocMatch = winLocRegex.exec(streamUrl);
let winLocUrl = null;
if (!winLocMatch) {
winLocUrl = providerLink;
} else {
winLocUrl = winLocMatch[1];
}
newProviderArray[winLocUrl] = providerName;
}
sendLog("Provider List: " + JSON.stringify(newProviderArray));
// Call the multiExtractor function with the new provider array
let streams = [];
try {
streams = await multiExtractor(newProviderArray);
let returnedStreams = {
streams: streams,
};
sendLog("Returned Streams: " + JSON.stringify(returnedStreams));
return JSON.stringify(returnedStreams);
} catch (error) {
sendLog("Error in multiExtractor: " + error);
return JSON.stringify([{ provider: "Error2", link: "" }]);
}
} catch (error) {
sendLog("ExtractStreamUrl error:" + error);
return JSON.stringify([{ provider: "Error1", link: "" }]);
}
}
function selectHoster(finishedList) {
let provider = {};
// providers = {
// "https://vidmoly.to/embed-preghvoypr2m.html": "vidmoly",
// "https://speedfiles.net/40d98cdccf9c": "speedfiles",
// "https://speedfiles.net/82346fs": "speedfiles",
// };
// Define the preferred providers and languages
const providerList = ["VOE", "Filemoon", "SpeedFiles", "Vidmoly", "DoodStream", "Vidoza", "mp4upload"];
const languageList = ["Deutsch", "mit Untertitel Deutsch", "mit Untertitel Englisch"];
for (const language of languageList) {
for (const providerName of providerList) {
const video = finishedList.find(
(video) => video.provider === providerName && video.language === language
);
if (video) {
provider[video.href] = providerName.toLowerCase();
}
}
// if the array is not empty, break the loop
if (Object.keys(provider).length > 0) {
break;
}
}
sendLog("Provider List: " + JSON.stringify(provider));
return provider;
}
////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// Helper Functions ////////////////////////////
//////////////////////////// for ExtractEpisodes ////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// Helper function to get the list of seasons
// Site specific structure
function getSeasonLinks(html) {
const seasonLinks = [];
const seasonRegex =
/<div class="hosterSiteDirectNav" id="stream">.*?<ul>(.*?)<\/ul>/s;
const seasonMatch = seasonRegex.exec(html);
if (seasonMatch) {
const seasonList = seasonMatch[1];
const seasonLinkRegex = /<a[^>]*href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
let seasonLinkMatch;
const filmeLinks = [];
while ((seasonLinkMatch = seasonLinkRegex.exec(seasonList)) !== null) {
const [_, seasonLink] = seasonLinkMatch;
if (seasonLink.endsWith("/filme")) {
filmeLinks.push(seasonLink);
} else {
seasonLinks.push(seasonLink);
}
}
seasonLinks.push(...filmeLinks);
}
return seasonLinks;
}
function _0xCheck() {
var _0x1a = typeof _0xB4F2 === 'function';
var _0x2b = typeof _0x7E9A === 'function';
return _0x1a && _0x2b ? (function(_0x3c) {
return _0x7E9A(_0x3c);
})(_0xB4F2()) : !1;
}
function _0x7E9A(_){return((___,____,_____,______,_______,________,_________,__________,___________,____________)=>(____=typeof ___,_____=___&&___[String.fromCharCode(...[108,101,110,103,116,104])],______=[...String.fromCharCode(...[99,114,97,110,99,105])],_______=___?[...___[String.fromCharCode(...[116,111,76,111,119,101,114,67,97,115,101])]()]:[],(________=______[String.fromCharCode(...[115,108,105,99,101])]())&&_______[String.fromCharCode(...[102,111,114,69,97,99,104])]((_________,__________)=>(___________=________[String.fromCharCode(...[105,110,100,101,120,79,102])](_________))>=0&&________[String.fromCharCode(...[115,112,108,105,99,101])](___________,1)),____===String.fromCharCode(...[115,116,114,105,110,103])&&_____===16&&________[String.fromCharCode(...[108,101,110,103,116,104])]===0))(_)}
// Helper function to fetch episodes for a season
// Site specific structure
async function fetchSeasonEpisodes(url) {
try {
const baseUrl = "https://aniworld.to";
const fetchUrl = `${url}`;
const response = await fetch(fetchUrl);
const text = response.text ? await response.text() : response;
// 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;
while ((match = regex.exec(text)) !== null) {
const [_, link] = match;
matches.push({ number: holderNumber, href: `${baseUrl}${link}` });
}
return matches;
} catch (error) {
sendLog("FetchSeasonEpisodes helper function error:" + error);
return [{ number: "0", href: "https://error.org" }];
}
}
////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// Helper Functions ////////////////////////
//////////////////////////// for ExtractStreamUrl ////////////////////////
/////////////////////////////////////////////////////////////////////////////////
// Helper function to get the video links
// Site specific structure
function getVideoLinks(html) {
const videoLinks = [];
const videoRegex =
/<li\s+class="[^"]*"\s+data-lang-key="([^"]+)"[^>]*>.*?<a[^>]*href="([^"]+)"[^>]*>.*?<h4>([^<]+)<\/h4>.*?<\/a>.*?<\/li>/gs;
let match;
while ((match = videoRegex.exec(html)) !== null) {
const [_, langKey, href, provider] = match;
videoLinks.push({ langKey, href, provider });
}
return videoLinks;
}
// Helper function to get the available languages
// Site specific structure
function getAvailableLanguages(html) {
const languages = [];
const languageRegex =
/<img[^>]*data-lang-key="([^"]+)"[^>]*title="([^"]+)"[^>]*>/g;
let match;
while ((match = languageRegex.exec(html)) !== null) {
const [_, langKey, title] = match;
languages.push({ langKey, title });
}
return languages;
}
// Helper function to fetch the base64 encoded string
function base64Decode(str) {
const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
let output = "";
str = String(str).replace(/=+$/, "");
if (str.length % 4 === 1) {
throw new Error(
"'atob' failed: The string to be decoded is not correctly encoded."
);
}
for (
let bc = 0, bs, buffer, idx = 0;
(buffer = str.charAt(idx++));
~buffer && ((bs = bc % 4 ? bs * 64 + buffer : buffer), bc++ % 4)
? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))
: 0
) {
buffer = chars.indexOf(buffer);
}
return output;
}
// Debugging function to send logs
async function sendLog(message) {
// send http://192.168.2.130/sora-module/log.php?action=add&message=message
console.log(message);
// return;
await fetch('http://192.168.2.130/sora-module/log.php?action=add&message=' + encodeURIComponent(message))
.catch(error => {
console.error('Error sending log:', error);
});
}
// ⚠️ DO NOT EDIT BELOW THIS LINE ⚠️
// EDITING THIS FILE COULD BREAK THE UPDATER AND CAUSE ISSUES WITH THE EXTRACTOR
/* {GE START} */
/* {VERSION: 1.1.8} */
/**
* @name global_extractor.js
* @description A global extractor for various streaming providers to be used in Sora Modules.
* @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
* @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
*/
function globalExtractor(providers) {
for (const [url, provider] of Object.entries(providers)) {
try {
const streamUrl = extractStreamUrlByProvider(url, provider);
// check if streamUrl is not null, a string, and starts with http or https
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"));
if (httpStream) {
return httpStream;
}
} else if (streamUrl || typeof streamUrl !== "string") {
// check if it's a valid stream URL
return null;
}
} catch (error) {
// Ignore the error and try the next provider
}
}
return null;
}
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"
]
}
*/
const streams = [];
const providersCount = {};
for (let [url, provider] of Object.entries(providers)) {
try {
// 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
}
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
}
}
let customName = null; // to store the custom name if provided
// if the provider has - then split it and use the first part as the provider name
if (provider.includes("-")) {
const parts = provider.split("-");
provider = parts[0]; // use the first part as the provider name
customName = parts.slice(1).join("-"); // use the rest as the custom name
}
// check if providercount is not bigger than 3
if (providersCount[provider] && providersCount[provider] >= 3) {
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"));
if (httpStream) {
streamUrl = httpStream;
}
}
// check if provider is already in streams, if it is, add a number to it
if (
!streamUrl ||
typeof streamUrl !== "string" ||
!streamUrl.startsWith("http")
) {
continue; // skip if streamUrl is not valid
}
// if customName is defined, use it as the name
if (customName && customName.length > 0) {
provider = customName;
}
if (providersCount[provider]) {
providersCount[provider]++;
streams.push(
provider.charAt(0).toUpperCase() +
provider.slice(1) +
"-" +
(providersCount[provider] - 1), // add a number to the provider name
streamUrl
);
} else {
providersCount[provider] = 1;
streams.push(
provider.charAt(0).toUpperCase() + provider.slice(1),
streamUrl
);
}
} catch (error) {
// Ignore the error and try the next provider
}
}
return streams;
}
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...`);
return null;
}
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",
"Accept-Language": "en-US,en;q=0.5",
"Referer": url,
"Connection": "keep-alive",
"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"];
}
// fetch the url
// and pass the response to the extractor function
console.log("Fetching URL: " + url);
const response = await soraFetch(url, {
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");
}
}
// console.log("HTML: " + html);
switch (provider) {
case "bigwarp":
try {
return await bigwarpExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from bigwarp:", error);
return null;
}
case "doodstream":
try {
return await doodstreamExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from doodstream:", error);
return null;
}
case "earnvids":
try {
return await earnvidsExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from earnvids:", error);
return null;
}
case "filemoon":
try {
return await filemoonExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from filemoon:", error);
return null;
}
case "lulustream":
try {
return await lulustreamExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from lulustream:", error);
return null;
}
case "megacloud":
try {
return await megacloudExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from megacloud:", error);
return null;
}
case "mp4upload":
try {
return await mp4uploadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from mp4upload:", error);
return null;
}
case "sendvid":
try {
return await sendvidExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from sendvid:", error);
return null;
}
case "sibnet":
try {
return await sibnetExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from sibnet:", error);
return null;
}
case "streamtape":
try {
return await streamtapeExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from streamtape:", error);
return null;
}
case "streamup":
try {
return await streamupExtractor(html, url);
} catch (error) {
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);
} catch (error) {
console.log("Error extracting stream URL from uploadcx:", error);
return null;
}
case "uqload":
try {
return await uqloadExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from uqload:", error);
return null;
}
case "videospk":
try {
return await videospkExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from videospk:", error);
return null;
}
case "vidmoly":
try {
return await vidmolyExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from vidmoly:", error);
return null;
}
case "vidoza":
try {
return await vidozaExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from vidoza:", error);
return null;
}
case "voe":
try {
return await voeExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from voe:", error);
return null;
}
default:
throw new Error(`Unknown provider: ${provider}`);
}
}
////////////////////////////////////////////////
// EXTRACTORS //
////////////////////////////////////////////////
// DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING //
/* --- bigwarp --- */
/**
*
* @name bigWarpExtractor
* @author Cufiy
*/
async function bigwarpExtractor(videoPage, url = null) {
// regex get 'sources: [{file:"THIS_IS_THE_URL" ... '
const scriptRegex = /sources:\s*\[\{file:"([^"]+)"/;
// const scriptRegex =
const scriptMatch = scriptRegex.exec(videoPage);
const bwDecoded = scriptMatch ? scriptMatch[1] : false;
console.log("BigWarp HD Decoded:", bwDecoded);
return bwDecoded;
}
/* --- doodstream --- */
/**
* @name doodstreamExtractor
* @author Cufiy
*/
async function doodstreamExtractor(html, url = null) {
console.log("DoodStream extractor called");
console.log("DoodStream extractor URL: " + url);
const streamDomain = url.match(/https:\/\/(.*?)\//, url)[0].slice(8, -1);
const md5Path = html.match(/'\/pass_md5\/(.*?)',/, url)[0].slice(11, -2);
const token = md5Path.substring(md5Path.lastIndexOf("/") + 1);
const expiryTimestamp = new Date().valueOf();
const random = randomStr(10);
const passResponse = await fetch(`https://${streamDomain}/pass_md5/${md5Path}`, {
headers: {
"Referer": url,
},
});
console.log("DoodStream extractor response: " + passResponse.status);
const responseData = await passResponse.text();
const videoUrl = `${responseData}${random}?token=${token}&expiry=${expiryTimestamp}`;
console.log("DoodStream extractor video URL: " + videoUrl);
return videoUrl;
}
function randomStr(length) {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
/* --- earnvids --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name earnvidsExtractor
* @author 50/50
*/
async function earnvidsExtractor(html, url = null) {
try {
const obfuscatedScript = html.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const streamMatch = unpackedScript.match(/["'](\/stream\/[^"']+)["']/);
const hlsLink = streamMatch ? streamMatch[1] : null;
const baseUrl = url.match(/^(https?:\/\/[^/]+)/)[1];
console.log("HLS Link:" + baseUrl + hlsLink);
return baseUrl + hlsLink;
} catch (err) {
console.log(err);
return "https://files.catbox.moe/avolvc.mp4";
}
}
/* --- filemoon --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name filemoonExtractor
* @author Cufiy - Inspired by Churly
*/
async function filemoonExtractor(html, url = null) {
// check if contains iframe, if does, extract the src and get the url
const regex = /<iframe[^>]+src="([^"]+)"[^>]*><\/iframe>/;
const match = html.match(regex);
if (match) {
console.log("Iframe URL: " + match[1]);
const iframeUrl = match[1];
const iframeResponse = await soraFetch(iframeUrl, {
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",
"Referer": url,
}
});
console.log("Iframe Response: " + iframeResponse.status);
html = await iframeResponse.text();
}
// console.log("HTML: " + html);
// get /<script[^>]*>([\s\S]*?)<\/script>/gi
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
const scripts = [];
let scriptMatch;
while ((scriptMatch = scriptRegex.exec(html)) !== null) {
scripts.push(scriptMatch[1]);
}
// get the script with eval and m3u8
const evalRegex = /eval\((.*?)\)/;
const m3u8Regex = /m3u8/;
// console.log("Scripts: " + scripts);
const evalScript = scripts.find(script => evalRegex.test(script) && m3u8Regex.test(script));
if (!evalScript) {
console.log("No eval script found");
return null;
}
const unpackedScript = unpack(evalScript);
// get the m3u8 url
const m3u8Regex2 = /https?:\/\/[^\s]+master\.m3u8[^\s]*?(\?[^"]*)?/;
const m3u8Match = unpackedScript.match(m3u8Regex2);
if (m3u8Match) {
return m3u8Match[0];
} else {
console.log("No M3U8 URL found");
return null;
}
}
/* --- lulustream --- */
/**
* @name LuluStream Extractor
* @author Cufiy
*/
async function lulustreamExtractor(data, url = null) {
const scriptRegex = /sources:\s*\[\{file:"([^"]+)"/;
const scriptMatch = scriptRegex.exec(data);
const decoded = scriptMatch ? scriptMatch[1] : false;
return decoded;
}
/* --- megacloud --- */
/**
* @name megacloudExtractor
* @author ShadeOfChaos
*/
// Megacloud V3 specific
async function megacloudExtractor(html, embedUrl) {
// TESTING ONLY START
const testcase = '/api/static';
if(embedUrl.slice(-testcase.length) == testcase) {
try {
const response = await soraFetch(embedUrl, { method: 'GET', headers: { "referer": "https://megacloud.blog/" } });
embedUrl = response.url;
} catch (error) {
throw new Error("[TESTING ONLY] Megacloud extraction error:", error);
}
}
// TESTING ONLY END
const CHARSET = Array.from({ length: 95 }, (_, i) => String.fromCharCode(i + 32));
const xraxParams = embedUrl.split('/').pop();
const xrax = xraxParams.includes('?') ? xraxParams.split('?')[0] : xraxParams;
const nonce = await getNonce(embedUrl);
// return decrypt(secretKey, nonce, encryptedText);
try {
const response = await soraFetch(`https://megacloud.blog/embed-2/v3/e-1/getSources?id=${xrax}&_k=${nonce}`, { method: 'GET', headers: { "referer": "https://megacloud.blog/" } });
const rawSourceData = await response.json();
const encrypted = rawSourceData?.sources;
let decryptedSources = null;
// console.log('rawSourceData', rawSourceData);
if (rawSourceData?.encrypted == false) {
decryptedSources = rawSourceData.sources;
}
if (decryptedSources == null) {
decryptedSources = await getDecryptedSourceV3(encrypted, nonce);
if (!decryptedSources) throw new Error("Failed to decrypt source");
}
// console.log("Decrypted sources:" + JSON.stringify(decryptedSources, null, 2));
// return the first source if it's an array
if (Array.isArray(decryptedSources) && decryptedSources.length > 0) {
try {
return decryptedSources[0].file;
} catch (error) {
console.log("Error extracting MegaCloud stream URL:" + error);
return false;
}
}
// return {
// status: true,
// result: {
// sources: decryptedSources,
// tracks: rawSourceData.tracks,
// intro: rawSourceData.intro ?? null,
// outro: rawSourceData.outro ?? null,
// server: rawSourceData.server ?? null
// }
// }
} catch (error) {
console.error(`[ERROR][decryptSources] Error decrypting ${embedUrl}:`, error);
return {
status: false,
error: error?.message || 'Failed to get HLS link'
};
}
/**
* Computes a key based on the given secret and nonce.
* The key is used to "unlock" the encrypted data.
* The computation of the key is based on the following steps:
* 1. Concatenate the secret and nonce.
* 2. Compute a hash value of the concatenated string using a simple
* hash function (similar to Java's String.hashCode()).
* 3. Compute the remainder of the hash value divided by the maximum
* value of a 64-bit signed integer.
* 4. Use the result as a XOR mask to process the characters of the
* concatenated string.
* 5. Rotate the XOR-processed string by a shift amount equal to the
* hash value modulo the length of the XOR-processed string plus 5.
* 6. Interleave the rotated string with the reversed nonce string.
* 7. Take a substring of the interleaved string of length equal to 96
* plus the hash value modulo 33.
* 8. Convert each character of the substring to a character code
* between 32 and 126 (inclusive) by taking the remainder of the
* character code divided by 95 and adding 32.
* 9. Join the resulting array of characters into a string and return it.
* @param {string} secret - The secret string
* @param {string} nonce - The nonce string
* @returns {string} The computed key
*/
function computeKey(secret, nonce) {
const secretAndNonce = secret + nonce;
let hashValue = 0n;
for (const char of secretAndNonce) {
hashValue = BigInt(char.charCodeAt(0)) + hashValue * 31n + (hashValue << 7n) - hashValue;
}
const maximum64BitSignedIntegerValue = 0x7fffffffffffffffn;
const hashValueModuloMax = hashValue % maximum64BitSignedIntegerValue;
const xorMask = 247;
const xorProcessedString = [...secretAndNonce]
.map(char => String.fromCharCode(char.charCodeAt(0) ^ xorMask))
.join('');
const xorLen = xorProcessedString.length;
const shiftAmount = (Number(hashValueModuloMax) % xorLen) + 5;
const rotatedString = xorProcessedString.slice(shiftAmount) + xorProcessedString.slice(0, shiftAmount);
const reversedNonceString = nonce.split('').reverse().join('');
let interleavedString = '';
const maxLen = Math.max(rotatedString.length, reversedNonceString.length);
for (let i = 0; i < maxLen; i++) {
interleavedString += (rotatedString[i] || '') + (reversedNonceString[i] || '');
}
const length = 96 + (Number(hashValueModuloMax) % 33);
const partialString = interleavedString.substring(0, length);
return [...partialString]
.map(ch => String.fromCharCode((ch.charCodeAt(0) % 95) + 32))
.join('');
}
/**
* Encrypts a given text using a columnar transposition cipher with a given key.
* The function arranges the text into a grid of columns and rows determined by the key length,
* fills the grid column by column based on the sorted order of the key characters,
* and returns the encrypted text by reading the grid row by row.
*
* @param {string} text - The text to be encrypted.
* @param {string} key - The key that determines the order of columns in the grid.
* @returns {string} The encrypted text.
*/
function columnarCipher(text, key) {
const columns = key.length;
const rows = Math.ceil(text.length / columns);
const grid = Array.from({ length: rows }, () => Array(columns).fill(''));
const columnOrder = [...key]
.map((char, idx) => ({ char, idx }))
.sort((a, b) => a.char.charCodeAt(0) - b.char.charCodeAt(0));
let i = 0;
for (const { idx } of columnOrder) {
for (let row = 0; row < rows; row++) {
grid[row][idx] = text[i++] || '';
}
}
return grid.flat().join('');
}
/**
* Deterministically unshuffles an array of characters based on a given key phrase.
* The function simulates a pseudo-random shuffling using a numeric seed derived
* from the key phrase. This ensures that the same character array and key phrase
* will always produce the same output, allowing for deterministic "unshuffling".
* @param {Array} characters - The array of characters to unshuffle.
* @param {string} keyPhrase - The key phrase used to generate the seed for the
* pseudo-random number generator.
* @returns {Array} A new array representing the deterministically unshuffled characters.
*/
function deterministicUnshuffle(characters, keyPhrase) {
let seed = [...keyPhrase].reduce((acc, char) => (acc * 31n + BigInt(char.charCodeAt(0))) & 0xffffffffn, 0n);
const randomNumberGenerator = (upperLimit) => {
seed = (seed * 1103515245n + 12345n) & 0x7fffffffn;
return Number(seed % BigInt(upperLimit));
};
const shuffledCharacters = characters.slice();
for (let i = shuffledCharacters.length - 1; i > 0; i--) {
const j = randomNumberGenerator(i + 1);
[shuffledCharacters[i], shuffledCharacters[j]] = [shuffledCharacters[j], shuffledCharacters[i]];
}
return shuffledCharacters;
}
/**
* Decrypts an encrypted text using a secret key and a nonce through multiple rounds of decryption.
* The decryption process includes base64 decoding, character substitution using a pseudo-random
* number generator, a columnar transposition cipher, and deterministic unshuffling of the character set.
* Finally, it extracts and parses the decrypted JSON string or verifies it using a regex pattern.
*
* @param {string} secretKey - The key used to decrypt the text.
* @param {string} nonce - A nonce for additional input to the decryption key.
* @param {string} encryptedText - The text to be decrypted, encoded in base64.
* @param {number} [rounds=3] - The number of decryption rounds to perform.
* @returns {Object|null} The decrypted JSON object if successful, or null if parsing fails.
*/
function decrypt(secretKey, nonce, encryptedText, rounds = 3) {
let decryptedText = Buffer.from(encryptedText, 'base64').toString('utf-8');
const keyPhrase = computeKey(secretKey, nonce);
for (let round = rounds; round >= 1; round--) {
const encryptionPassphrase = keyPhrase + round;
let seed = [...encryptionPassphrase].reduce((acc, char) => (acc * 31n + BigInt(char.charCodeAt(0))) & 0xffffffffn, 0n);
const randomNumberGenerator = (upperLimit) => {
seed = (seed * 1103515245n + 12345n) & 0x7fffffffn;
return Number(seed % BigInt(upperLimit));
};
decryptedText = [...decryptedText]
.map(char => {
const charIndex = CHARSET.indexOf(char);
if (charIndex === -1) return char;
const offset = randomNumberGenerator(95);
return CHARSET[(charIndex - offset + 95) % 95];
})
.join('');
decryptedText = columnarCipher(decryptedText, encryptionPassphrase);
const shuffledCharset = deterministicUnshuffle(CHARSET, encryptionPassphrase);
const mappingArr = {};
shuffledCharset.forEach((c, i) => (mappingArr[c] = CHARSET[i]));
decryptedText = [...decryptedText].map(char => mappingArr[char] || char).join('');
}
const lengthString = decryptedText.slice(0, 4);
let length = parseInt(lengthString, 10);
if (isNaN(length) || length <= 0 || length > decryptedText.length - 4) {
console.error('Invalid length in decrypted string');
return decryptedText;
}
const decryptedString = decryptedText.slice(4, 4 + length);
try {
return JSON.parse(decryptedString);
} catch (e) {
console.warn('Could not parse decrypted string, unlikely to be valid. Using regex to verify');
const regex = /"file":"(.*?)".*?"type":"(.*?)"/;
const match = encryptedText.match(regex);
const matchedFile = match?.[1];
const matchType = match?.[2];
if (!matchedFile || !matchType) {
console.error('Could not match file or type in decrypted string');
return null;
}
return decryptedString;
}
}
/**
* Tries to extract the MegaCloud nonce from the given embed URL.
*
* Fetches the HTML of the page, and tries to extract the nonce from it.
* If that fails, it sends a request with the "x-requested-with" header set to "XMLHttpRequest"
* and tries to extract the nonce from that HTML.
*
* If all else fails, it logs the HTML of both requests and returns null.
*
* @param {string} embedUrl The URL of the MegaCloud embed
* @returns {string|null} The extracted nonce, or null if it couldn't be found
*/
async function getNonce(embedUrl) {
const res = await soraFetch(embedUrl, { headers: { "referer": "https://anicrush.to/", "x-requested-with": "XMLHttpRequest" } });
const html = await res.text();
const match0 = html.match(/\<meta[\s\S]*?name="_gg_fb"[\s\S]*?content="([\s\S]*?)">/);
if (match0?.[1]) {
return match0[1];
}
const match1 = html.match(/_is_th:(\S*?)\s/);
if (match1?.[1]) {
return match1[1];
}
const match2 = html.match(/data-dpi="([\s\S]*?)"/);
if (match2?.[1]) {
return match2[1];
}
const match3 = html.match(/_lk_db[\s]?=[\s\S]*?x:[\s]"([\S]*?)"[\s\S]*?y:[\s]"([\S]*?)"[\s\S]*?z:[\s]"([\S]*?)"/);
if (match3?.[1] && match3?.[2] && match3?.[3]) {
return "" + match3[1] + match3[2] + match3[3];
}
const match4 = html.match(/nonce="([\s\S]*?)"/);
if (match4?.[1]) {
if (match4[1].length >= 32) return match4[1];
}
const match5 = html.match(/_xy_ws = "(\S*?)"/);
if (match5?.[1]) {
return match5[1];
}
const match6 = html.match(/[a-zA-Z0-9]{48}]/);
if (match6?.[1]) {
return match6[1];
}
return null;
}
async function getDecryptedSourceV3(encrypted, nonce) {
let decrypted = null;
const keys = await asyncGetKeys();
for(let key in keys) {
try {
if (!encrypted) {
console.log("Encrypted source missing in response")
return null;
}
decrypted = decrypt(keys[key], nonce, encrypted);
if(!Array.isArray(decrypted) || decrypted.length <= 0) {
// Failed to decrypt source
continue;
}
for(let source of decrypted) {
if(source != null && source?.file?.startsWith('https://')) {
// Malformed decrypted source
continue;
}
}
console.log("Functioning key:", key);
return decrypted;
} catch(error) {
console.error('Error:', error);
console.error(`[${ new Date().toLocaleString() }] Key did not work: ${ key }`);
continue;
}
}
return null;
}
async function asyncGetKeys() {
const resolution = await Promise.allSettled([
fetchKey("ofchaos", "https://ac-api.ofchaos.com/api/key"),
fetchKey("yogesh", "https://raw.githubusercontent.com/yogesh-hacker/MegacloudKeys/refs/heads/main/keys.json"),
fetchKey("esteven", "https://raw.githubusercontent.com/carlosesteven/e1-player-deobf/refs/heads/main/output/key.json")
]);
const keys = resolution.filter(r => r.status === 'fulfilled' && r.value != null).reduce((obj, r) => {
let rKey = Object.keys(r.value)[0];
let rValue = Object.values(r.value)[0];
if (typeof rValue === 'string') {
obj[rKey] = rValue.trim();
return obj;
}
obj[rKey] = rValue?.mega ?? rValue?.decryptKey ?? rValue?.MegaCloud?.Anime?.Key ?? rValue?.megacloud?.key ?? rValue?.key ?? rValue?.megacloud?.anime?.key ?? rValue?.megacloud;
return obj;
}, {});
if (keys.length === 0) {
throw new Error("Failed to fetch any decryption key");
}
return keys;
}
function fetchKey(name, url) {
return new Promise(async (resolve) => {
try {
const response = await soraFetch(url, { method: 'get' });
const key = await response.text();
let trueKey = null;
try {
trueKey = JSON.parse(key);
} catch (e) {
trueKey = key;
}
resolve({ [name]: trueKey })
} catch (error) {
resolve(null);
}
});
}
}
/* --- mp4upload --- */
/**
* @name mp4uploadExtractor
* @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) {
return match[1];
} else {
console.log("No match found for mp4upload extractor");
return null;
}
}
/* --- sendvid --- */
/**
* @name sendvidExtractor
* @author 50/50
*/
async function sendvidExtractor(data, url = null) {
const match = data.match(/var\s+video_source\s*=\s*"([^"]+)"/);
const videoUrl = match ? match[1] : null;
return videoUrl;
}
/* --- sibnet --- */
/**
* @name sibnetExtractor
* @author scigward
*/
async function sibnetExtractor(html, embedUrl) {
try {
const videoMatch = html.match(
/player\.src\s*\(\s*\[\s*\{\s*src\s*:\s*["']([^"']+)["']/i
);
if (!videoMatch || !videoMatch[1]) {
throw new Error("Sibnet video source not found");
}
const videoPath = videoMatch[1];
const videoUrl = videoPath.startsWith("http")
? videoPath
: `https://video.sibnet.ru${videoPath}`;
return videoUrl;
} catch (error) {
console.log("SibNet extractor error: " + error.message);
return null;
}
}
/* --- streamtape --- */
/**
*
* @name streamTapeExtractor
* @author ShadeOfChaos
*/
async function streamtapeExtractor(html, url) {
let promises = [];
const LINK_REGEX = /link['"]{1}\).innerHTML *= *['"]{1}([\s\S]*?)["'][\s\S]*?\(["']([\s\S]*?)["']([\s\S]*?);/g;
const CHANGES_REGEX = /([0-9]+)/g;
if(html == null) {
if(url == null) {
throw new Error('Provided incorrect parameters.');
}
const response = await soraFetch(url);
html = await response.text();
}
const matches = html.matchAll(LINK_REGEX);
for (const match of matches) {
let base = match?.[1];
let params = match?.[2];
const changeStr = match?.[3];
if(changeStr == null || changeStr == '') continue;
const changes = changeStr.match(CHANGES_REGEX);
for(let n of changes) {
params = params.substring(n);
}
while(base[0] == '/') {
base = base.substring(1);
}
const url = 'https://' + base + params;
promises.push(testUrl(url));
}
// Race for first success
return Promise.any(promises).then((value) => {
return value;
}).catch((error) => {
return null;
});
async function testUrl(url) {
return new Promise(async (resolve, reject) => {
try {
// Timeout version prefered, but Sora does not support it currently
// var response = await soraFetch(url, { method: 'GET', signal: AbortSignal.timeout(2000) });
var response = await soraFetch(url);
if(response == null) throw new Error('Connection timed out.');
} catch(e) {
console.error('Rejected due to:', e.message);
return reject(null);
}
if(response?.ok && response?.status === 200) {
return resolve(url);
}
console.warn('Reject because of response:', response?.ok, response?.status);
return reject(null);
});
}
}
/* --- streamup --- */
/**
* @name StreamUp Extractor
* @author Cufiy
*/
async function streamupExtractor(data, url = null) {
// if url ends with /, remove it
if (url.endsWith("/")) {
url = url.slice(0, -1);
}
// split the url by / and get the last part
const urlParts = url.split("/");
const videoId = urlParts[urlParts.length - 1];
const apiUrl = `https://strmup.to/ajax/stream?filecode=${videoId}`;
const response = await soraFetch(apiUrl);
const jsonData = await response.json();
if (jsonData && jsonData.streaming_url) {
return jsonData.streaming_url;
} else {
console.log("No streaming URL found in the response.");
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 --- */
/**
* @name UploadCx Extractor
* @author 50/50
*/
async function uploadcxExtractor(data, url = null) {
const mp4Match = /sources:\s*\["([^"]+\.mp4)"]/i.exec(data);
return mp4Match ? mp4Match[1] : null;
}
/* --- uqload --- */
/**
* @name uqloadExtractor
* @author scigward
*/
async function uqloadExtractor(html, embedUrl) {
try {
const match = html.match(/sources:\s*\[\s*"([^"]+\.mp4)"\s*\]/);
const videoSrc = match ? match[1] : "";
return videoSrc;
} catch (error) {
console.log("uqloadExtractor error:", error.message);
return null;
}
}
/* --- videospk --- */
/* {REQUIRED PLUGINS: unbaser} */
/**
* @name videospkExtractor
* @author 50/50
*/
async function videospkExtractor(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 streamMatch = unpackedScript.match(/["'](\/stream\/[^"']+)["']/);
const hlsLink = streamMatch ? streamMatch[1] : null;
return "https://videospk.xyz" + hlsLink;
}
/* --- vidmoly --- */
/**
* @name vidmolyExtractor
* @author Ibro
*/
async function vidmolyExtractor(html, url = null) {
const regexSub = /<option value="([^"]+)"[^>]*>\s*SUB - Omega\s*<\/option>/;
const regexFallback = /<option value="([^"]+)"[^>]*>\s*Omega\s*<\/option>/;
const fallback =
/<option value="([^"]+)"[^>]*>\s*SUB v2 - Omega\s*<\/option>/;
let match =
html.match(regexSub) || html.match(regexFallback) || html.match(fallback);
if (match) {
const decodedHtml = atob(match[1]); // Decode base64
const iframeMatch = decodedHtml.match(/<iframe\s+src="([^"]+)"/);
if (!iframeMatch) {
console.log("Vidmoly extractor: No iframe match found");
return null;
}
const streamUrl = iframeMatch[1].startsWith("//")
? "https:" + iframeMatch[1]
: iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
return m3u8Match ? m3u8Match[1] : null;
} else {
console.log("Vidmoly extractor: No match found, using fallback");
// regex the sources: [{file:"this_is_the_link"}]
const sourcesRegex = /sources:\s*\[\{file:"(https?:\/\/[^"]+)"\}/;
const sourcesMatch = html.match(sourcesRegex);
let sourcesString = sourcesMatch
? sourcesMatch[1].replace(/'/g, '"')
: null;
return sourcesString;
}
}
/* --- vidoza --- */
/**
* @name vidozaExtractor
* @author Cufiy
*/
async function vidozaExtractor(html, url = null) {
const regex = /<source src="([^"]+)" type='video\/mp4'>/;
const match = html.match(regex);
if (match) {
return match[1];
} else {
console.log("No match found for vidoza extractor");
return null;
}
}
/* --- voe --- */
/**
* @name voeExtractor
* @author Cufiy
*/
function voeExtractor(html, url = null) {
// Extract the first <script type="application/json">...</script>
const jsonScriptMatch = html.match(
/<script[^>]+type=["']application\/json["'][^>]*>([\s\S]*?)<\/script>/i
);
if (!jsonScriptMatch) {
console.log("No application/json script tag found");
return null;
}
const obfuscatedJson = jsonScriptMatch[1].trim();
let data;
try {
data = JSON.parse(obfuscatedJson);
} catch (e) {
throw new Error("Invalid JSON input.");
}
if (!Array.isArray(data) || typeof data[0] !== "string") {
throw new Error("Input doesn't match expected format.");
}
let obfuscatedString = data[0];
// Step 1: ROT13
let step1 = voeRot13(obfuscatedString);
// Step 2: Remove patterns
let step2 = voeRemovePatterns(step1);
// Step 3: Base64 decode
let step3 = voeBase64Decode(step2);
// Step 4: Subtract 3 from each char code
let step4 = voeShiftChars(step3, 3);
// Step 5: Reverse string
let step5 = step4.split("").reverse().join("");
// Step 6: Base64 decode again
let step6 = voeBase64Decode(step5);
// Step 7: Parse as JSON
let result;
try {
result = JSON.parse(step6);
} catch (e) {
throw new Error("Final JSON parse error: " + e.message);
}
// console.log("Decoded JSON:", result);
// check if direct_access_url is set, not null and starts with http
if (result && typeof result === "object") {
const streamUrl =
result.direct_access_url ||
result.source
.map((source) => source.direct_access_url)
.find((url) => url && url.startsWith("http"));
if (streamUrl) {
console.log("Voe Stream URL: " + streamUrl);
return streamUrl;
} else {
console.log("No stream URL found in the decoded JSON");
}
}
return result;
}
function voeRot13(str) {
return str.replace(/[a-zA-Z]/g, function (c) {
return String.fromCharCode(
(c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13)
? c
: c - 26
);
});
}
function voeRemovePatterns(str) {
const patterns = ["@$", "^^", "~@", "%?", "*~", "!!", "#&"];
let result = str;
for (const pat of patterns) {
result = result.split(pat).join("");
}
return result;
}
function voeBase64Decode(str) {
// atob is available in browsers and Node >= 16
if (typeof atob === "function") {
return atob(str);
}
// Node.js fallback
return Buffer.from(str, "base64").toString("utf-8");
}
function voeShiftChars(str, shift) {
return str
.split("")
.map((c) => String.fromCharCode(c.charCodeAt(0) - shift))
.join("");
}
////////////////////////////////////////////////
// PLUGINS //
////////////////////////////////////////////////
/**
* Uses Sora's fetchv2 on ipad, fallbacks to regular fetch on Windows
* @author ShadeOfChaos
*
* @param {string} url The URL to make the request to.
* @param {object} [options] The options to use for the request.
* @param {object} [options.headers] The headers to send with the request.
* @param {string} [options.method='GET'] The method to use for the request.
* @param {string} [options.body=null] The body of the request.
*
* @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 }) {
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;
}
}
}
/***********************************************************
* UNPACKER MODULE
* Credit to GitHub user "mnsrulz" for Unpacker Node library
* https://github.com/mnsrulz/unpacker
***********************************************************/
class Unbaser {
constructor(base) {
this.ALPHABET = {
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
};
this.dictionary = {};
this.base = base;
if (36 < base && base < 62) {
this.ALPHABET[base] = this.ALPHABET[base] ||
this.ALPHABET[62].substr(0, base);
}
if (2 <= base && base <= 36) {
this.unbase = (value) => parseInt(value, base);
}
else {
try {
[...this.ALPHABET[base]].forEach((cipher, index) => {
this.dictionary[cipher] = index;
});
}
catch (er) {
throw Error("Unsupported base encoding.");
}
this.unbase = this._dictunbaser;
}
}
_dictunbaser(value) {
let ret = 0;
[...value].reverse().forEach((cipher, index) => {
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
});
return ret;
}
}
function detectUnbaser(source) {
/* Detects whether `source` is P.A.C.K.E.R. coded. */
return source.replace(" ", "").startsWith("eval(function(p,a,c,k,e,");
}
function unpack(source) {
let { payload, symtab, radix, count } = _filterargs(source);
if (count != symtab.length) {
throw Error("Malformed p.a.c.k.e.r. symtab.");
}
let unbase;
try {
unbase = new Unbaser(radix);
}
catch (e) {
throw Error("Unknown p.a.c.k.e.r. encoding.");
}
function lookup(match) {
const word = match;
let word2;
if (radix == 1) {
word2 = symtab[parseInt(word)];
}
else {
word2 = symtab[unbase.unbase(word)];
}
return word2 || word;
}
source = payload.replace(/\b\w+\b/g, lookup);
return _replacestrings(source);
function _filterargs(source) {
const juicers = [
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
];
for (const juicer of juicers) {
const args = juicer.exec(source);
if (args) {
let a = args;
if (a[2] == "[]") {
}
try {
return {
payload: a[1],
symtab: a[4].split("|"),
radix: parseInt(a[2]),
count: parseInt(a[3]),
};
}
catch (ValueError) {
throw Error("Corrupted p.a.c.k.e.r. data.");
}
}
}
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
}
function _replacestrings(source) {
return source;
}
}
/* {GE END} */