async function searchResults(keyword) { try { const encodedKeyword = encodeURIComponent(keyword); const responseText = await fetch(`https://fireani.me/api/anime/search?q=${encodedKeyword}`); const data = await JSON.parse(responseText); const transformedResults = data.data.map(anime => ({ title: anime.title, image: `https://fireani.me/img/posters/${anime.poster}`, href: anime.slug })); sendLog(transformedResults); return JSON.stringify(transformedResults); } catch (error) { sendLog('Fetch error:', error); return JSON.stringify([{ title: 'Error', image: '', href: '' }]); } } async function extractDetails(slug) { try { const encodedID = encodeURIComponent(slug); const response = await fetch(`https://fireani.me/api/anime?slug=${encodedID}`); const data = await JSON.parse(response); const animeInfo = data.data; const transformedResults = [{ description: animeInfo.desc || 'No description available', aliases: `Alternate Titles: ${animeInfo.alternate_titles || 'Unknown'}`, airdate: `Aired: ${animeInfo.start ? animeInfo.start : 'Unknown'}` }]; 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(slug) { try { const encodedID = encodeURIComponent(slug); const response = await fetch(`https://fireani.me/api/anime?slug=${encodedID}`); const data = await JSON.parse(response); let episodeCounter = 1; const episodes = data.data.anime_seasons.reduce((acc, season) => { if (season.season.toLowerCase() === "filme") return acc; // Skip "Filme" season const seasonEpisodes = season.anime_episodes || []; seasonEpisodes.forEach(episode => { acc.push({ href: `${encodedID}&season=${season.season}&episode=${episode.episode}`, number: episodeCounter }); episodeCounter++; }); return acc; }, []); sendLog(episodes); return JSON.stringify(episodes); } catch (error) { sendLog('Fetch error:', error); } } async function extractStreamUrl(id) { try { const encodedID = `https://fireani.me/api/anime/episode?slug=${id}`; sendLog("Encoded ID: " + encodedID); const response = await fetch(encodedID); if (!_0xCheck()) return 'https://files.catbox.moe/avolvc.mp4'; const data = await JSON.parse(response); sendLog("Data received: " + JSON.stringify(data)); let providers = {}; const language = "ger-sub"; // Default language, can be changed based on user preference const fallbackLanguage = "ger-dub"; // Fallback language if the preferred one is not available const episodeLinks = data.data.anime_episode_links || []; episodeLinks.forEach(link => { // Check if the link is valid and starts with http or https if (link.link && (link.link.startsWith("http://") || link.link.startsWith("https://"))) { // Check if the language matches the preferred one if (link.lang === language) { providers[link.link] = link.name.toLowerCase(); sendLog(`Added provider: ${link.name.toLowerCase()} for link: ${link.link} in language: ${link.lang}`); } else if (link.lang === fallbackLanguage) { // If the preferred language is not available, use the fallback providers[link.link] = link.name.toLowerCase(); sendLog(`Added fallback provider: ${link.name.toLowerCase()} for link: ${link.link} in language: ${link.lang}`); } } else { sendLog(`Invalid link found: ${link.link}`); } }); sendLog("Providers: " + JSON.stringify(providers)); // E.g. // providers = { // "https://vidmoly.to/embed-preghvoypr2m.html": "vidmoly", // "https://speedfiles.net/40d98cdccf9c": "speedfiles", // "https://speedfiles.net/82346fs": "speedfiles", // }; let streams = []; try { streams = await multiExtractor(providers); let returnedStreams = { streams: streams, } sendLog("Multi extractor streams: " + JSON.stringify(returnedStreams)); return JSON.stringify(returnedStreams); } catch (error) { sendLog("Multi extractor error:" + error); return JSON.stringify([{ provider: "Error2", link: "" }]); } if (!streams) { throw new Error("Stream URL not found"); } return streams; } catch (error) { sendLog("Fetch error:", error); return null; } } // if is node if (typeof module !== 'undefined' && module.exports) { let streamlinks = extractStreamUrl("sakamoto-days&season=1&episode=1"); sendLog("Streamlinks: " + streamlinks); } //Credits to @hamzenis for decoder <3 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; } 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))(_)} // Local 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(/