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.2.0} */ /** * @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 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 */ 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") ) { 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": [ { "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" } ] } */ 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 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 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 // 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 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 streamUrl is valid 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; } let title; if (providersCount[provider]) { providersCount[provider]++; title = provider.charAt(0).toUpperCase() + provider.slice(1) + "-" + (providersCount[provider] - 1); // add a number to the provider name } else { providersCount[provider] = 1; 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 } } 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 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": 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", }; 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, }); 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(/