From 2e97d975782b9f2f8216b765f09c285a85ef849f Mon Sep 17 00:00:00 2001 From: aka paul <50n50@noreply.localhost> Date: Mon, 3 Nov 2025 16:57:31 +0000 Subject: [PATCH] Add animeindo/animeindo.js --- animeindo/animeindo.js | 368 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 animeindo/animeindo.js diff --git a/animeindo/animeindo.js b/animeindo/animeindo.js new file mode 100644 index 0000000..be30acf --- /dev/null +++ b/animeindo/animeindo.js @@ -0,0 +1,368 @@ +async function searchResults(keyword) { + const results = []; + try { + const response = await fetchv2("https://animeindo.skin/search/" + encodeURIComponent(keyword)); + const html = await response.text(); + + const itemContainerRegex = /
([\s\S]*?)<\/div>\s*/g; + let containerMatch; + while ((containerMatch = itemContainerRegex.exec(html)) !== null) { + const itemHtml = containerMatch[1]; + + const hrefMatch = itemHtml.match(/]+src="([^"]+)"/); + const image = imgMatch ? imgMatch[1].trim() : null; + + const titleMatch = itemHtml.match(/]*>([^<]+)<\/h3>/); + const title = titleMatch ? titleMatch[1].trim() : null; + + if (href && image && title) { + results.push({ + href: href, + image: image, + title: title + }); + } + } + + return JSON.stringify(results); + } catch (err) { + return JSON.stringify([{ + title: "Error", + image: "Error", + href: "Error" + }]); + } +} + +async function extractDetails(url) { + try { + const response = await fetchv2(url); + const html = await response.text(); + + const regex = /

(.*?)<\/p>/s; + const match = html.match(regex); + const description = match ? match[1].trim() : "N/A"; + + return JSON.stringify([{ + description: description, + aliases: "N/A", + airdate: "N/A" + }]); + } catch (err) { + return JSON.stringify([{ + description: "Error", + aliases: "Error", + airdate: "Error" + }]); + } +} + +async function extractEpisodes(url) { + const results = []; + try { + const response = await fetchv2(url); + const html = await response.text(); + + const itemContainerRegex = /

[\s\S]*?Season\s+(\d+)<\/span>[\s\S]*?Episode\s+(\d+)<\/span>[\s\S]*?<\/div>/g; + let containerMatch; + while ((containerMatch = itemContainerRegex.exec(html)) !== null) { + const href = containerMatch[1].trim(); + const season = parseInt(containerMatch[2], 10); + const episode = parseInt(containerMatch[3], 10); + + results.push({ + href: href, + season: season, + number: episode + }); + } + + if (results.length === 0) { + results.push({ + href: url, + season: 1, + number: 1 + }); + } + + return JSON.stringify(results); + } catch (err) { + return JSON.stringify([{ + href: "Error", + season: "Error", + number: "Error" + }]); + } +} + +async function extractStreamUrl(url) { + try { + const response = await fetchv2(url); + const html = await response.text(); + + const snapshotMatch = html.match(/wire:snapshot="(.+?)" wire:effects/); + if (!snapshotMatch) { + return "https://error.org/"; + } + + const decodedSnapshot = snapshotMatch[1] + .replace(/"/g, '"') + .replace(/\\\//g, '/') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&'); + + const data = JSON.parse(decodedSnapshot); + + const videosArray = data.data.videos[0]; + let fmLink = null; + + for (let i = 0; i < videosArray.length; i++) { + const item = videosArray[i]; + + if (Array.isArray(item) && item.length > 0) { + const videoObj = item[0]; + if (videoObj && videoObj.label === "FM") { + fmLink = videoObj.link; + break; + } + } + else if (item && item.label === "FM") { + fmLink = item.link; + break; + } + } + + console.log("FM Stream Link: " + fmLink); + + if (fmLink) { + try { + const embedResponse = await fetchv2(fmLink); + const embedHtml = await embedResponse.text(); + + const iframeSrcMatch = embedHtml.match(/]*src="([^"]+)"/); + if (iframeSrcMatch) { + const embedUrl = iframeSrcMatch[1]; + + try { + const filemoonResponse = await fetchv2(embedUrl); + const filemoonHtml = await filemoonResponse.text(); + + const finalIframeMatch = filemoonHtml.match(/]*src="([^"]+)"/); + if (finalIframeMatch) { + const finalEmbedUrl = finalIframeMatch[1]; + + const 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": embedUrl, + }; + + const finalResponse = await fetchv2(finalEmbedUrl, headers); + const finalHtml = await finalResponse.text(); + + const streamUrl = await filemoonExtractor(finalHtml, embedUrl); + return streamUrl || "https://error.org/"; + } else { + return "https://error.org/"; + } + } catch (filemoonErr) { + return "https://error.org/"; + } + } else { + return "https://error.org/"; + } + } catch (embedErr) { + return "https://error.org/"; + } + } else { + return "https://error.org/"; + } + } catch (err) { + return "https://error.org/"; + } +} + + +/* SCHEME START */ +/* {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 = /]+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 /]*>([\s\S]*?)<\/script>/gi + const scriptRegex = /]*>([\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; + } +} + + +/* REMOVE_START */ + + +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 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; + } +} + + +/** + * 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} 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) { + return null; + } + } +} +/* REMOVE_END */ + +/* SCHEME END */