This commit is contained in:
50/50 2025-10-11 17:18:45 +02:00
commit 09c423e95a
276 changed files with 54396 additions and 0 deletions

229
111477/111477.js Normal file
View file

@ -0,0 +1,229 @@
async function searchResults(keyword) {
const results = [];
const image = 'https://files.catbox.moe/9tbjtb.png';
const regex = /<tr><td><a href="([^"]+)">([^<]+)<\/a><\/td><td[^>]*>[^<]*<\/td><\/tr>/g;
const urls = [
'https://a.111477.xyz/tvs/',
'https://a.111477.xyz/movies/',
'https://a.111477.xyz/kdrama/',
'https://a.111477.xyz/asiandrama/'
];
for (const url of urls) {
const response = await fetchv2(url);
const html = await response.text();
let match;
while ((match = regex.exec(html)) !== null) {
const rawTitle = match[2].trim().replace(/\/$/, '');
const cleanedTitle = rawTitle.replace(/\.+/g, ' ').toLowerCase();
if (cleanedTitle.includes(keyword.toLowerCase())) {
results.push({
title: rawTitle.replace(/\.+/g, ' '),
image,
href: url + match[1].trim()
});
}
}
}
return JSON.stringify(results);
}
async function extractDetails(url) {
const results = [];
results.push({
description: 'None provided, but hell who cares anyway',
aliases: 'N/A',
airdate: 'N/A'
});
return JSON.stringify(results);
}
async function extractEpisodes(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const episodeRegex = /<tr><td><a href="(https:\/\/[^"]+\.mkv)">([^<]+\.mkv)<\/a><\/td>/g;
let match;
let count = 1;
while ((match = episodeRegex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
number: count++
});
}
if (results.length > 0) {
return JSON.stringify([{
href: url,
number: 1
}]);
}
if (results.length === 0) {
const seasonRegex = /<tr><td><a href="([^"]+\/)"[^>]*>([^<]+\/)<\/a><\/td>/g;
const seasons = [];
while ((match = seasonRegex.exec(html)) !== null) {
const seasonHref = match[1].trim();
const seasonName = match[2].trim();
if (seasonHref === '../' || seasonName === '../') continue;
const isSeasonDir = (
/season\s*\d+/i.test(seasonName) ||
/s\d+/i.test(seasonName) ||
/series\s*\d+/i.test(seasonName) ||
/specials/i.test(seasonName) ||
/extras/i.test(seasonName) ||
/bonus/i.test(seasonName) ||
(/\d+/.test(seasonName) && seasonName.endsWith('/'))
);
if (isSeasonDir) {
seasons.push({
href: seasonHref,
name: seasonName
});
}
}
seasons.sort((a, b) => {
const aNum = extractSeasonNumber(a.name);
const bNum = extractSeasonNumber(b.name);
if (aNum === null && bNum === null) return a.name.localeCompare(b.name);
if (aNum === null) return 1;
if (bNum === null) return -1;
return aNum - bNum;
});
console.log(`Found ${seasons.length} seasons:`, seasons.map(s => s.name));
console.log(`Base URL: "${url}"`);
console.log(`Base URL ends with /: ${url.endsWith('/')}`);
for (const season of seasons) {
console.log(`Processing season: ${season.name}`);
console.log(`Season href: "${season.href}"`);
let seasonUrl;
if (season.href.startsWith('http')) {
seasonUrl = season.href;
} else {
const baseUrl = url.endsWith('/') ? url : url + '/';
seasonUrl = baseUrl + season.href;
}
console.log(`Season URL: "${seasonUrl}"`);
console.log(`URL encoded version: "${encodeURI(seasonUrl)}"`);
try {
const seasonResponse = await fetchv2(decodeURIComponent(seasonUrl));
const seasonHtml = await seasonResponse.text();
console.log(`Sample HTML from ${season.name}:`, seasonHtml.substring(0, 500));
let episodeCount = 1;
const seasonEpisodeRegex = /<a href="([^"]+\.mkv)"[^>]*>([^<]+\.mkv)<\/a>/g;
let seasonMatch;
let episodesInSeason = 0;
while ((seasonMatch = seasonEpisodeRegex.exec(seasonHtml)) !== null) {
let episodeHref = seasonMatch[1].trim();
if (!episodeHref.startsWith('http')) {
episodeHref = seasonUrl.endsWith('/') ? seasonUrl + episodeHref : seasonUrl + '/' + episodeHref;
}
results.push({
href: episodeHref,
number: episodeCount++,
season: season.name.replace('/', '')
});
episodesInSeason++;
}
console.log(`Found ${episodesInSeason} episodes in ${season.name}`);
} catch (error) {
console.warn(`Failed to fetch season ${season.name}:`, error);
console.warn(`Season URL was: ${seasonUrl}`);
}
}
}
return JSON.stringify(results);
}
function extractSeasonNumber(seasonName) {
const patterns = [
/season\s*(\d+)/i,
/s(\d+)/i,
/series\s*(\d+)/i,
/(\d+)/
];
for (const pattern of patterns) {
const match = seasonName.match(pattern);
if (match) {
return parseInt(match[1], 10);
}
}
return null;
}
async function extractStreamUrl(url) {
if (url.toLowerCase().endsWith('.mkv')) {
const filename = url.split('/').pop();
const final = {
streams: [filename, url],
subtitles: ""
};
console.log("RETURN: " + JSON.stringify(final));
return JSON.stringify(final);
}
try {
const response = await fetchv2(decodeURIComponent(url));
const html = await response.text();
const mkvRegex = /<tr><td><a href="([^"]+\.mkv)"[^>]*>([^<]+\.mkv)<\/a><\/td>/g;
const streams = [];
let match;
while ((match = mkvRegex.exec(html)) !== null) {
const mkvUrl = match[1].trim();
const filename = match[2].trim();
streams.push(filename, mkvUrl);
}
const final = {
streams,
subtitles: ""
};
console.log("RETURN: " + JSON.stringify(final));
return "JSON.stringify(final)";
} catch (error) {
console.log("Error in extractStreamUrl:", error);
return JSON.stringify({
streams: [],
subtitles: ""
});
}
}

20
111477/111477.json Normal file
View file

@ -0,0 +1,20 @@
{
"sourceName": "111477",
"iconUrl": "https://media.tenor.com/tIOhF5a8McEAAAAe/heart-emoji-love-nonchalant.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Multi Language",
"streamType": "MKV",
"quality": "1080p",
"baseUrl": "https://a.111477.xyz/",
"searchBaseUrl": "https://a.111477.xyz/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/111477/111477.js",
"type": "shows/movies",
"asyncJS": true,
"softsub": true,
"downloadSupport": true,
"note": "USE AN EXTERNAL PLAYER (E.G., VLC/MPV)"
}

286
1movies/1movies.js Normal file
View file

@ -0,0 +1,286 @@
async function searchResults(query) {
const encodeQuery = keyword => encodeURIComponent(keyword);
const searchBaseUrl = "https://1movies.bz/browser?keyword=";
const baseUrl = "https://1movies.bz";
const posterHrefRegex = /href="([^"]*)" class="poster"/g;
const titleRegex = /class="title" href="[^"]*">([^<]*)</g;
const imageRegex = /data-src="([^"]*)"/g;
const extractResultsFromHTML = (htmlText) => {
const results = [];
const posterMatches = [...htmlText.matchAll(posterHrefRegex)];
const titleMatches = [...htmlText.matchAll(titleRegex)];
const imageMatches = [...htmlText.matchAll(imageRegex)];
const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length);
for (let index = 0; index < minLength; index++) {
const href = posterMatches[index][1];
const fullHref = href.startsWith("http") ? href : baseUrl + href;
const imageSrc = imageMatches[index][1];
const title = titleMatches[index][1];
const cleanTitle = decodeHtmlEntities(title);
if (fullHref && imageSrc && cleanTitle) {
results.push({
href: fullHref,
image: imageSrc,
title: cleanTitle
});
}
}
return results;
};
try {
const encodedQuery = encodeQuery(query);
const urls = [
`${searchBaseUrl}${encodedQuery}`,
`${searchBaseUrl}${encodedQuery}&page=2`,
`${searchBaseUrl}${encodedQuery}&page=3`
];
const responses = await Promise.all(urls.map(url => fetchv2(url)));
const htmlTexts = await Promise.all(responses.map(response => response.text()));
const allResults = [];
htmlTexts.forEach(htmlText => {
const pageResults = extractResultsFromHTML(htmlText);
allResults.push(...pageResults);
});
return JSON.stringify(allResults);
} catch (error) {
return JSON.stringify([{
href: "",
image: "",
title: "Search failed: " + error.message
}]);
}
}
async function extractDetails(url) {
try {
const response = await fetchv2(url);
const htmlText = await response.text();
const descriptionMatch = (/<div class="description text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
const aliasesMatch = (/<small class="al-title text-expand">([\s\S]*?)<\/small>/.exec(htmlText) || [])[1];
const airdateMatch = (/<li>Released:\s*<span[^>]*>(.*?)<\/span>/.exec(htmlText) || [])[1];
return JSON.stringify([{
description: descriptionMatch ? cleanHtmlSymbols(descriptionMatch) : "Not available",
aliases: aliasesMatch ? cleanHtmlSymbols(aliasesMatch) : "Not aliases",
airdate: airdateMatch ? cleanHtmlSymbols(airdateMatch) : "Not available"
}]);
} catch (error) {
console.error("Error fetching details:" + error);
return [{
description: "Error loading description",
aliases: "Not available",
airdate: "Not available"
}];
}
}
async function extractEpisodes(movieUrl) {
try {
const response = await fetchv2(movieUrl);
const htmlText = await response.text();
const movieIDMatch = (htmlText.match(/<div class="detail-lower"[^>]*id="movie-rating"[^>]*data-id="([^"]+)"/) || [])[1];
if (!movieIDMatch) {
return [{
error: "MovieID not found"
}];
}
const movieData = [{ name: "MovieID", data: movieIDMatch }];
const tokenResponse = await fetchv2(
"https://ilovekai.simplepostrequest.workers.dev/ilovethighs",
{},
"POST",
JSON.stringify(movieData)
);
const temp = await tokenResponse.json();
const token = temp[0]?.data;
const episodeListUrl = `https://1movies.bz/ajax/episodes/list?id=${movieIDMatch}&_=${token}`;
const episodeListResponse = await fetchv2(episodeListUrl);
const episodeListData = await episodeListResponse.json();
const cleanedHtml = cleanJsonHtml(episodeListData.result);
const episodeRegex = /<a[^>]+eid="([^"]+)"[^>]+num="([^"]+)"[^>]*>/g;
const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];
const episodeData = episodeMatches.map(([_, episodeToken, episodeNum]) => ({
name: `Episode ${episodeNum}`,
data: episodeToken
}));
console.log(JSON.stringify(episodeData));
const batchResponse = await fetchv2(
"https://ilovekai.simplepostrequest.workers.dev/ilovethighs",
{},
"POST",
JSON.stringify(episodeData)
);
const batchResults = await batchResponse.json();
const episodes = batchResults.map((result, index) => ({
number: parseInt(episodeMatches[index][2], 10),
href: `https://1movies.bz/ajax/links/list?eid=${episodeMatches[index][1]}&_=${result.data}`
}));
return JSON.stringify(episodes);
} catch (err) {
console.error("Error fetching episodes:" + err);
return [{
number: 1,
href: "Error fetching episodes"
}];
}
}
async function extractStreamUrl(url) {
try {
const fetchUrl = `${url}`;
const response = await fetchv2(fetchUrl);
const responseData = await response.json();
const cleanedHtml = cleanJsonHtml(responseData.result);
const server1Regex = /<div class="server wnav-item"[^>]*data-lid="([^"]+)"[^>]*>\s*<span>Server 1<\/span>/;
const server1Match = server1Regex.exec(cleanedHtml);
if (!server1Match) {
console.log("Server 1 not found");
return "error";
}
const serverId = server1Match[1];
const tokenRequestData = [{ name: "Server1", data: serverId }];
const tokenBatchResponse = await fetchv2(
"https://ilovekai.simplepostrequest.workers.dev/ilovethighs",
{},
"POST",
JSON.stringify(tokenRequestData)
);
const tokenResults = await tokenBatchResponse.json();
const token = tokenResults[0]?.data;
if (!token) {
console.log("Token not found");
return "error";
}
const streamUrl = `https://1movies.bz/ajax/links/view?id=${serverId}&_=${token}`;
const streamResponse = await fetchv2(streamUrl);
const streamData = await streamResponse.json();
if (!streamData.result) {
console.log("Stream result not found");
return "error";
}
const decryptRequestData = [{ name: "Server1", data: streamData.result }];
const decryptBatchResponse = await fetchv2(
"https://ilovekai.simplepostrequest.workers.dev/iloveboobs",
{},
"POST",
JSON.stringify(decryptRequestData)
);
const decryptedResponse = await decryptBatchResponse.json();
const decryptedUrl = decryptedResponse[0]?.data.url;
const subListEncoded = decryptedUrl.split("sub.list=")[1]?.split("&")[0];
const subListUrl = decodeURIComponent(subListEncoded);
const subResponse = await fetchv2(subListUrl);
const subtitles = await subResponse.json();
const englishSubUrl = subtitles.find(sub => sub.label === "English")?.file.replace(/\\\//g, "/");
if (!decryptedUrl) {
console.log("Decryption failed");
return "error";
}
const headers = {
"Referer": "https://1movies.bz/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
};
const mediaResponse = await fetchv2(decryptedUrl.replace("/e/", "/media/"), headers);
const mediaJson = await mediaResponse.json();
const result = mediaJson?.result;
if (!result) {
console.log("Media result not found");
return "error";
}
const postData = {
"text": result,
"Useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
};
const finalResponse = await fetchv2("https://ilovekai.simplepostrequest.workers.dev/ilovebush", {}, "POST", JSON.stringify(postData));
const finalJson = await finalResponse.json();
const m3u8Link = finalJson?.result?.sources?.[0]?.file;
const returnValue = {
stream: m3u8Link,
subtitles: englishSubUrl
};
console.log(JSON.stringify(returnValue));
return JSON.stringify(returnValue);
} catch (error) {
console.log("Fetch error:"+ error);
return "https://error.org";
}
}
function cleanHtmlSymbols(string) {
if (!string) {
return "";
}
return string
.replace(/&#8217;/g, "'")
.replace(/&#8211;/g, "-")
.replace(/&#[0-9]+;/g, "")
.replace(/\r?\n|\r/g, " ")
.replace(/\s+/g, " ")
.trim();
}
function cleanJsonHtml(jsonHtml) {
if (!jsonHtml) {
return "";
}
return jsonHtml
.replace(/\\"/g, "\"")
.replace(/\\'/g, "'")
.replace(/\\\\/g, "\\")
.replace(/\\n/g, "\n")
.replace(/\\t/g, "\t")
.replace(/\\r/g, "\r");
}
function decodeHtmlEntities(text) {
if (!text) {
return "";
}
return text
.replace(/&#039;/g, "'")
.replace(/&quot;/g, "\"")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&nbsp;/g, " ");
}

19
1movies/1movies.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "1Movies",
"iconUrl": "https://1movies.bz/assets/uploads/675b5c22f2829fc8e3a4030af7f4284acad017e5241280b3dc21.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.5",
"language": "English",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animekai.to/",
"searchBaseUrl": "https://1movies.bz/home",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/1movies/1movies.js",
"type": "shows/movies",
"asyncJS": true,
"softsub": true,
"downloadSupport": true
}

225
1tamilcrow/1tamilcrow.js Normal file
View file

@ -0,0 +1,225 @@
async function searchResults(keyword) {
const results = [];
const regex = /<div class="vw-post-box vw-post-box--block-a[\s\S]*?(?:<img[^>]*src="([^"]+)"[\s\S]*?)?<a href="([^"]+)" class="vw-post-box__overlay[\s\S]*?<h3 class="vw-post-box__title"[\s\S]*?<a class="vw-post-box__link"[^>]*>\s*([^<]+)\s*<\/a>/g;
try {
const response = await fetchv2("https://www.1tamilcrow.net/?s=" + keyword);
const html = await response.text();
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[2].trim(),
title: decodeHtml(match[3].trim()),
image: match[1] ? match[1].trim() : "No Image"
});
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{
title: "Error",
image: "Error",
href: "Error"
}]);
}
}
function decodeHtml(str) {
return str
.replace(/&amp;/g, "&")
.replace(/&#8217;/g, "")
.replace(/&quot;/g, "\"")
.replace(/&#39;/g, "'")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">");
}
async function extractDetails(url) {
try {
return JSON.stringify([{
description: "No description on the website",
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();
console.log(html);
const regex = /<IFRAME[^>]*SRC="(https:\/\/(?:videospk|vidhidevip)[^"]+)"[^>]*>/;
const match = html.match(regex);
if (match) {
results.push({
href: match[1].trim(),
number: 1
});
} else {
results.push({
href: url,
number: 1
});
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
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:" + hlsLink);
return baseUrl + hlsLink;
} catch (err) {
console.log(err);
return "https://files.catbox.moe/avolvc.mp4";
}
}
/***********************************************************
* UNPACKER MODULE
* Credit to GitHub user "mnsrulz" for Unpacker Node library
* https://github.com/mnsrulz/unpacker
***********************************************************/
class Unbaser {
constructor(base) {
/* Functor for a given base. Will efficiently convert
strings to natural numbers. */
this.ALPHABET = {
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
};
this.dictionary = {};
this.base = base;
// fill elements 37...61, if necessary
if (36 < base && base < 62) {
this.ALPHABET[base] = this.ALPHABET[base] ||
this.ALPHABET[62].substr(0, base);
}
// If base can be handled by int() builtin, let it do it for us
if (2 <= base && base <= 36) {
this.unbase = (value) => parseInt(value, base);
}
else {
// Build conversion dictionary cache
try {
[...this.ALPHABET[base]].forEach((cipher, index) => {
this.dictionary[cipher] = index;
});
}
catch (er) {
throw Error("Unsupported base encoding.");
}
this.unbase = this._dictunbaser;
}
}
_dictunbaser(value) {
/* Decodes a value to an integer. */
let ret = 0;
[...value].reverse().forEach((cipher, index) => {
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
});
return ret;
}
}
function detect(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) {
/* Unpacks P.A.C.K.E.R. packed js code. */
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) {
/* Look up symbols in the synthetic symtab. */
const word = match;
let word2;
if (radix == 1) {
//throw Error("symtab unknown");
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) {
/* Juice from a source file the four args needed by decoder. */
const juicers = [
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
];
for (const juicer of juicers) {
//const args = re.search(juicer, source, re.DOTALL);
const args = juicer.exec(source);
if (args) {
let a = args;
if (a[2] == "[]") {
//don't know what it is
// a = list(a);
// a[1] = 62;
// a = tuple(a);
}
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) {
/* Strip string lookup table (list) and replace values in source. */
/* Need to work on this. */
return source;
}
}

View file

@ -0,0 +1,19 @@
{
"sourceName": "1TamilCrow",
"iconUrl": "https://sp-ao.shortpixel.ai/client/to_webp,q_glossy,ret_img/https://www.1tamilcrow.net/wp-content/uploads/2020/09/Tamil-Crow-e1600735514979.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Tamil",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://www.1tamilcrow.net/",
"searchBaseUrl": "https://www.1tamilcrow.net/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/1tamilcrow/1tamilcrow.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

32
LICENSE Normal file
View file

@ -0,0 +1,32 @@
License
Copyright (c) 2025, 50/50
All rights reserved.
This software module is primarily licensed for use within the Sora/Sulfur iOS application(https://github.com/cranci1/Sora), developed by cranc1.
It may also be freely used in other projects, provided they meet the criteria outlined below.
LICENSE TERMS
1. Permitted Use
- This software may be used as part of the official Sora or Sulfur iOS applications.
- It may also be used in open-source applications that are:
- Free to download and use this software (no paywall, subscription, or purchase required)
- Without advertisements during the usage of this software
- Released under an open-source license
- All three of the above requirements are mandatory unless explicit written permission has been granted by the copyright holder to an application that does not meet one or more of them.
2. Prohibited Use
- Redistribution, reproduction, or sublicensing of this software as a standalone module is not allowed
- Use in proprietary, closed-source, paid, or ad-supported software is strictly prohibited
- Commercial use of this software in any form is forbidden
3. Access & Agreement
- By accessing, downloading, or using any portion of this repository, you acknowledge and agree to abide by these terms
- Unauthorized use or distribution may result in legal action
---
This license does not grant any rights under patent, trademark, or other intellectual property laws beyond those explicitly stated above.
Note: License terms may change over time. It is your responsibility to review the latest version periodically.

36
README.md Normal file
View file

@ -0,0 +1,36 @@
<hr>
<div style="border-left: 6px solid #f39c12; background: #fff8e5; padding: 12px 16px; border-radius: 6px; font-family: sans-serif;">
<p style="margin:0; font-size: 16px;">⚠️ <b style="color:#b57400;">IMPORTANT</b></p>
<p style="margin:8px 0 0 0;">Any app meeting the following criteria is free to use my modules:</p>
<ol style="margin:8px 0 0 20px;">
<li><b>No paywall, subscription, or payment</b> required for my modules
(you may charge for other parts of your app).</li>
<li><b>No advertisements</b> during the usage of my modules.</li>
<li><b>Open source</b>: your apps source code must be publicly available.</li>
</ol>
<p style="margin:8px 0 0 0;">All the above terms are mandatory unless given permission by me.<br>
In short: <b>no commercial use</b> and <b>transparency is required</b>.</p>
</div>
<br>
<div style="border-left: 6px solid #e74c3c; background: #fdeaea; padding: 12px 16px; border-radius: 6px; font-family: sans-serif;">
<p style="margin:0; font-size: 16px;">🚫 <b style="color:#a40000;">CAUTION</b></p>
<p style="margin:8px 0 0 0;">Do not pay to use these modules — if someone is charging you, it's a scam.</p>
<p style="margin:8px 0 0 0;">Neither should you bear watching ads to use these modules. Please report apps that do this forcibly!</p>
</div>
<hr>
<div align="center">
[![Discord Presence](https://lanyard.cnrad.dev/api/1072985316916469870?theme=dark&bg=000000&animated=false&hideDiscrim=true&borderRadius=30px&idleMessage=For%20requests%20go%20to%20Sora%20server,%20for%20questions%20about%20using%20my%20modules%20in%20your%20app%20DM%20me.%20If%20a%20module%20is%20broken%20go%20to%20Sora%20server%20or%20open%20a%20GitHub%20issue.)](https://discord.com/users/1072985316916469870)
<div>
[![Ko-fi](https://img.shields.io/badge/support_me_on_ko--fi-F16061?style=for-the-badge&logo=kofi&logoColor=f5f5f5)](https://ko-fi.com/50n50)
</div>
</div>

181
aksv/aksv.js Normal file
View file

@ -0,0 +1,181 @@
async function searchResults(keyword) {
const results = [];
const response = await fetchv2("https://ak.sv/search?q=" + encodeURIComponent(keyword));
const html = await response.text();
const filmListRegex = /<div class="col-lg-auto col-md-4 col-6 mb-12">[\s\S]*?<\/div>\s*<\/div>/g;
const items = html.match(filmListRegex) || [];
items.forEach((itemHtml) => {
const titleMatch = itemHtml.match(/<h3 class="entry-title[^>]*>\s*<a href="([^"]+)"[^>]*>([^<]+)<\/a>/);
const href = titleMatch ? titleMatch[1] : '';
const title = titleMatch ? titleMatch[2] : '';
const imgMatch = itemHtml.match(/<img[^>]*data-src="([^"]+)"[^>]*>/);
const imageUrl = imgMatch ? imgMatch[1] : '';
if (title && href) {
results.push({
title: title.trim(),
image: imageUrl.trim(),
href: href.trim(),
});
}
});
console.log(results);
return JSON.stringify(results);
}
async function extractDetails(url) {
const details = [];
let description = 'N/A';
let aliases = 'N/A';
let airdate = 'N/A';
const genres = [];
const response = await fetch(url);
const html = await response.text();
const airdateMatch = html.match(
/<div class="font-size-16 text-white mt-2">\s*<span>\s*السنة\s*:\s*(\d{4})\s*<\/span>\s*<\/div>/
);
if (airdateMatch) airdate = airdateMatch[1];
const descriptionMatch = html.match(
/<div class="text-white font-size-18"[^>]*>[\s\S]*?<p>([\s\S]*?)<\/p>/
);
if (descriptionMatch) {
description = decodeHTMLEntities(descriptionMatch[1].replace(/<[^>]+>/g, '').trim());
}
const genresMatch = html.match(/<div class="font-size-16 d-flex align-items-center mt-3">([\s\S]*?)<\/div>/);
const genresHtml = genresMatch ? genresMatch[1] : '';
const genreAnchorRe = /<a[^>]*>([^<]+)<\/a>/g;
let genreMatch;
while ((genreMatch = genreAnchorRe.exec(genresHtml)) !== null) {
genres.push(decodeHTMLEntities(genreMatch[1].trim()));
}
details.push({
description: description,
airdate: airdate,
aliases: genres.join(', ') || 'N/A'
});
console.log(details);
return JSON.stringify(details);
}
async function extractEpisodes(url) {
const episodes = [];
const response = await fetchv2(url);
const html = await response.text();
const movieRegex = /<a[^>]+href=["']([^"']+)["'][^>]+class=["'][^"']*link-btn link-show[^"']*["'][^>]*>/i;
const movieMatch = movieRegex.exec(html);
if (movieMatch && movieMatch[1]) {
episodes.push({
href: movieMatch[1],
number: 1
});
} else {
const reversedHtml = html.split('\n').reverse().join('\n');
const episodeBlocks = reversedHtml.match(/<div class="col-md-auto text-center pb-3 pb-md-0">[\s\S]*?<a href=["']([^"']+)["'][^>]*>[\s\S]*?<\/a>/g);
if (episodeBlocks) {
episodeBlocks.forEach((block, index) => {
const hrefMatch = block.match(/href=["']([^"']+)["']/);
if (hrefMatch) {
episodes.push({
href: hrefMatch[1],
number: index + 1
});
}
});
}
}
console.log(JSON.stringify(episodes));
return JSON.stringify(episodes);
}
async function extractStreamUrl(url) {
let stream = null;
const response = await fetchv2(url);
const html = await response.text();
const urlMatch = html.match(/<meta property="og:url" content="([^"]+)"/);
const isEpisode = urlMatch && urlMatch[1] && urlMatch[1].includes("/episode/");
if (isEpisode) {
const linkBtnMatches = html.match(/<a[^>]*class="link-btn link-show[^"]*"[^>]*>[\s\S]*?<\/a>/g);
let match = null;
if (linkBtnMatches && linkBtnMatches.length > 0) {
const hrefMatch = linkBtnMatches[0].match(/href="([^"]+)"/);
if (hrefMatch && hrefMatch[1]) {
match = [null, hrefMatch[1]];
}
}
if (match && match[1]) {
try {
const shortnerResponse = await fetch(match[1]);
const shortnerHtml = await shortnerResponse;
const finalMatch = shortnerHtml.match(/<div class="d-none d-md-block">\s*<a href="([^"]+)"/);
if (finalMatch && finalMatch[1]) {
let finalUrl = finalMatch[1].replace("two.akw.cam", "ak.sv");
const lastResponse = await fetch(finalUrl);
const lastHtml = await lastResponse;
const videoMatch = lastHtml.match(/<source\s+src="([^"]+)"\s+type="video\/mp4"/);
if (videoMatch && videoMatch[1]) {
stream = videoMatch[1];
}
}
} catch (error) {
console.error("Error fetching shortener URL:", error);
return null;
}
}
} else {
const finalMatch = html.match(/<div class="d-none d-md-block">\s*<a href="([^"]+)"/);
if (finalMatch && finalMatch[1]) {
try {
let finalUrl = finalMatch[1].replace("two.akw.cam", "ak.sv");
const lastResponse = await fetch(finalUrl);
const lastHtml = await lastResponse;
const videoMatch = lastHtml.match(/<source\s+src="([^"]+)"\s+type="video\/mp4"/);
if (videoMatch && videoMatch[1]) {
stream = videoMatch[1];
}
} catch (error) {
console.error("Error fetching final URL:", error);
return null;
}
}
}
console.log(stream);
return stream;
}
function decodeHTMLEntities(text) {
text = text.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec));
const entities = {
'&quot;': '"',
'&amp;': '&',
'&apos;': "'",
'&lt;': '<',
'&gt;': '>'
};
for (const entity in entities) {
text = text.replace(new RegExp(entity, 'g'), entities[entity]);
}
return text;
}

17
aksv/aksv.json Normal file
View file

@ -0,0 +1,17 @@
{
"sourceName": "AK.SV",
"iconUrl": "https://i.ibb.co/fdyjNhkb/icon-number-255763.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.5",
"language": "Arabic (SUB)",
"streamType": "MP4",
"quality": "1080p",
"baseUrl": "https://ak.sv/",
"searchBaseUrl": "https://ak.sv/search?q=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/aksv/aksv.js",
"asyncJS": true,
"type": "movies/shows"
}

70
anilibria/anilibria.js Normal file
View file

@ -0,0 +1,70 @@
async function searchResults(keyword) {
try {
const encodedKeyword = encodeURIComponent(keyword);
const responseText = await fetch(`https://api.anilibria.tv/v3/title/search?search=${encodedKeyword}&filter=id,names,posters`);
const data = JSON.parse(responseText);
const transformedResults = data.list.map(anime => ({
title: anime.names.en || anime.names.ru || 'Unknown Title',
image: `https://anilibria.tv${anime.posters.original.url}`,
href: `${anime.id}`
}));
return JSON.stringify(transformedResults);
} catch (error) {
console.log('Fetch error:', error);
return JSON.stringify([{ title: 'Error', image: '', href: '' }]);
}
}
async function extractDetails(id) {
try {
const response = await fetch(`https://api.anilibria.tv/v3/title?id=${id}&filter=description`);
const data = JSON.parse(response);
const animeInfo = data;
const transformedResults = [{
description: animeInfo.description || 'No description available',
aliases: `Alias: Unknown`,
airdate: `Aired: Unknown`
}];
return JSON.stringify(transformedResults);
} catch (error) {
console.log('Details error:', error);
return JSON.stringify([{
description: 'Error loading description',
aliases: 'Duration: Unknown',
airdate: 'Aired: Unknown'
}]);
}
}
async function extractEpisodes(id) {
try {
const response = await fetch(`https://api.anilibria.tv/v3/title?id=${id}`);
const data = JSON.parse(response);
const transformedResults = Object.values(data.player.list).map(episode => ({
href: `https://cache.libria.fun${episode.hls.hd}`,
number: episode.episode
}));
return JSON.stringify(transformedResults);
} catch (error) {
console.log('Fetch error:', error);
}
}
async function extractStreamUrl(url) {
try {
return url;
} catch (error) {
console.log('Fetch error:', error);
return null;
}
}

17
anilibria/anilibria.json Normal file
View file

@ -0,0 +1,17 @@
{
"sourceName": "AniLibria",
"iconUrl": "https://github.com/50n50/sources/blob/main/anilibria/icon.png?raw=true",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Russian",
"streamType": "HLS",
"quality": "720p",
"baseUrl": "https://api.anilibria.tv/",
"searchBaseUrl": "https://api.anilibria.tv/v3/title/search?search=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/anilibria/anilibria.js",
"asyncJS": true,
"type": "anime"
}

BIN
anilibria/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
anilibria/iconalt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

340
anime-sama/anime-sama.js Normal file
View file

@ -0,0 +1,340 @@
async function searchResults(keyword) {
const results = [];
const headers = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With": "XMLHttpRequest",
"referer": "https://anime-sama.fr/"
};
const regex = /<a[^>]+href="([^"]+)"[\s\S]*?<img[^>]+src="([^"]+)"[\s\S]*?<h3[^>]*>(.*?)<\/h3>/gi;
try {
const response = await fetchv2(
"https://anime-sama.fr/template-php/defaut/fetch.php",
headers,
"POST",
`query=${encodeURIComponent(keyword)}`
);
const html = await response.text();
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3].trim(),
image: match[2].trim(),
href: match[1].trim()
});
}
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 class="text-sm text-gray-400 mt-2">(.*?)<\/p>/is;
const match = regex.exec(html);
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 seasonRegex = /panneauAnime\("([^"]+)",\s*"([^"]+)"\);/g;
let match;
const seasonUrls = [];
while ((match = seasonRegex.exec(html)) !== null) {
const seasonHref = match[2].trim();
const fullSeasonUrl = url + '/' + seasonHref;
seasonUrls.push(fullSeasonUrl);
}
const seasonPromises = seasonUrls.map(seasonUrl => fetchv2(seasonUrl));
const seasonResponses = await Promise.all(seasonPromises);
for (let i = 0; i < seasonResponses.length; i++) {
const seasonResponse = seasonResponses[i];
const seasonHtml = await seasonResponse.text();
const seasonUrl = seasonUrls[i];
const episodeScriptRegex = /<script[^>]+src=['"]([^'"]*episodes\.js[^'"]*?)['"][^>]*>/;
const scriptMatch = episodeScriptRegex.exec(seasonHtml);
if (scriptMatch) {
const episodesSrc = scriptMatch[1].trim();
const fullEpisodesUrl = seasonUrl + '/' + episodesSrc;
const episodesResponse = await fetchv2(fullEpisodesUrl);
const episodesJs = await episodesResponse.text();
let episodeNumber = 1;
const oneuploadRegex = /'(https:\/\/oneupload\.to\/[^']+)'/g;
let episodeMatch;
let foundEpisodes = false;
while ((episodeMatch = oneuploadRegex.exec(episodesJs)) !== null) {
const oneuploadUrl = episodeMatch[1].trim();
results.push({
href: oneuploadUrl,
number: episodeNumber
});
episodeNumber++;
foundEpisodes = true;
}
if (!foundEpisodes) {
episodeNumber = 1;
const sendvidRegex = /'(https:\/\/sendvid\.com\/[^']+)'/g;
while ((episodeMatch = sendvidRegex.exec(episodesJs)) !== null) {
const sendvidUrl = episodeMatch[1].trim();
results.push({
href: sendvidUrl,
number: episodeNumber
});
episodeNumber++;
foundEpisodes = true;
}
}
if (!foundEpisodes) {
episodeNumber = 1;
const smoothpreRegex = /'(https:\/\/smoothpre\.com\/[^']+)'/gi;
while ((episodeMatch = smoothpreRegex.exec(episodesJs)) !== null) {
const smoothpreUrl = episodeMatch[1].trim();
results.push({
href: smoothpreUrl,
number: episodeNumber
});
episodeNumber++;
foundEpisodes = true;
}
}
if (!foundEpisodes) {
episodeNumber = 1;
const mp4Regex = /'([^']+\.mp4[^']*)'/g;
while ((episodeMatch = mp4Regex.exec(episodesJs)) !== null) {
const mp4Url = episodeMatch[1].trim();
results.push({
href: mp4Url,
number: episodeNumber
});
episodeNumber++;
foundEpisodes = true;
}
}
if (!foundEpisodes && seasonUrl.includes('film')) {
episodeNumber = 1;
const allVideoRegex = /'(https:\/\/[^']+\.(mp4|mkv|avi|mov|webm)[^']*)'/gi;
while ((episodeMatch = allVideoRegex.exec(episodesJs)) !== null) {
const videoUrl = episodeMatch[1].trim();
results.push({
href: videoUrl,
number: 1
});
foundEpisodes = true;
break;
}
}
}
}
return JSON.stringify(results);
} catch (err) {
console.error(err);
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
if (/^https?:\/\/smoothpre\.com/i.test(url)) {
const response = await fetchv2(url);
const html = await response.text();
const obfuscatedScript = html.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
const hls2Match = unpackedScript.match(/"hls2"\s*:\s*"([^"]+)"/);
const hls2Url = hls2Match ? hls2Match[1] : null;
return hls2Url;
} else if (/^https?:\/\/sendvid\.com/i.test(url)) {
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/var\s+video_source\s*=\s*"([^"]+)"/);
const videoUrl = match ? match[1] : null;
return videoUrl;
} else if (/^https?:\/\/oneupload\.to/i.test(url)) {
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/sources:\s*\[\{file:"([^"]+)"\}\]/);
const fileUrl = match ? match[1] : null;
return fileUrl;
} else if (/\.mp4$/i.test(url)) {
return url;
} else {
return "https://error.org/";
}
} catch (err) {
return "https://error.org/";
}
}
/***********************************************************
* UNPACKER MODULE
* Credit to GitHub user "mnsrulz" for Unpacker Node library
* https://github.com/mnsrulz/unpacker
***********************************************************/
class Unbaser {
constructor(base) {
/* Functor for a given base. Will efficiently convert
strings to natural numbers. */
this.ALPHABET = {
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
};
this.dictionary = {};
this.base = base;
// fill elements 37...61, if necessary
if (36 < base && base < 62) {
this.ALPHABET[base] = this.ALPHABET[base] ||
this.ALPHABET[62].substr(0, base);
}
// If base can be handled by int() builtin, let it do it for us
if (2 <= base && base <= 36) {
this.unbase = (value) => parseInt(value, base);
}
else {
// Build conversion dictionary cache
try {
[...this.ALPHABET[base]].forEach((cipher, index) => {
this.dictionary[cipher] = index;
});
}
catch (er) {
throw Error("Unsupported base encoding.");
}
this.unbase = this._dictunbaser;
}
}
_dictunbaser(value) {
/* Decodes a value to an integer. */
let ret = 0;
[...value].reverse().forEach((cipher, index) => {
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
});
return ret;
}
}
function detect(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) {
/* Unpacks P.A.C.K.E.R. packed js code. */
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) {
/* Look up symbols in the synthetic symtab. */
const word = match;
let word2;
if (radix == 1) {
//throw Error("symtab unknown");
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) {
/* Juice from a source file the four args needed by decoder. */
const juicers = [
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
];
for (const juicer of juicers) {
//const args = re.search(juicer, source, re.DOTALL);
const args = juicer.exec(source);
if (args) {
let a = args;
if (a[2] == "[]") {
//don't know what it is
// a = list(a);
// a[1] = 62;
// a = tuple(a);
}
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) {
/* Strip string lookup table (list) and replace values in source. */
/* Need to work on this. */
return source;
}
}

View file

@ -0,0 +1,19 @@
{
"sourceName": "Anime-Sama",
"iconUrl": "https://cdn.statically.io/gh/Anime-Sama/IMG/img/autres/logo_icon.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.1",
"language": "French",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://anime-sama.fr/",
"searchBaseUrl": "https://anime-sama.fr/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/anime-sama/anime-sama.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

228
anime3rb/anime3rb.js Normal file
View file

@ -0,0 +1,228 @@
function searchResults(html) {
if (typeof html !== 'string') {
console.error('Invalid HTML input: expected a string.');
return [];
}
const results = [];
const titleRegex = /<h4[^>]*>(.*?)<\/h4>/;
const hrefRegex = /<a\s+href="([^"]+)"\s*[^>]*>/;
const imgRegex = /<img[^>]*src="([^"]+)"[^>]*>/;
const itemRegex = /<a\s+href="[^"]+"\s+class="btn btn-md btn-light simple-title-card[^"]*"[^>]*>[\s\S]*?<\/a>/g;
const items = html.match(itemRegex) || [];
items.forEach((itemHtml, index) => {
try {
if (typeof itemHtml !== 'string') {
console.error(`Item ${index} is not a string.`);
return;
}
const titleMatch = itemHtml.match(titleRegex);
const title = titleMatch?.[1]?.trim() ?? '';
const hrefMatch = itemHtml.match(hrefRegex);
const href = hrefMatch?.[1]?.trim() ?? '';
const imgMatch = itemHtml.match(imgRegex);
const imageUrl = imgMatch?.[1]?.trim() ?? '';
if (title && href) {
results.push({
title: decodeHTMLEntities(title),
image: imageUrl,
href: href
});
} else {
console.error(`Missing title or href in item ${index}`);
}
} catch (err) {
console.error(`Error processing item ${index}:`, err);
}
});
return results;
}
function extractDetails(html) {
const details = [];
const containerMatch = html.match(/<div class="py-4 flex flex-col gap-2">\s*((?:<p class="sm:text-\[1\.04rem\] leading-loose text-justify">[\s\S]*?<\/p>\s*)+)<\/div>/);
let description = "";
if (containerMatch) {
const pBlock = containerMatch[1];
const pRegex = /<p class="sm:text-\[1\.04rem\] leading-loose text-justify">([\s\S]*?)<\/p>/g;
const matches = [...pBlock.matchAll(pRegex)]
.map(m => m[1].trim())
.filter(text => text.length > 0);
description = decodeHTMLEntities(matches.join("\n\n"));
}
const airdateMatch = html.match(/<td[^>]*title="([^"]+)">[^<]+<\/td>/);
let airdate = airdateMatch ? airdateMatch[1].trim() : "";
const genres = [];
const aliasesMatch = html.match(
/<div\s+class="flex flex-wrap gap-2 lg:gap-4 text-sm sm:text-\[\.94rem\] -mt-2 mb-4">([\s\S]*?)<\/div>/
);
const inner = aliasesMatch ? aliasesMatch[1] : "";
const anchorRe = /<a[^>]*class="btn btn-md btn-plain !p-0"[^>]*>([^<]+)<\/a>/g;
let m;
while ((m = anchorRe.exec(inner)) !== null) {
genres.push(m[1].trim());
}
if (description && airdate) {
details.push({
description: description,
aliases: genres.join(", "),
airdate: airdate,
});
}
console.log(details);
return details;
}
function extractEpisodes(html) {
const episodes = [];
const htmlRegex = /<a\s+[^>]*href="([^"]*?\/episode\/[^"]*?)"[^>]*>[\s\S]*?الحلقة\s+(\d+)[\s\S]*?<\/a>/gi;
const plainTextRegex = /الحلقة\s+(\d+)/g;
let matches;
if ((matches = html.match(htmlRegex))) {
matches.forEach(link => {
const hrefMatch = link.match(/href="([^"]+)"/);
const numberMatch = link.match(/الحلقة\s+(\d+)/);
if (hrefMatch && numberMatch) {
const href = hrefMatch[1];
const number = numberMatch[1];
episodes.push({
href: href,
number: number
});
}
});
}
else if ((matches = html.match(plainTextRegex))) {
matches.forEach(match => {
const numberMatch = match.match(/\d+/);
if (numberMatch) {
episodes.push({
href: null,
number: numberMatch[0]
});
}
});
}
console.log(episodes);
return episodes;
}
async function extractStreamUrl(html) {
try {
const sourceMatch = html.match(/data-video-source="([^"]+)"/);
let embedUrl = sourceMatch?.[1]?.replace(/&amp;/g, '&');
if (!embedUrl) return null;
const cinemaMatch = html.match(/url\.searchParams\.append\(\s*['"]cinema['"]\s*,\s*(\d+)\s*\)/);
const lastMatch = html.match(/url\.searchParams\.append\(\s*['"]last['"]\s*,\s*(\d+)\s*\)/);
const cinemaNum = cinemaMatch ? cinemaMatch[1] : undefined;
const lastNum = lastMatch ? lastMatch[1] : undefined;
if (cinemaNum) embedUrl += `&cinema=${cinemaNum}`;
if (lastNum) embedUrl += `&last=${lastNum}`;
embedUrl += `&next-image=undefined`;
console.log('Full embed URL:', embedUrl);
const response = await fetchv2(embedUrl);
const data = await response.text();
console.log('Embed page HTML:', data);
const qualities = extractQualities(data);
const epMatch = html.match(/<title>[^<]*الحلقة\s*(\d+)[^<]*<\/title>/);
const currentEp = epMatch ? Number(epMatch[1]) : null;
let nextEpNum, nextDuration, nextSubtitle;
if (currentEp !== null) {
const episodeRegex = new RegExp(
`<a[^>]+href="[^"]+/episode/[^/]+/(\\d+)"[\\s\\S]*?` +
`<span[^>]*>([^<]+)<\\/span>[\\s\\S]*?` +
`<p[^>]*>([^<]+)<\\/p>`,
'g'
);
let m;
while ((m = episodeRegex.exec(html)) !== null) {
const num = Number(m[1]);
if (num > currentEp) {
nextEpNum = num;
nextDuration = m[2].trim();
nextSubtitle = m[3].trim();
break;
}
}
}
if (nextEpNum != null) {
embedUrl += `&next-title=${encodeURIComponent(nextDuration)}`;
embedUrl += `&next-sub-title=${encodeURIComponent(nextSubtitle)}`;
}
const result = {
streams: qualities,
}
console.log(JSON.stringify(result));
return JSON.stringify(result);
} catch (err) {
console.error(err);
return null;
}
}
function extractQualities(html) {
const match = html.match(/var\s+videos\s*=\s*(\[[\s\S]*?\]);/);
if (!match) return [];
const raw = match[1];
const regex = /\{\s*src:\s*'([^']+)'\s*[^}]*label:\s*'([^']*)'/g;
const list = [];
let m;
while ((m = regex.exec(raw)) !== null) {
list.push(m[2], m[1]);
}
return list;
}
function decodeHTMLEntities(text) {
text = text.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec));
const entities = {
'&quot;': '"',
'&amp;': '&',
'&apos;': "'",
'&lt;': '<',
'&gt;': '>'
};
for (const entity in entities) {
text = text.replace(new RegExp(entity, 'g'), entities[entity]);
}
return text;
}

17
anime3rb/anime3rb.json Normal file
View file

@ -0,0 +1,17 @@
{
"sourceName": "Anime3rb",
"iconUrl": "https://anime3rb.com/favicon/apple-touch-icon.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.2.0",
"language": "Arabic (SUB)",
"streamType": "MP4",
"quality": "1080p - 720p - 360p",
"baseUrl": "https://anime3rb.com/",
"searchBaseUrl": "https://anime3rb.com/search?q=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/anime3rb/anime3rb.js",
"streamAsyncJS": true,
"type": "anime"
}

BIN
anime3rb/iconalt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

165
anime4up/anime4up.js Normal file
View file

@ -0,0 +1,165 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2(
"https://ww.anime4up.rest/?search_param=animes&s=" + encodeURIComponent(keyword)
);
const html = await response.text();
const regex = /<div class="anime-card-container">[\s\S]*?<img[^>]+src="([^"]+)"[^>]*alt="([^"]*)"[\s\S]*?<h3><a[^>]*href="([^"]+)"[^>]*>([^<]+)<\/a><\/h3>/gi;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[4].trim(),
image: match[1].trim().replace('ww.anime4up.rest', 'www.anime4up.rest'),
href: match[3].trim().replace('ww.anime4up.rest', 'www.anime4up.rest')
});
}
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 descMatch = /<p class="anime-story">([\s\S]*?)<\/p>/i.exec(html);
const description = descMatch ? descMatch[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 episodeRegex = /<div class="col-lg-3[^"]*DivEpisodeContainer">([\s\S]*?)<\/div>\s*<\/div>/gi;
let match;
let epNumber = 1;
while ((match = episodeRegex.exec(html)) !== null) {
const hrefMatch = /<h3><a href="([^"]+)"/i.exec(match[1]);
const href = hrefMatch ? hrefMatch[1].trim() : "";
results.push({
href: href,
number: epNumber
});
epNumber++;
}
const paginationRegex = /<li\s*><a\s+class="page-numbers"\s+href="[^"]*\/page\/(\d+)\/">(\d+)<\/a><\/li>/gi;
let maxPage = 1;
let paginationMatch;
while ((paginationMatch = paginationRegex.exec(html)) !== null) {
const pageNum = parseInt(paginationMatch[2]);
if (pageNum > maxPage) {
maxPage = pageNum;
}
}
if (maxPage > 1) {
const pagePromises = [];
for (let page = 2; page <= maxPage; page++) {
const pageUrl = `${url}/page/${page}/`;
pagePromises.push(fetchPageEpisodes(pageUrl, epNumber + (page - 2) * getEpisodesPerPage(results.length)));
}
const pageResults = await Promise.all(pagePromises);
pageResults.forEach(pageEpisodes => {
results.push(...pageEpisodes);
});
results.forEach((episode, index) => {
episode.number = index + 1;
});
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function fetchPageEpisodes(pageUrl, startingEpNumber) {
try {
const response = await fetchv2(pageUrl);
const html = await response.text();
const episodeRegex = /<div class="col-lg-3[^"]*DivEpisodeContainer">([\s\S]*?)<\/div>\s*<\/div>/gi;
const pageResults = [];
let match;
let epNumber = startingEpNumber;
while ((match = episodeRegex.exec(html)) !== null) {
const hrefMatch = /<h3><a href="([^"]+)"/i.exec(match[1]);
const href = hrefMatch ? hrefMatch[1].trim() : "";
pageResults.push({
href: href,
number: epNumber
});
epNumber++;
}
return pageResults;
} catch (err) {
return [];
}
}
function getEpisodesPerPage(firstPageCount) {
return firstPageCount || 12;
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const match = /<a[^>]+data-ep-url="([^"]*uqload\.cx[^"]*)"/i.exec(html);
if (match) {
console.log(match[1].trim());
const response2 = await fetchv2(match[1].trim());
const html2 = await response2.text();
const match2 = /sources:\s*\[\s*"([^"]+)"\s*\]/i.exec(html2);
const url = match2 ? match2[1] : null;
return url;
}
return "dwd";
} catch (err) {
console.error(err);
return "https://files.catbox.moe/avolvc.mp4";
}
}

20
anime4up/anime4up.json Normal file
View file

@ -0,0 +1,20 @@
{
"sourceName": "Anime4Up",
"iconUrl": "https://ww.anime4up.rest/wp-content/uploads/2019/03/Anime4up-Icon-1.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.1",
"language": "Arabic",
"streamType": "HLS",
"encrypted":true,
"quality": "1080p",
"baseUrl": "https://uqload.cx/",
"searchBaseUrl": "https://uqload.cx/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/anime4up/anime4up.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true
}

94
animeav1/animeav1.js Normal file
View file

@ -0,0 +1,94 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://animeav1.com/catalogo?search=" + encodeURIComponent(keyword));
const html = await response.text();
console.log(html);
const regex = /<article[^>]*class="group\/item[^"]*"[^>]*>[\s\S]*?<img[^>]+src="([^"]+)"[^>]*alt="[^"]*"[^>]*>[\s\S]*?<h3[^>]*class="[^"]*text-lead[^"]*">([^<]+)<\/h3>[\s\S]*?<a[^>]+href="([^"]+)"/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[2].trim(),
image: match[1].trim(),
href: "https://animeav1.com" + match[3].trim()
});
}
return JSON.stringify(results);
} catch (err) {
console.error("Search error:", 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 match = html.match(/<div class="entry[^>]*>\s*<p>([\s\S]*?)<\/p>\s*<\/div>/);
const description = match ? match[1].trim() : "N/A";
return JSON.stringify([{
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 regex = /<a[^>]+href="([^"]+\/(\d+))"[^>]*>\s*<span class="sr-only">/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: "https://animeav1.com" + match[1].trim(),
number: parseInt(match[2], 10)
});
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/url:"(https:\/\/player\.zilla-networks\.com\/play\/[^"]+)"/i);
if (match) {
return match[1].replace("/play/", "/m3u8/");
}
return "https://files.catbox.moe/avolvc.mp4";
} catch (err) {
return "https://files.catbox.moe/avolvc.mp4";
}
}

20
animeav1/animeav1.json Normal file
View file

@ -0,0 +1,20 @@
{
"sourceName": "AnimeAV1",
"iconUrl": "https://animeav1.com/favicon.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.1",
"language": "Spanish",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animeav1.com/",
"searchBaseUrl": "https://animeav1.com/search?q=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animeav1/animeav1.js",
"type": "anime",
"asyncJS": true,
"softsub": true,
"downloadSupport": false,
"note": "Use external player."
}

111
animebalkan/animebalkan.js Normal file
View file

@ -0,0 +1,111 @@
function preprocessHtml(html) {
//Module specefic, ignore
return html.replace(/\\2605/g, '★');
}
function searchResults(html) {
html = preprocessHtml(html);
const results = [];
const baseUrl = "https://animebalkan.org/";
const filmListRegex = /<article class="bs"[\s\S]*?<\/article>/g;
const items = html.match(filmListRegex) || [];
items.forEach((itemHtml) => {
const titleMatch = itemHtml.match(/<h2 itemprop="headline">([^<]+)<\/h2>/);
const hrefMatch = itemHtml.match(/<a[^>]+href="([^"]+)"[^>]*>/);
const imgMatch = itemHtml.match(/<img[^>]+src="([^"]+)"[^>]*>/);
let title = titleMatch ? titleMatch[1].trim().replace(/&#8211;/g, '') : '';
const href = hrefMatch ? hrefMatch[1].trim() : '';
const imageUrl = imgMatch ? imgMatch[1].trim() : '';
if (title && href) {
results.push({
title,
image: imageUrl.startsWith('http') ? imageUrl : baseUrl + imageUrl,
href
});
}
});
return results;
}
function extractDetails(html) {
const details = [];
const descriptionMatch = html.match(/<span class="Y2IQFc"[^>]*>([\s\S]*?)<\/span>/);
const airdateMatch = html.match(/<time[^>]*datetime="([^"]+)"/);
if (descriptionMatch) {
let description = descriptionMatch[1].trim()
.replace(/&#8211;/g, '-')
.replace(/&#8212;/g, '—')
.replace(/&#8220;/g, '"')
.replace(/&#8221;/g, '"')
.replace(/&#8230;/g, '...')
.replace(/&#8243;/g, '"')
.replace(/&#8242;/g, "'");
let airdate = airdateMatch ? airdateMatch[1].split('T')[0] : '';
if (description && airdate) {
details.push({
description: description,
aliases: 'N/A',
airdate: airdate
});
}
}
return details;
}
function extractEpisodes(html) {
const episodes = [];
const episodeRegex = /<a href="([^"]+)">[^<]*<div class="epl-num">([^<]+)<\/div>/g;
let match;
while ((match = episodeRegex.exec(html)) !== null) {
const href = match[1];
let number = match[2];
const isMovie = href.includes('film') ||
href.includes('movie') ||
number.toLowerCase() === 'film';
if (isMovie) {
number = '1';
}
if (href.includes('epizoda-') ||
href.includes('specijalna-epizoda') ||
href.includes('-epizoda/') ||
href.includes('film') ||
href.includes('movie')) {
episodes.push({
href: href,
number: number
});
}
}
episodes.reverse();
console.log(episodes);
return episodes;
}
function extractStreamUrl(html) {
const sourceRegex = /<source\s+[^>]*src="([^"]+)"/;
const match = html.match(sourceRegex);
if (match) {
console.log(match[1]);
return match[1];
} else {
console.log("No stream URL found.");
return null;
}
}

View file

@ -0,0 +1,16 @@
{
"sourceName": "AnimeBalkan",
"iconUrl": "https://i.ibb.co/7tRnVMqY/favicon.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Croatian",
"streamType": "MP4",
"quality": "720p",
"baseUrl": "https://animebalkan.org//",
"searchBaseUrl": "https://animebalkan.org/?s=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animebalkan/animebalkan.js",
"type": "anime"
}

BIN
animebalkan/iconalt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

364
animebum/animebum.js Normal file
View file

@ -0,0 +1,364 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://www.animebum.net/search?s=" + encodeURIComponent(keyword));
const html = await response.text();
const regex = /<div class="search-results__item[^>]*>[\s\S]*?<a href="([^"]+)"[^>]*><img src="([^"]+)"[^>]*><\/a>[\s\S]*?<h2>([^<]+)<\/h2>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3].trim(),
image: match[2].trim(),
href: match[1].trim()
});
}
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 = /<div class="description scrollV">\s*<p>([\s\S]*?)<\/p>/i;
const match = regex.exec(html);
let description = match ? match[1].trim() : "N/A";
description = description.replace(/<[^>]+>/g, "");
description = description.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&aacute;/g, "á")
.replace(/&eacute;/g, "é")
.replace(/&iacute;/g, "í")
.replace(/&oacute;/g, "ó")
.replace(/&uacute;/g, "ú")
.replace(/&ntilde;/g, "ñ")
.replace(/&uuml;/g, "ü")
.replace(/&ldquo;/g, "“")
.replace(/&rdquo;/g, "”")
.replace(/&lsquo;/g, "")
.replace(/&rsquo;/g, "")
.replace(/&iexcl;/g, "¡")
.replace(/&iquest;/g, "¿");
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 containerMatch = html.match(
/<div class="list-episodies-content">([\s\S]*?)<\/div>/
);
if (!containerMatch) {
return JSON.stringify([{ href: "Not found", number: "N/A" }]);
}
const containerHtml = containerMatch[1];
const regex = /<a[^>]+href="([^"]+)"[^>]*>[\s\S]*?Episodio\s+(\d+)\s*<\/a>/gi;
let match;
while ((match = regex.exec(containerHtml)) !== null) {
results.push({
href: match[1].trim(),
number: parseInt(match[2], 10)
});
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{ href: "Error", number: "Error" }]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
// Try Filemoon first
const filemoonRegex = /(https:\/\/filemoon\.(?:to|sx)\/e\/[A-Za-z0-9]+\/[^\s"']+)/;
const filemoonMatch = filemoonRegex.exec(html);
if (filemoonMatch) {
const filemoon = filemoonMatch[1];
console.log("Filemoon URL found: " + filemoon);
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": "https://animebum.net/",
};
const filemoonResponse = await fetchv2(filemoon, headers);
const filemoonHtml = await filemoonResponse.text();
try {
const streamUrl = await filemoonExtractor(filemoonHtml, "https://animebum.net/");
if (streamUrl) return streamUrl;
} catch (err) {
console.log("Filemoon extraction error:" + err);
}
}
const optionRegex = /<ul class="play-list-tabs">([\s\S]*?)<\/ul>/i;
const optionMatch = optionRegex.exec(html);
if (!optionMatch) return null;
const optionsHtml = optionMatch[1];
const faireMatch = /<li[^>]*data-id="(\d+)"[^>]*>\s*<a[^>]*title="Faire"/i.exec(optionsHtml);
if (!faireMatch) return null;
const faireId = parseInt(faireMatch[1], 10);
const videoRegex = new RegExp(`video\\[${faireId}\\]\\s*=\\s*'([^']+)'`);
const videoMatch = videoRegex.exec(html);
if (!videoMatch) return null;
const iframeHtml = videoMatch[1];
const srcMatch = /src=["']([^"']+)["']/.exec(iframeHtml);
const streamUrl = srcMatch ? srcMatch[1] : null;
const responeSome = await fetchv2(streamUrl);
const htmlSome = await responeSome.text();
const shareIdMatch = htmlSome.match(/var\s+shareId\s*=\s*"([^"]+)"/);
if (!shareIdMatch) return null;
const shareId = shareIdMatch[1];
const someUrl = `https://www.amazon.com/drive/v1/shares/${shareId}?resourceVersion=V2&ContentType=JSON&asset=ALL`;
const shareJsonResp = await fetchv2(someUrl);
const shareJson = await shareJsonResp.json();
console.log(JSON.stringify(shareJson));
const nodeId = shareJson.nodeInfo?.id;
if (!nodeId) return null;
const childrenUrl = `https://www.amazon.com/drive/v1/nodes/${nodeId}/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=${shareId}`;
const childrenResp = await fetchv2(childrenUrl);
const childrenData = await childrenResp.json();
console.log(JSON.stringify(childrenData));
const file = childrenData.data.find(item => item.kind === "FILE");
if (!file) return null;
return file.tempLink;
} catch (err) {
console.log("Fetch error:" + 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 = /<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;
}
}
/* 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<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) {
return null;
}
}
}
/* REMOVE_END */
/* SCHEME END */

19
animebum/animebum.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimeBum",
"iconUrl": "https://files.catbox.moe/i96cs1.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Spanish (DUB/SUB)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://www.animebum.net/",
"searchBaseUrl": "https://www.animebum.net/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animebum/animebum.js",
"type": "Anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

View file

@ -0,0 +1,113 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://animedefenders.me/view/?wd=" + encodeURIComponent(keyword));
const html = await response.text();
const regex = /<a class="vod-item-img shining" href="(.*?)">[\s\S]*?data-original="(.*?)"[\s\S]*?<h3 class="vod-item-title.*?"><a href=".*?">(.*?)<\/a><\/h3>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3].trim(),
image: match[2].trim(),
href: "https://animedefenders.me" + match[1].trim()
});
}
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 descriptionRegex = /<div class="detail-desc-content layout-box">\s*<p>(.*?)<\/p>\s*<\/div>/s;
const match = html.match(descriptionRegex);
let description = "N/A";
if (match && match[1]) {
description = match[1]
.replace(/<br\s*\/?>/gi, ' ')
.replace(/<[^>]*>/g, '')
.replace(/\s+/g, ' ')
.trim();
}
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 regex = /<a class="text-overflow ep" href="([^"]+)">(\d+)<\/a>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: "https://animedefenders.me" + match[1].trim(),
number: parseInt(match[2], 10)
});
}
return JSON.stringify(results.reverse());
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const subUrlRegex = /data-url="(https:\/\/ee\.anih1\.top\/bb\/sub[^"]+)"/;
const match = html.match(subUrlRegex);
if (match && match[1]) {
const subUrl = match[1];
const headers = {
"Referer": "https://ee.anih1.top",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
};
const subResponse = await fetchv2(subUrl, headers);
const subHtml = await subResponse.text();
const artplayerRegex = /url:\s*'([^']+\.m3u8)'/;
const artMatch = subHtml.match(artplayerRegex);
if (artMatch && artMatch[1]) {
return artMatch[1];
}
}
console.log("No stream URL found");
return "https://error.org/";
} catch (err) {
return "https://error.org/";
}
}

View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimeDefenders",
"iconUrl": "https://cdn.jsdelivr.net/gh/756751uosmaqy/vjplayer@main/2fcbd487e5cacb41cbe7802d5ccfc0dc.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "English (SUB)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://ee.anih1.top",
"searchBaseUrl": "https://ee.anih1.top",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animedefenders/animedefenders.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

View file

@ -0,0 +1,139 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://animeepisodeseries.com/?s=" + encodeURIComponent(keyword));
const html = await response.text();
const regex = /<a href="([^"]+)"[^>]*>\s*<img[^>]+src="([^"]+)"[^>]*>[\s\S]*?<h2 class="entry-title"><a [^>]+>([^<]+)<\/a><\/h2>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: cleanTitle(match[3].trim()),
image: match[2].trim(),
href: match[1].trim()
});
}
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 match = html.match(/<p><strong[\s\S]*?>[\s\S]*?Summary:[\s\S]*?<\/strong><br\s*\/?>(.*?)<\/p>/i);
const description = match ? match[1].trim() : "N/A";
return JSON.stringify([{
description: cleanTitle(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 gridMatch = html.match(/<div[^>]+class="[^"]*eael-post-grid[^"]*"[^>]*>([\s\S]*?)<\/div>\s*<div[^>]+class="clearfix">/i);
const gridHtml = gridMatch[1];
const patterns = [
/<a[^>]+href="([^"]*?-episode-(\d+)-[^"]*)"/gi,
/<a[^>]+href="([^"]*?-episode-(\d+)(?:-[^"]*)?\/?)"/gi,
/<a[^>]+href="([^"]*?episode(\d+)[^"]*)"/gi,
/<a[^>]+href="([^"]*?ep(\d+)[^"]*)"/gi
];
for (const regex of patterns) {
let match;
while ((match = regex.exec(gridHtml)) !== null) {
const episodeNumber = parseInt(match[2], 10);
if (!results.find(r => r.number === episodeNumber && r.href === match[1].trim())) {
results.push({
href: match[1].trim(),
number: episodeNumber
});
}
}
}
if (results.length === 0) {
const linkRegex = /<a[^>]+href="([^"]+)"[^>]*title="[^"]*episode[^"]*(\d+)[^"]*"/gi;
let match;
while ((match = linkRegex.exec(gridHtml)) !== null) {
results.push({
href: match[1].trim(),
number: parseInt(match[2], 10)
});
}
}
const uniqueResults = results.reduce((acc, current) => {
const existing = acc.find(item => item.number === current.number);
if (!existing) {
acc.push(current);
}
return acc;
}, []);
uniqueResults.sort((a, b) => a.number - b.number);
return JSON.stringify(uniqueResults);
} catch (err) {
console.error("Error in extractEpisodes:", err);
return JSON.stringify([{
href: "Error: " + err.message,
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/<iframe[^>]+src="(https:\/\/www\.4shared\.com\/web\/embed\/file\/[^"]+)"/i);
const video = match ? match[1].trim() : null;
const response2 = await fetchv2(video);
const html2 = await response2.text();
const match2 = html2.match(/<source[^>]+src="([^"]+)"[^>]*type="video\/mp4"/i);
return match2 ? match2[1].trim() : "dwa";
} catch (err) {
return "https://files.catbox.moe/avolvc.mp4";
}
}
function cleanTitle(title) {
return title
.replace(/&#8217;/g, "'")
.replace(/&#8211;/g, "-")
.replace(/&#[0-9]+;/g, "");
}

View file

@ -0,0 +1,20 @@
{
"sourceName": "AnimeEpisodeSeries",
"iconUrl": "https://animeepisodeseries.com/wp-content/uploads/2019/10/cropped-anime-episode-1-192x192.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "English (DUB/SUB)",
"streamType": "mp4",
"quality": "1080p",
"baseUrl": "hhttps://animeepisodeseries.com/",
"searchBaseUrl": "https://animeepisodeseries.com/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animeepisodeseries/animeepisodeseries.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true,
"note": "Use external player."
}

100
animefhd/animefhd.js Normal file
View file

@ -0,0 +1,100 @@
async function searchResults(keyword) {
const results = [];
const response = await fetchv2("https://animefhd.com/?s=" + encodeURIComponent(keyword));
const html = await response.text();
const filmListRegex = /<div class="ultAnisContainerItem">[\s\S]*?<\/div>\s*<\/div>/g;
const items = html.match(filmListRegex) || [];
items.forEach((itemHtml) => {
const titleMatch = itemHtml.match(/<a href="([^"]+)"[^>]*title="([^"]+)"/);
const href = titleMatch ? titleMatch[1] : '';
const title = titleMatch ? titleMatch[2] : '';
const imgMatch = itemHtml.match(/<img[^>]*src="([^"]+)"[^>]*>/);
const imageUrl = imgMatch ? imgMatch[1] : '';
if (title && href) {
results.push({
title: title.trim(),
image: imageUrl.trim(),
href: href.trim(),
});
}
});
console.log(results);
return JSON.stringify(results);
}
async function extractDetails(url) {
const details = [];
const response = await fetchv2(url);
const html = await response.text();
const descriptionMatch = html.match(/<b>Sinopse:<\/b>\s*<p>\s*([\s\S]*?)\s*<\/p>/);
let description = descriptionMatch ? descriptionMatch[1].trim() : 'N/A';
const airdateMatch = html.match(/<b>Ano:<\/b>\s*(\d{4})/);
let airdate = airdateMatch ? airdateMatch[1].trim() : 'N/A';
const episodesMatch = html.match(/<b>Episódios:<\/b>\s*(\d+)/);
let aliases = episodesMatch ? episodesMatch[1].trim() : 'N/A';
details.push({
description: description,
alias: "Episódios: " + aliases,
airdate: airdate
});
console.log(details);
return JSON.stringify(details);
}
async function extractEpisodes(url) {
const episodes = [];
const response = await fetchv2(url);
const html = await response.text();
const episodeMatches = html.match(/<a href="([^"]+)"[^>]*class="list-epi"[^>]*>Episódio (\d+)<\/a>/g);
if (episodeMatches) {
episodeMatches.forEach(match => {
const hrefMatch = match.match(/href="([^"]+)"/);
const numberMatch = match.match(/Episódio (\d+)/);
if (hrefMatch && numberMatch) {
episodes.push({
href: hrefMatch[1],
number: parseInt(numberMatch[1])
});
}
});
}
console.log(JSON.stringify(episodes));
return JSON.stringify(episodes);
}
async function extractStreamUrl(url) {
const response = await fetchv2(url);
const html = await response.text();
const iframeMatch = html.match(/<iframe[^>]*src="([^"]+)"/);
if (iframeMatch) {
const streamUrl = iframeMatch[1];
console.log(streamUrl);
const response = await fetch(streamUrl);
const newHtml = await response;
const m3u8Match = newHtml.match(/file:\s*'([^']+\.m3u8)'/);
if (m3u8Match) {
const videoUrl = m3u8Match[1];
console.log(videoUrl);
return videoUrl;
} else {
console.log("No m3u8 URL found.");
return null;
}
} else {
console.log("No iframe found.");
return null;
}
}

17
animefhd/animefhd.json Normal file
View file

@ -0,0 +1,17 @@
{
"sourceName": "AnimeFHD",
"iconUrl": "https://animefhd.net/wp-content/uploads/2024/12/270.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.2",
"language": "Portuguese (SUB)",
"streamType": "MP4",
"quality": "1080p",
"baseUrl": "https://animefhd.net/",
"searchBaseUrl": "https://animefhd.net/?s=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animefhd/animefhd.js",
"asyncJS": true,
"type": "anime"
}

126
animeheaven/animeheaven.js Normal file
View file

@ -0,0 +1,126 @@
function cleanTitle(title) {
return title
.replace(/&#8217;/g, "'")
.replace(/&#8211;/g, "-")
.replace(/&#[0-9]+;/g, "");
}
async function searchResults(keyword) {
const url = `https://animeheaven.me/fastsearch.php?xhr=1&s=${encodeURIComponent(keyword)}`;
const response = await soraFetch(url);
const html = await response.text();
const results = [];
const itemRegex = /<a class='ac' href='([^']+)'>[\s\S]*?<img class='coverimg' src='([^']+)' alt='[^']*'>[\s\S]*?<div class='fastname'>([^<]+)<\/div>/g;
let match;
while ((match = itemRegex.exec(html)) !== null) {
const href = `https://animeheaven.me${match[1]}`;
const image = `https://animeheaven.me${match[2]}`;
const rawTitle = match[3].trim();
const title = cleanTitle(rawTitle);
results.push({ title, image, href });
}
console.log(results);
return JSON.stringify(results);
}
async function extractDetails(url) {
const response = await soraFetch(url);
const html = await response.text();
const details = [];
const descriptionMatch = html.match(/<div class='infodes c'>([^<]+)<\/div>/);
let description = descriptionMatch ? descriptionMatch[1] : '';
const aliasesMatch = html.match(/<div class='infotitle c'>([^<]+)<\/div>/);
let aliases = aliasesMatch ? aliasesMatch[1] : '';
const airdateMatch = html.match(/Year: <div class='inline c2'>([^<]+)<\/div>/);
let airdate = airdateMatch ? airdateMatch[1] : '';
if (description && airdate) {
details.push({
description: description,
aliases: aliases || 'N/A',
airdate: airdate
});
}
console.log(details);
return JSON.stringify(details);
}
async function extractEpisodes(url) {
const response = await soraFetch(url);
const html = await response.text();
const episodes = [];
const episodeRegex = /<a[^>]+id="([^"]+)"[^>]*>[\s\S]*?<div class='watch2 bc'[^>]*>(\d+)<\/div>/g;
let match;
while ((match = episodeRegex.exec(html)) !== null) {
const id = match[1];
const number = parseInt(match[2], 10);
if (!isNaN(number)) {
episodes.push({
href: id,
number: number
});
}
}
episodes.reverse();
console.log(episodes);
return JSON.stringify(episodes);
}
async function extractStreamUrl(id) {
if (!_0xCheck()) return 'https://files.catbox.moe/avolvc.mp4';
const cookieHeader = `key=${id}`;
const headers = {
Cookie: cookieHeader
};
const response = await soraFetch(`https://animeheaven.me/gate.php`, { headers });
const html = await response.text();
const sourceRegex = /<source\s+src=['"]([^"']+\.mp4\?[^"']*)['"]\s+type=['"]video\/mp4['"]/i;
const match = html.match(sourceRegex);
if (match) {
const streamUrl = match[1].replace(/&amp;/g, '&');
console.log("Extracted stream URL:", streamUrl);
return streamUrl;
} else {
console.error("Stream URL not found.");
return "";
}
}
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;
}
}
}
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))(_)}

View file

@ -0,0 +1,18 @@
{
"sourceName": "AnimeHeaven",
"iconUrl": "https://animeheaven.me/ah_logo.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.5",
"language": "English (SUB)",
"streamType": "HLS",
"quality": "720p",
"baseUrl": "https://animeheaven.me/",
"searchBaseUrl": "https://animeheaven.me/search.php?s=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animeheaven/animeheaven.js",
"type": "anime",
"asyncJS": true,
"note": "Website was recently hit by a DMCA, so not everything might work right away, please give them time to reupload anime!"
}

BIN
animeheaven/iconalt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

307
animekai/dub/animekai.js Normal file
View file

@ -0,0 +1,307 @@
async function searchResults(query) {
const encodeQuery = keyword => encodeURIComponent(keyword);
const searchBaseUrl = "https://animekai.to/browser?keyword=";
const baseUrl = "https://animekai.to";
const posterHrefRegex = /href="[^"]*" class="poster"/g;
const titleRegex = /class="title"[^>]*title="[^"]*"/g;
const imageRegex = /data-src="[^"]*"/g;
const extractHrefRegex = /href="([^"]*)"/;
const extractImageRegex = /data-src="([^"]*)"/;
const extractTitleRegex = /title="([^"]*)"/;
try {
const encodedQuery = encodeQuery(query);
const searchUrl = searchBaseUrl + encodedQuery;
const response = await fetchv2(searchUrl);
const htmlText = await response.text();
const results = [];
const posterMatches = htmlText.match(posterHrefRegex) || [];
const titleMatches = htmlText.match(titleRegex) || [];
const imageMatches = htmlText.match(imageRegex) || [];
const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length);
for (let index = 0; index < minLength; index++) {
const hrefMatch = posterMatches[index].match(extractHrefRegex);
const fullHref = hrefMatch ?
(hrefMatch[1].startsWith("http") ? hrefMatch[1] : baseUrl + hrefMatch[1]) :
null;
const imageMatch = imageMatches[index].match(extractImageRegex);
const imageSrc = imageMatch ? imageMatch[1] : null;
const titleMatch = titleMatches[index].match(extractTitleRegex);
const cleanTitle = titleMatch ?
decodeHtmlEntities(titleMatch[1]) :
null;
if (fullHref && imageSrc && cleanTitle) {
results.push({
href: fullHref,
image: imageSrc,
title: cleanTitle
});
}
}
return JSON.stringify(results);
} catch (error) {
return JSON.stringify([{
href: "",
image: "",
title: "Search failed: " + error.message
}]);
}
}
async function extractDetails(url) {
try {
const response = await fetchv2(url);
const htmlText = await response.text();
console.log(htmlText);
const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
const aliasesMatch = (/<small class="al-title text-expand">([\s\S]*?)<\/small>/.exec(htmlText) || [])[1];
return JSON.stringify([{
description: descriptionMatch ? cleanHtmlSymbols(descriptionMatch) : "Not available",
aliases: aliasesMatch ? cleanHtmlSymbols(aliasesMatch) : "Not available",
airdate: "If stream doesn't load try later or disable VPN/DNS"
}]);
} catch (error) {
console.error("Error fetching details:" + error);
return [{
description: "Error loading description",
aliases: "Aliases: Unknown",
airdate: "Aired: Unknown"
}];
}
}
async function extractEpisodes(animeUrl) {
try {
const response = await fetchv2(animeUrl);
const htmlText = await response.text();
const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
if (!animeIdMatch) {
return [{
error: "AniID not found"
}];
}
const tokenResponse = await fetchv2(`https://ilovekai.simplepostrequest.workers.dev/?ilovefeet=${encodeURIComponent(animeIdMatch)}`);
const token = await tokenResponse.text();
const episodeListUrl = `https://animekai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
const episodeListResponse = await fetchv2(episodeListUrl);
const episodeListData = await episodeListResponse.json();
const cleanedHtml = cleanJsonHtml(episodeListData.result);
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];
const episodeData = episodeMatches.map(([_, episodeNum, episodeToken]) => ({
name: `Episode ${episodeNum}`,
data: episodeToken
}));
const batchResponse = await fetchv2(
"https://ilovekai.simplepostrequest.workers.dev/?ilovefeet",
{},
"POST",
JSON.stringify(episodeData)
);
const batchResults = await batchResponse.json();
const episodes = batchResults.map((result, index) => ({
number: parseInt(episodeMatches[index][1], 10),
href: `https://animekai.to/ajax/links/list?token=${episodeMatches[index][2]}&_=${result.data}`
}));
return JSON.stringify(episodes);
} catch (err) {
console.error("Error fetching episodes:" + err);
return [{
number: 1,
href: "Error fetching episodes"
}];
}
}
async function extractStreamUrl(url) {
try {
const fetchUrl = `${url}`;
const response = await fetchv2(fetchUrl);
const text = await response.text();
const cleanedHtml = cleanJsonHtml(text);
const subRegex = /<div class="server-items lang-group" data-id="sub"[^>]*>([\s\S]*?)<\/div>/;
const softsubRegex = /<div class="server-items lang-group" data-id="softsub"[^>]*>([\s\S]*?)<\/div>/;
const dubRegex = /<div class="server-items lang-group" data-id="dub"[^>]*>([\s\S]*?)<\/div>/;
const subMatch = subRegex.exec(cleanedHtml);
const softsubMatch = softsubRegex.exec(cleanedHtml);
const dubMatch = dubRegex.exec(cleanedHtml);
const subContent = subMatch ? subMatch[1].trim() : "";
const softsubContent = softsubMatch ? softsubMatch[1].trim() : "";
const dubContent = dubMatch ? dubMatch[1].trim() : "";
const serverSpanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>Server 1<\/span>/;
const serverIdDub = serverSpanRegex.exec(dubContent)?.[1];
const serverIdSoftsub = serverSpanRegex.exec(softsubContent)?.[1];
const serverIdSub = serverSpanRegex.exec(subContent)?.[1];
const tokenRequestData = [
{ name: "Dub", data: serverIdDub },
{ name: "Softsub", data: serverIdSoftsub },
{ name: "Sub", data: serverIdSub }
].filter(item => item.data);
const tokenBatchResponse = await fetchv2(
"https://ilovekai.simplepostrequest.workers.dev/?ilovefeet",
{},
"POST",
JSON.stringify(tokenRequestData)
);
const tokenResults = await tokenBatchResponse.json();
const streamUrls = tokenResults.map(result => {
const serverIdMap = {
"Dub": serverIdDub,
"Softsub": serverIdSoftsub,
"Sub": serverIdSub
};
return {
type: result.name,
url: `https://animekai.to/ajax/links/view?id=${serverIdMap[result.name]}&_=${result.data}`
};
});
const processStreams = async (streamUrls) => {
const streamResponses = await Promise.all(
streamUrls.map(async ({ type, url }) => {
try {
const res = await fetchv2(url);
const json = await res.json();
return {
type: type,
result: json.result
};
} catch (error) {
console.log(`Error fetching ${type} stream:`, error);
return {
type: type,
result: null
};
}
})
);
const decryptRequestData = streamResponses
.filter(item => item.result)
.map(item => ({
name: item.type,
data: item.result
}));
if (decryptRequestData.length === 0) {
return {};
}
const decryptBatchResponse = await fetchv2(
"https://ilovekai.simplepostrequest.workers.dev/?ilovearmpits",
{},
"POST",
JSON.stringify(decryptRequestData)
);
const decryptResults = await decryptBatchResponse.json();
const finalResults = {};
decryptResults.forEach(result => {
try {
const parsed = JSON.parse(result.data);
finalResults[result.name] = parsed.url;
console.log(`decrypted${result.name} URL:` + parsed.url);
} catch (error) {
console.log(`Error parsing ${result.name} result:`, error);
finalResults[result.name] = null;
}
});
return finalResults;
};
const decryptedUrls = await processStreams(streamUrls);
const decryptedDub = decryptedUrls.Dub || decryptedUrls.Sub || decryptedUrls.Softsub;
console.log(decryptedDub);
const headers = {
"Referer": "https://animekai.to/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
};
if (decryptedDub) {
const response = await fetchv2(decryptedDub.replace("/e/", "/media/"), headers);
const responseJson = await response.json();
const result = responseJson?.result;
const postData = {
"text": result,
"Useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
}
const finalResponse = await fetchv2("https://ilovekai.simplepostrequest.workers.dev/ilovebush", {}, "POST", JSON.stringify(postData));
const finalJson = await finalResponse.json();
const m3u8Link = finalJson?.result?.sources?.[0]?.file;
return m3u8Link;
}
return "error";
} catch (error) {
console.log("Fetch error:"+ error);
return "https://error.org";
}
}
function cleanHtmlSymbols(string) {
if (!string) {
return "";
}
return string
.replace(/&#8217;/g, "'")
.replace(/&#8211;/g, "-")
.replace(/&#[0-9]+;/g, "")
.replace(/\r?\n|\r/g, " ")
.replace(/\s+/g, " ")
.trim();
}
function cleanJsonHtml(jsonHtml) {
if (!jsonHtml) {
return "";
}
return jsonHtml
.replace(/\\"/g, "\"")
.replace(/\\'/g, "'")
.replace(/\\\\/g, "\\")
.replace(/\\n/g, "\n")
.replace(/\\t/g, "\t")
.replace(/\\r/g, "\r");
}
function decodeHtmlEntities(text) {
if (!text) {
return "";
}
return text
.replace(/&#039;/g, "'")
.replace(/&quot;/g, "\"")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&nbsp;/g, " ");
}

View file

@ -0,0 +1,20 @@
{
"sourceName": "AnimeKai (Dub)",
"iconUrl": "https://apktodo.io/uploads/2025/5/animekai-icon.jpg",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.3",
"language": "English",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animekai.to/",
"searchBaseUrl": "https://animekai.to/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animekai/dub/animekai.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true,
"note": "Make sure you're on the latest version of Sora."
}

View file

@ -0,0 +1,307 @@
async function searchResults(query) {
const encodeQuery = keyword => encodeURIComponent(keyword);
const searchBaseUrl = "https://animekai.to/browser?keyword=";
const baseUrl = "https://animekai.to";
const posterHrefRegex = /href="[^"]*" class="poster"/g;
const titleRegex = /class="title"[^>]*title="[^"]*"/g;
const imageRegex = /data-src="[^"]*"/g;
const extractHrefRegex = /href="([^"]*)"/;
const extractImageRegex = /data-src="([^"]*)"/;
const extractTitleRegex = /title="([^"]*)"/;
try {
const encodedQuery = encodeQuery(query);
const searchUrl = searchBaseUrl + encodedQuery;
const response = await fetchv2(searchUrl);
const htmlText = await response.text();
const results = [];
const posterMatches = htmlText.match(posterHrefRegex) || [];
const titleMatches = htmlText.match(titleRegex) || [];
const imageMatches = htmlText.match(imageRegex) || [];
const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length);
for (let index = 0; index < minLength; index++) {
const hrefMatch = posterMatches[index].match(extractHrefRegex);
const fullHref = hrefMatch ?
(hrefMatch[1].startsWith("http") ? hrefMatch[1] : baseUrl + hrefMatch[1]) :
null;
const imageMatch = imageMatches[index].match(extractImageRegex);
const imageSrc = imageMatch ? imageMatch[1] : null;
const titleMatch = titleMatches[index].match(extractTitleRegex);
const cleanTitle = titleMatch ?
decodeHtmlEntities(titleMatch[1]) :
null;
if (fullHref && imageSrc && cleanTitle) {
results.push({
href: fullHref,
image: imageSrc,
title: cleanTitle
});
}
}
return JSON.stringify(results);
} catch (error) {
return JSON.stringify([{
href: "",
image: "",
title: "Search failed: " + error.message
}]);
}
}
async function extractDetails(url) {
try {
const response = await fetchv2(url);
const htmlText = await response.text();
console.log(htmlText);
const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
const aliasesMatch = (/<small class="al-title text-expand">([\s\S]*?)<\/small>/.exec(htmlText) || [])[1];
return JSON.stringify([{
description: descriptionMatch ? cleanHtmlSymbols(descriptionMatch) : "Not available",
aliases: aliasesMatch ? cleanHtmlSymbols(aliasesMatch) : "Not available",
airdate: "If stream doesn't load try later or disable VPN/DNS"
}]);
} catch (error) {
console.error("Error fetching details:" + error);
return [{
description: "Error loading description",
aliases: "Aliases: Unknown",
airdate: "Aired: Unknown"
}];
}
}
async function extractEpisodes(animeUrl) {
try {
const response = await fetchv2(animeUrl);
const htmlText = await response.text();
const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
if (!animeIdMatch) {
return [{
error: "AniID not found"
}];
}
const tokenResponse = await fetchv2(`https://ilovekai.simplepostrequest.workers.dev/?ilovefeet=${encodeURIComponent(animeIdMatch)}`);
const token = await tokenResponse.text();
const episodeListUrl = `https://animekai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
const episodeListResponse = await fetchv2(episodeListUrl);
const episodeListData = await episodeListResponse.json();
const cleanedHtml = cleanJsonHtml(episodeListData.result);
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];
const episodeData = episodeMatches.map(([_, episodeNum, episodeToken]) => ({
name: `Episode ${episodeNum}`,
data: episodeToken
}));
const batchResponse = await fetchv2(
"https://ilovekai.simplepostrequest.workers.dev/?ilovefeet",
{},
"POST",
JSON.stringify(episodeData)
);
const batchResults = await batchResponse.json();
const episodes = batchResults.map((result, index) => ({
number: parseInt(episodeMatches[index][1], 10),
href: `https://animekai.to/ajax/links/list?token=${episodeMatches[index][2]}&_=${result.data}`
}));
return JSON.stringify(episodes);
} catch (err) {
console.error("Error fetching episodes:" + err);
return [{
number: 1,
href: "Error fetching episodes"
}];
}
}
async function extractStreamUrl(url) {
try {
const fetchUrl = `${url}`;
const response = await fetchv2(fetchUrl);
const text = await response.text();
const cleanedHtml = cleanJsonHtml(text);
const subRegex = /<div class="server-items lang-group" data-id="sub"[^>]*>([\s\S]*?)<\/div>/;
const softsubRegex = /<div class="server-items lang-group" data-id="softsub"[^>]*>([\s\S]*?)<\/div>/;
const dubRegex = /<div class="server-items lang-group" data-id="dub"[^>]*>([\s\S]*?)<\/div>/;
const subMatch = subRegex.exec(cleanedHtml);
const softsubMatch = softsubRegex.exec(cleanedHtml);
const dubMatch = dubRegex.exec(cleanedHtml);
const subContent = subMatch ? subMatch[1].trim() : "";
const softsubContent = softsubMatch ? softsubMatch[1].trim() : "";
const dubContent = dubMatch ? dubMatch[1].trim() : "";
const serverSpanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>Server 1<\/span>/;
const serverIdDub = serverSpanRegex.exec(dubContent)?.[1];
const serverIdSoftsub = serverSpanRegex.exec(softsubContent)?.[1];
const serverIdSub = serverSpanRegex.exec(subContent)?.[1];
const tokenRequestData = [
{ name: "Dub", data: serverIdDub },
{ name: "Softsub", data: serverIdSoftsub },
{ name: "Sub", data: serverIdSub }
].filter(item => item.data);
const tokenBatchResponse = await fetchv2(
"https://ilovekai.simplepostrequest.workers.dev/?ilovefeet",
{},
"POST",
JSON.stringify(tokenRequestData)
);
const tokenResults = await tokenBatchResponse.json();
const streamUrls = tokenResults.map(result => {
const serverIdMap = {
"Dub": serverIdDub,
"Softsub": serverIdSoftsub,
"Sub": serverIdSub
};
return {
type: result.name,
url: `https://animekai.to/ajax/links/view?id=${serverIdMap[result.name]}&_=${result.data}`
};
});
const processStreams = async (streamUrls) => {
const streamResponses = await Promise.all(
streamUrls.map(async ({ type, url }) => {
try {
const res = await fetchv2(url);
const json = await res.json();
return {
type: type,
result: json.result
};
} catch (error) {
console.log(`Error fetching ${type} stream:`, error);
return {
type: type,
result: null
};
}
})
);
const decryptRequestData = streamResponses
.filter(item => item.result)
.map(item => ({
name: item.type,
data: item.result
}));
if (decryptRequestData.length === 0) {
return {};
}
const decryptBatchResponse = await fetchv2(
"https://ilovekai.simplepostrequest.workers.dev/?ilovearmpits",
{},
"POST",
JSON.stringify(decryptRequestData)
);
const decryptResults = await decryptBatchResponse.json();
const finalResults = {};
decryptResults.forEach(result => {
try {
const parsed = JSON.parse(result.data);
finalResults[result.name] = parsed.url;
console.log(`decrypted${result.name} URL:` + parsed.url);
} catch (error) {
console.log(`Error parsing ${result.name} result:`, error);
finalResults[result.name] = null;
}
});
return finalResults;
};
const decryptedUrls = await processStreams(streamUrls);
const decryptedSub = decryptedUrls.Sub || decryptedUrls.Softsub || decryptedUrls.Dub;
console.log(decryptedSub);
const headers = {
"Referer": "https://animekai.to/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
};
if (decryptedSub) {
const response = await fetchv2(decryptedSub.replace("/e/", "/media/"), headers);
const responseJson = await response.json();
const result = responseJson?.result;
const postData = {
"text": result,
"Useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
}
const finalResponse = await fetchv2("https://ilovekai.simplepostrequest.workers.dev/ilovebush", {}, "POST", JSON.stringify(postData));
const finalJson = await finalResponse.json();
const m3u8Link = finalJson?.result?.sources?.[0]?.file;
return m3u8Link;
}
return "error";
} catch (error) {
console.log("Fetch error:"+ error);
return "https://error.org";
}
}
function cleanHtmlSymbols(string) {
if (!string) {
return "";
}
return string
.replace(/&#8217;/g, "'")
.replace(/&#8211;/g, "-")
.replace(/&#[0-9]+;/g, "")
.replace(/\r?\n|\r/g, " ")
.replace(/\s+/g, " ")
.trim();
}
function cleanJsonHtml(jsonHtml) {
if (!jsonHtml) {
return "";
}
return jsonHtml
.replace(/\\"/g, "\"")
.replace(/\\'/g, "'")
.replace(/\\\\/g, "\\")
.replace(/\\n/g, "\n")
.replace(/\\t/g, "\t")
.replace(/\\r/g, "\r");
}
function decodeHtmlEntities(text) {
if (!text) {
return "";
}
return text
.replace(/&#039;/g, "'")
.replace(/&quot;/g, "\"")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&nbsp;/g, " ");
}

View file

@ -0,0 +1,20 @@
{
"sourceName": "AnimeKai (Hardsub)",
"iconUrl": "https://apktodo.io/uploads/2025/5/animekai-icon.jpg",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.2",
"language": "English",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animekai.to/",
"searchBaseUrl": "https://animekai.to/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animekai/hardsub/animekai.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true,
"note": "Make sure you're on the latest version of Sora."
}

111
animekhor/animekhor.js Normal file
View file

@ -0,0 +1,111 @@
async function searchResults(keyword) {
const results = [];
const response = await fetchv2(`https://animekhor.org/?s=${keyword}`);
const html = await response.text();
const regex = /<article class="bs"[^>]*>.*?<a href="([^"]+)"[^>]*>.*?<img src="([^"]+)"[^>]*>.*?<h2[^>]*>(.*?)<\/h2>/gs;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3].trim(),
image: match[2].trim(),
href: match[1].trim()
});
}
return JSON.stringify(results);
}
async function extractDetails(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/<div class="entry-content"[^>]*>([\s\S]*?)<\/div>/);
let description = "N/A";
if (match) {
description = match[1]
.replace(/<[^>]+>/g, '')
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&amp;/g, "&")
.trim();
}
results.push({
description: description,
aliases: 'N/A',
airdate: 'N/A'
});
return JSON.stringify(results);
}
async function extractEpisodes(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const regex = /<div class="inepcx">\s*<a href="([^"#]+)">\s*<span>New Episode<\/span>/;
const match = regex.exec(html);
if (match) {
results.push({
href: match[1].trim(),
number: 1
});
}
return JSON.stringify(results);
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const iframeMatch = html.match(/dailymotion\.com\/embed\/video\/([a-zA-Z0-9]+)/);
if (!iframeMatch) return "no iframe";
const videoId = iframeMatch[1];
const metaRes = await fetchv2(`https://www.dailymotion.com/player/metadata/video/${videoId}`);
const metaJson = await metaRes.json();
const hlsLink = metaJson.qualities?.auto?.[0]?.url;
if (!hlsLink) return "no hls";
async function getBestHls(hlsUrl) {
try {
const res = await fetchv2(hlsUrl);
const text = await res.text();
const regex = /#EXT-X-STREAM-INF:.*RESOLUTION=(\d+)x(\d+).*?\n(https?:\/\/[^\n]+)/g;
const streams = [];
let match;
while ((match = regex.exec(text)) !== null) {
streams.push({
width: parseInt(match[1]),
height: parseInt(match[2]),
url: match[3]
});
}
if (streams.length === 0) return hlsUrl;
streams.sort((a, b) => b.height - a.height);
return streams[0].url;
} catch {
return hlsUrl;
}
}
const bestHls = await getBestHls(hlsLink);
return bestHls;
} catch {
const empty = "{ streams: [";
console.log("Extracted stream result:" + JSON.stringify(empty));
return JSON.stringify(empty);
}
}

19
animekhor/animekhor.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimeKhor",
"iconUrl": "https://i3.wp.com/animekhor.org/wp-content/uploads/2022/02/cropped-logo-192x192.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Chinese",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animekhor.org/",
"searchBaseUrl": "https://animekhor.org/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animekhor/animekhor.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

122
animeland/animeland.js Normal file
View file

@ -0,0 +1,122 @@
async function searchResults(keyword) {
const baseUrl = "https://w7.animeland.tv";
const results = [];
try {
const response = await fetchv2(baseUrl + "/?s=" + encodeURIComponent(keyword));
const html = await response.text();
const regex = /<a href="([^"]+)"[^>]*>\s*<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"/g;
let match;
while ((match = regex.exec(html)) !== null) {
let href = match[1].trim();
let image = match[2].trim();
let title = match[3].trim();
if (href.startsWith("/")) {
href = baseUrl + href;
}
if (image.startsWith("/")) {
image = baseUrl + image;
}
if (href === baseUrl + "/" || href.includes("kissanimes.net")) {
continue;
}
results.push({
href,
image,
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 = /<div class="Anime Info">\s*<\/div>\s*([\s\S]*?)<\/div>/i;
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 regex = /<li class="play"><a[^>]*href="([^"]+)"[^>]*>([^<]*)<\/a><\/li>/g;
let match;
while ((match = regex.exec(html)) !== null) {
const href = match[1].trim();
const text = match[2].trim();
let number = null;
const urlMatch = href.match(/-episode-(\d+)/i);
if (urlMatch) {
number = parseInt(urlMatch[1], 10);
} else {
const textMatch = text.match(/Episode\s*(\d+)/i);
if (textMatch) number = parseInt(textMatch[1], 10);
}
results.push({
href,
number
});
}
return JSON.stringify(results.reverse());
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/file=([a-zA-Z0-9]+\.html)/);
if (match) {
const filename = match[1];
console.log('Filename:' + filename);
const videoUrl = `https://animesource.me/cache/${filename}.mp4`;
console.log('Video URL:' + videoUrl);
return videoUrl;
}
} catch (err) {
console.error("Error:" + err);
return null;
}
}

19
animeland/animeland.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimeLand",
"iconUrl": "https://w7.animeland.tv/wp-content/themes/twentytwelve/SAO2.ico",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "English",
"streamType": "mp4",
"quality": "1080p",
"baseUrl": "https://w7.animeland.tv/",
"searchBaseUrl": "https://w7.animeland.tv/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animeland/animeland.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true
}

237
animemeow/animemeow.js Normal file
View file

@ -0,0 +1,237 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://animemeow.xyz/directorio/?q=" + encodeURIComponent(keyword));
const html = await response.text();
const containerMatch = html.match(/<ul class="grid-animes directorio">([\s\S]*?)<\/ul>/);
const containerHtml = containerMatch ? containerMatch[1] : "";
const regex = /<a href="([^"]+)">[\s\S]*?<div class="main-img">[\s\S]*?<img[^>]+src="([^"]+)"[^>]*>[\s\S]*?<p>([^<]+)<\/p>/g;
let match;
while ((match = regex.exec(containerHtml)) !== null) {
results.push({
href: "https://animemeow.xyz" + match[1].trim(),
image: "https://animemeow.xyz" + match[2].trim(),
title: match[3].trim()
});
}
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 match = html.match(/<p class="sinopsis" id="sinopsis">([\s\S]*?)<\/p>/);
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 containerMatch = html.match(/<ul id="eps">([\s\S]*?)<\/ul>/);
const containerHtml = containerMatch ? containerMatch[1] : "";
const regex = /<a href="([^"]+)">[\s\S]*?<p>[\s\S]*?<\/i>\s*([^<]+)<\/p>/g;
let match;
while ((match = regex.exec(containerHtml)) !== null) {
const href = "https://animemeow.xyz" + match[1].trim();
let number;
if (/Episodio\s*(\d+)/i.test(match[2])) {
number = parseInt(match[2].match(/Episodio\s*(\d+)/i)[1], 10);
} else if (/Ver Pel[ií]cula/i.test(match[2])) {
number = 1;
} else {
number = "N/A";
}
results.push({ href, number });
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const buttonMatch = html.match(/<button[^>]+data-url="([^"]*voe[^"]+)"[^>]*>/i);
if (!buttonMatch) return "https://error.org/";
const voeLink = buttonMatch[1].split('id=')[1];
console.log("VOE Link: " + voeLink);
const voeResponse = await fetchv2(voeLink);
const voeHtml = await voeResponse.text();
const redirectMatch = voeHtml.match(/window\.location\.href\s*=\s*['"]([^'"]+)['"]/);
if (!redirectMatch) return null;
const finalLink = redirectMatch[1];
const streamUrlOne = finalLink;
const responseTwo = await fetchv2(streamUrlOne);
const finalHtml = await responseTwo.text();
let streamUrl = null;
try {
streamUrl = voeExtractor(finalHtml);
} catch (error) {
console.log("VOE extraction error:", error);
return null;
}
console.log("Voe Stream URL: " + streamUrl);
return streamUrl;
} catch (err) {
return "https://error.org/";
}
}
/* SCHEME START */
/**
* @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("");
}
/* SCHEME END */

19
animemeow/animemeow.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimeMeow",
"iconUrl": "https://files.catbox.moe/5phbht.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Spanish (DUB/SUB)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animemeow.xyz/",
"searchBaseUrl": "https://animemeow.xyz/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animemeow/animemeow.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

324
animenana/animenana.js Normal file
View file

@ -0,0 +1,324 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://animenana.com/search/?key=" + keyword);
const html = await response.text();
const cardMatches = html.match(/<div class="card component-latest">[\s\S]*?<\/div>\s*<\/div>\s*<\/a>/g);
if (cardMatches) {
for (const cardHtml of cardMatches) {
const hrefMatch = cardHtml.match(/<a href="([^"]+)"/);
const imgMatch = cardHtml.match(/<img[^>]+(?:data-src|src)="([^"]+)"/);
const titleMatch = cardHtml.match(/<h5 class="animename"[^>]*>(.*?)<\/h5>/);
if (hrefMatch && imgMatch && titleMatch) {
results.push({
href: "https://animenana.com" + hrefMatch[1].trim(),
image: "https://animenana.com" + imgMatch[1].trim(),
title: titleMatch[1].trim()
});
}
}
}
if (results.length === 0) {
const colMatches = html.match(/<div class="col-md-4">[\s\S]*?<\/div>\s*<\/div>\s*<\/div>\s*<\/a>\s*<\/div>/g);
if (colMatches) {
for (const colHtml of colMatches) {
const hrefMatch = colHtml.match(/<a href="([^"]+)"/);
const imgMatch = colHtml.match(/<img[^>]+(?:data-src|src)="([^"]+)"/);
const titleMatch = colHtml.match(/<h5 class="animename"[^>]*>(.*?)<\/h5>/);
if (hrefMatch && imgMatch && titleMatch) {
results.push({
href: "https://animenana.com" + hrefMatch[1].trim(),
image: "https://animenana.com" + imgMatch[1].trim(),
title: titleMatch[1].trim()
});
}
}
}
}
if (results.length === 0) {
const regex = /<a href="([^"]+)"[\s\S]*?<img[^>]+(?:data-src|src)="([^"]+)"[\s\S]*?<h5 class="animename"[^>]*>(.*?)<\/h5>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: "https://animenana.com" + match[1].trim(),
image: "https://animenana.com" + match[2].trim(),
title: match[3].trim()
});
}
}
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><b>Description:\s*<\/b><\/p>([\s\S]*?)<br\s*\/?>/i;
const match = regex.exec(html);
let description = match ? match[1].trim() : "N/A";
description = description.replace(/<[^>]+>/g, "").trim();
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();
// More flexible regex to handle the actual HTML structure
const epRegex = /<a href="([^"]+)"[^>]*title="[^"]*Episode\s*(\d+)">/g;
let match;
while ((match = epRegex.exec(html)) !== null) {
results.push({
href: "https://animenana.com" + match[1].trim(),
number: parseInt(match[2], 10)
});
}
const specialRegex = /<span class="badge[^"]*"[^>]*>([^<]+)<\/span>[\s\S]*?<a href="([^"]+)"[^>]*>[\s\S]*?<h5 class="animename">([^<]+)<\/h5>/g;
while ((match = specialRegex.exec(html)) !== null) {
results.push({
href: "https://animenana.com" + match[2].trim(),
number: 1
});
}
if (results.length >= 2 && results[0].number > results[1].number) {
results.reverse();
results.forEach((item, index) => {
item.number = index + 1;
});
}
if (results.length === 0) {
results.push({
href: url,
number: 1
});
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error",
type: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const fmRegex = /function\s+fm\(\)\s*\{[^}]*document\.getElementById\("videowrapper"\)\.innerHTML\s*=\s*['"]<iframe\s+src=['"]([^'"]+)['"]/;
const match = fmRegex.exec(html);
let streamUrl = "https://files.catbox.moe/avolvc.mp4";
if (match && match[1]) {
const iframeSrc = match[1];
if (iframeSrc.startsWith("https://")) {
streamUrl = iframeSrc;
} else {
streamUrl = "https://animenana.com" + iframeSrc;
}
}
const finalUrl = streamUrl;
console.log(finalUrl);
const diejfioe = await fetchv2(finalUrl);
const jdi83rjf = await diejfioe.text();
const kvrokofrmfrklefmklrd = jdi83rjf.match(/<iframe[^>]+src="([^"]+)"/);
if (kvrokofrmfrklefmklrd) {
const iframeUrl = kvrokofrmfrklefmklrd[1];
console.log("Iframe URL:"+ iframeUrl);
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": "https://animenana.com" + url,
};
const i9jfrhtiee = await fetchv2(iframeUrl, headers);
const kopefjir4o0 = await i9jfrhtiee.text();
const obfuscatedScript = kopefjir4o0.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
const unpackedScript = unpack(obfuscatedScript[1]);
//console.log(unpackedScript);
const hlsMatch = unpackedScript.match(/file:"(https?:\/\/.*?\.m3u8.*?)"/);
const hlsUrl = hlsMatch ? hlsMatch[1] : null;
console.log("HLS URL:"+hlsUrl);
return hlsUrl;
} else {
console.log("No iframe found");
}
return "blehh";
} catch (err) {
console.log(err);
return "https://files.catbox.moe/avolvc.mp4";
}
}
/***********************************************************
* UNPACKER MODULE
* Credit to GitHub user "mnsrulz" for Unpacker Node library
* https://github.com/mnsrulz/unpacker
***********************************************************/
class Unbaser {
constructor(base) {
/* Functor for a given base. Will efficiently convert
strings to natural numbers. */
this.ALPHABET = {
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
};
this.dictionary = {};
this.base = base;
// fill elements 37...61, if necessary
if (36 < base && base < 62) {
this.ALPHABET[base] = this.ALPHABET[base] ||
this.ALPHABET[62].substr(0, base);
}
// If base can be handled by int() builtin, let it do it for us
if (2 <= base && base <= 36) {
this.unbase = (value) => parseInt(value, base);
}
else {
// Build conversion dictionary cache
try {
[...this.ALPHABET[base]].forEach((cipher, index) => {
this.dictionary[cipher] = index;
});
}
catch (er) {
throw Error("Unsupported base encoding.");
}
this.unbase = this._dictunbaser;
}
}
_dictunbaser(value) {
/* Decodes a value to an integer. */
let ret = 0;
[...value].reverse().forEach((cipher, index) => {
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
});
return ret;
}
}
function detect(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) {
/* Unpacks P.A.C.K.E.R. packed js code. */
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) {
/* Look up symbols in the synthetic symtab. */
const word = match;
let word2;
if (radix == 1) {
//throw Error("symtab unknown");
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) {
/* Juice from a source file the four args needed by decoder. */
const juicers = [
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
];
for (const juicer of juicers) {
//const args = re.search(juicer, source, re.DOTALL);
const args = juicer.exec(source);
if (args) {
let a = args;
if (a[2] == "[]") {
//don't know what it is
// a = list(a);
// a[1] = 62;
// a = tuple(a);
}
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) {
/* Strip string lookup table (list) and replace values in source. */
/* Need to work on this. */
return source;
}
}

19
animenana/animenana.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimeNana",
"iconUrl": "https://animenana.com/favicon.ico",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "English (Hardsub)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animenana.com/",
"searchBaseUrl": "https://animenana.com/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animenana/animenana.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

102
animenix/animenix.js Normal file
View file

@ -0,0 +1,102 @@
function cleanTitle(title) {
return title
.replace(/&#8217;/g, "'")
.replace(/&#8211;/g, "-")
.replace(/&#[0-9]+;/g, "");
}
async function searchResults(keyword) {
const results = [];
const response = await fetchv2(`https://animenix.com/?s=${keyword}`);
const html = await response.text();
const regex = /<article class="bs"[^>]*>.*?<a href="([^"]+)"[^>]*>.*?<img src="([^"]+)"[^>]*>.*?<h2[^>]*>(.*?)<\/h2>/gs;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: cleanTitle(match[3].trim()),
image: match[2].trim(),
href: match[1].trim()
});
}
return JSON.stringify(results);
}
async function extractDetails(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/<div class="entry-content"[^>]*>([\s\S]*?)<\/div>/);
let description = "N/A";
if (match) {
description = match[1]
.replace(/<[^>]+>/g, '')
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&amp;/g, "&")
.trim();
}
results.push({
description: description,
aliases: 'N/A',
airdate: 'N/A'
});
return JSON.stringify(results);
}
async function extractEpisodes(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const regex = /<li data-index="\d+">[\s\S]*?<a href="([^"]+)">/g;
let match;
let count = 1;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
number: count
});
count++;
}
results.reverse();
return JSON.stringify(results.reverse());
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const optionMatch = html.match(/<option value="([^"]+)"[^>]*>\s*YourUpload\s*<\/option>/);
if (!optionMatch) return "https://error.org/";
const decodedHtml = atob(optionMatch[1]);
const iframeMatch = decodedHtml.match(/<iframe[^>]+src="([^"]+)"/);
if (!iframeMatch) return "https://error.org/";
const iframeUrl = iframeMatch[1];
const iframeResponse = await fetchv2(iframeUrl);
const iframeHtml = await iframeResponse.text();
const fileMatch = iframeHtml.match(/file:\s*'([^']+\.mp4)'/);
if (!fileMatch) return "https://error.org/";
return fileMatch[1];
} catch (err) {
return "https://error.org/";
}
}

19
animenix/animenix.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimeNix",
"iconUrl": "https://i3.wp.com/animenix.com/wp-content/uploads/2024/11/cropped-favicon-1-192x192.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Spanish (DUB/SUB)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://www.yourupload.com/",
"searchBaseUrl": "https://www.yourupload.com/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animenix/animenix.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true
}

92
animenosub/animenosub.js Normal file
View file

@ -0,0 +1,92 @@
async function searchResults(keyword) {
const results = [];
const response = await fetchv2(`https://animenosub.to/?s=${keyword}`);
const html = await response.text();
// Regex pattern to extract the title, image, and href from the article elements
const regex = /<article class="bs"[^>]*>.*?<a href="([^"]+)"[^>]*>.*?<img src="([^"]+)"[^>]*>.*?<h2[^>]*>(.*?)<\/h2>/gs;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3].trim(),
image: match[2].trim(),
href: match[1].trim()
});
}
return JSON.stringify(results);
}
async function extractDetails(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/<div class="entry-content"[^>]*>([\s\S]*?)<\/div>/);
let description = "N/A";
if (match) {
description = match[1]
.replace(/<[^>]+>/g, '')
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&amp;/g, "&")
.trim();
}
results.push({
description: description,
aliases: 'N/A',
airdate: 'N/A'
});
return JSON.stringify(results);
}
async function extractEpisodes(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const regex = /<a href="([^"]+)">\s*<div class="epl-num">([\d.]+)<\/div>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
number: parseInt(match[2], 10)
});
}
results.reverse();
return JSON.stringify(results);
}
async function extractStreamUrl(url) {
const response = await fetchv2(url);
const html = await response.text();
const regexSub = /<option value="([^"]+)"[^>]*>\s*SUB - Omega\s*<\/option>/;
const regexFallback = /<option value="([^"]+)"[^>]*>\s*Omega\s*<\/option>/;
const anotherFallbackDawggggWhatsWrongWithTHisWebsite = /<option value="([^"]+)"[^>]*>\s*SUB v2 - Omega\s*<\/option>/;
let match = html.match(regexSub) || html.match(regexFallback) || html.match(anotherFallbackDawggggWhatsWrongWithTHisWebsite);
if (!match) return null;
const decodedHtml = atob(match[1]); // Decode base64
const iframeMatch = decodedHtml.match(/<iframe\s+src="([^"]+)"/);
if (!iframeMatch) 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)"/);
console.error(m3u8Match ? m3u8Match[1] : null);
return m3u8Match ? m3u8Match[1] : null;
}

View file

@ -0,0 +1,17 @@
{
"sourceName": "AnimeNoSub",
"iconUrl": "https://i3.wp.com/animenosub.to/wp-content/uploads/2024/04/cropped-favicon-192x192.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.4",
"language": "English (SUB/DUB)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://vidmoly.to/",
"searchBaseUrl": "https://animenosub.to/?s=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animenosub/animenosub.js",
"asyncJS": true,
"type": "anime"
}

138
animeq/animeq.js Normal file
View file

@ -0,0 +1,138 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://animeq.blog/?s=" + encodeURIComponent(keyword));
const html = await response.text();
const regex = /<div class="result-item">[\s\S]*?<a href="([^"]+)"[^>]*>\s*<img src="([^"]+)"[^>]*alt="([^"]+)"/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
image: match[2].trim(),
title: cleanHtmlSymbols(match[3].trim())
});
}
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*Sinopse\s*:\s*([\s\S]*?)<\/p>/i;
const match = regex.exec(html);
const description = match ? match[1].trim() : "fuck off you don't need a description";
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 = [];
const regex = /<div class=['"]?numerando['"]?[^>]*>\d+\s*-\s*(\d+)<\/div>[\s\S]*?<a\s+href=['"]([^'"]+)['"][^>]*>/g;
try {
const response = await fetchv2(url);
const html = await response.text();
let match;
while ((match = regex.exec(html)) !== null) {
const episodeNumber = parseInt(match[1], 10);
const href = match[2].trim();
results.push({
href: "episode: " + href,
number: episodeNumber
});
}
if (results.length === 0) {
results.push({
href: "movie: " + url,
number: 1
});
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
let endpointType;
if (url.startsWith("movie: ")) {
url = url.replace("movie: ", "");
endpointType = "movie";
} else if (url.startsWith("episode: ")) {
url = url.replace("episode: ", "");
endpointType = "tv";
} else {
return "ERROR";
}
const response = await fetchv2(url);
const html = await response.text();
const idMatch = html.match(/<link rel=['"]shortlink['"] href=['"][^?]+\?p=(\d+)['"]/);
if (!idMatch) return "ID NOT FOUND";
const id = idMatch[1];
const apiUrl = `https://animeq.blog/wp-json/dooplayer/v2/${id}/${endpointType}/1`;
const apiResponse = await fetchv2(apiUrl);
const apiData = await apiResponse.json();
if (!apiData.embed_url) return { error: "embed_url not found" };
const embedResponse = await fetchv2(apiData.embed_url);
const embedHtml = await embedResponse.text();
const fileMatch = embedHtml.match(/"file":"(https?:\\\/\\\/[^"]+)"/);
if (!fileMatch) return { error: "file not found" };
const fileUrl = fileMatch[1].replace(/\\\//g, "/");
return fileUrl;
} catch (err) {
console.error("Error extracting stream URL:"+ err);
return "{ error: err.message }";
}
}
function cleanHtmlSymbols(string) {
if (!string) return "";
return string
.replace(/&#8217;/g, "'")
.replace(/&#8211;/g, "-")
.replace(/&#[0-9]+;/g, "")
.replace(/\r?\n|\r/g, " ")
.replace(/\s+/g, " ")
.trim();
}

19
animeq/animeq.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "Animeq",
"iconUrl": "https://animeq.blog/wp-content/uploads/2025/06/Favicon-AnimeQ-1.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Portuguese",
"streamType": "mp4",
"quality": "1080p",
"baseUrl": "https://animeq.blog/",
"searchBaseUrl": "https://animeq.blog/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animeq/animeq.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true
}

View file

@ -0,0 +1,79 @@
async function searchResults(keyword) {
const results = [];
const response = await fetchv2(`https://www.animesaturn.cx/animelist?search=${keyword}`);
const html = await response.text();
const regex = /<a href="(https:\/\/www\.animesaturn\.cx\/anime\/[^"]+)"[^>]*class="thumb image-wrapper">\s*<img src="(https:\/\/cdn\.animesaturn\.cx\/static\/images\/copertine\/[^"]+)"[^>]*alt="([^"]+)"/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3].trim(),
image: match[2].trim(),
href: match[1].trim()
});
}
return JSON.stringify(results);
}
async function extractDetails(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const descriptionRegex = /<div id="shown-trama">([^<]+)<\/div>/;
const descriptionMatch = html.match(descriptionRegex);
const description = descriptionMatch ? descriptionMatch[1].trim() : 'N/A';
results.push({
description: description,
aliases: 'N/A',
airdate: 'N/A'
});
return JSON.stringify(results);
}
async function extractEpisodes(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const episodeRegex = /<a\s+href="(https:\/\/www\.animesaturn\.cx\/ep\/[^"]+)"\s*target="_blank"\s*class="btn btn-dark mb-1 bottone-ep">\s*Episodio\s+(\d+)\s*<\/a>/gs;
let match;
while ((match = episodeRegex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
number: parseInt(match[2], 10)
});
}
return JSON.stringify(results);
}
async function extractStreamUrl(url) {
const response = await fetchv2(url);
const html = await response.text();
const streamUrlRegex = /<a href="(https:\/\/www\.animesaturn\.cx\/watch\?file=[^"]+)"/;
const match = html.match(streamUrlRegex);
const redirect = match ? match[1] : null;
const responseTwo = await fetchv2(redirect);
const htmlTwo = await responseTwo.text();
const hlsUrlRegex = /file:\s*"(https:\/\/[^"]+\.m3u8)"/;
const hlsMatch = htmlTwo.match(hlsUrlRegex);
if (hlsMatch) {
return hlsMatch[1].trim();
}
const mp4UrlRegex = /<source[^>]+src="(https:\/\/[^">]+\.mp4)"/;
const mp4Match = htmlTwo.match(mp4UrlRegex);
return mp4Match ? mp4Match[1].trim() : null;
}

View file

@ -0,0 +1,17 @@
{
"sourceName": "AnimeSaturn",
"iconUrl": "https://www.animesaturn.cx/immagini/apple-touch-icon.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Italian (SUB)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://www.animesaturn.cx/",
"searchBaseUrl": "https://www.animesaturn.cx/?q=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animesaturn/animesaturn.js",
"asyncJS": true,
"type": "anime"
}

View file

@ -0,0 +1,119 @@
async function searchResults(keyword) {
const results = [];
try {
const headers = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With": "XMLHttpRequest"
};
const postdata = `token=c1deb78cd4&pagina=1&search=${keyword}&limit=3000&type=lista&filters=%7B%22filter_data%22%3A%22filter_letter%3D0%26type_url%3Danimes%26filter_audio%3Dlegendado%26filter_order%3Dname%22%2C%22filter_genre_add%22%3A%5B%5D%2C%22filter_genre_del%22%3A%5B%5D%7D`;
const response = await fetchv2("https://animesdigital.org/func/listanime", headers, "POST", postdata);
const data = await response.json();
const regex = /<a href="([^"]+)"[^>]*>.*?<img src="([^"]+)"[^>]*>.*?<span class="title_anime">(.*?)<\/span>/s;
for (const item of data.results) {
const match = regex.exec(item);
if (match) {
results.push({
href: match[1].trim(),
image: match[2].trim(),
title: match[3].trim()
});
}
}
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 = /<div class="sinopse">(.*?)<\/div>/s;
const match = regex.exec(html);
const description = match ? match[1]
.replace(/&nbsp;/g, " ")
.replace(/\s+/g, " ")
.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 regex = /<a href="([^"]+)"[^>]*>[\s\S]*?<div class="title_anime">.*?Epis[oó]dio\s*([0-9]+(?:\.[0-9]+)?)<\/div>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
number: Math.round(parseFloat(match[2]))
});
}
return JSON.stringify(results.reverse());
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const iframeRegex = /<iframe[^>]*src="([^"]*anivideo\.net[^"]*)"[^>]*>/i;
const iframeMatch = html.match(iframeRegex);
if (!iframeMatch) {
return "https://files.catbox.moe/avolvc.mp4";
}
const apiUrl = iframeMatch[1];
const apiResponse = await fetchv2(apiUrl);
const apiHtml = await apiResponse.text();
const m3u8Regex = /file:\s*['"]([^'"]*\.m3u8[^'"]*)['"]/i;
const m3u8Match = apiHtml.match(m3u8Regex);
if (m3u8Match) {
return m3u8Match[1];
}
return "https://files.catbox.moe/avolvc.mp4";
} catch (err) {
console.error('Error extracting stream URL:', err);
return "https://files.catbox.moe/avolvc.mp4";
}
}

View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimesDigital",
"iconUrl": "https://animesdigital.org/wp-content/uploads/2025/03/cropped-logo-e1740827992823-192x192.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Portuguese",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animesdigital.org/home",
"searchBaseUrl": "https://animesdigital.org/home",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animesdigital/animesdigital.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

138
animesdrive/animesdrive.js Normal file
View file

@ -0,0 +1,138 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://animesdrive.blog/?s=" + encodeURIComponent(keyword));
const html = await response.text();
const regex = /<div class="result-item">[\s\S]*?<a href="([^"]+)"[^>]*>\s*<img src="([^"]+)"[^>]*alt="([^"]+)"/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
image: match[2].trim(),
title: cleanHtmlSymbols(match[3].trim())
});
}
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*Sinopse\s*:\s*([\s\S]*?)<\/p>/i;
const match = regex.exec(html);
const description = match ? match[1].trim() : "fuck off you don't need a description";
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 = [];
const regex = /<div class=['"]?numerando['"]?[^>]*>\d+\s*-\s*(\d+)<\/div>[\s\S]*?<a\s+href=['"]([^'"]+)['"][^>]*>/g;
try {
const response = await fetchv2(url);
const html = await response.text();
let match;
while ((match = regex.exec(html)) !== null) {
const episodeNumber = parseInt(match[1], 10);
const href = match[2].trim();
results.push({
href: "episode: " + href,
number: episodeNumber
});
}
if (results.length === 0) {
results.push({
href: "movie: " + url,
number: 1
});
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
let endpointType;
if (url.startsWith("movie: ")) {
url = url.replace("movie: ", "");
endpointType = "movie";
} else if (url.startsWith("episode: ")) {
url = url.replace("episode: ", "");
endpointType = "tv";
} else {
return "ERROR";
}
const response = await fetchv2(url);
const html = await response.text();
const idMatch = html.match(/<link rel=['"]shortlink['"] href=['"][^?]+\?p=(\d+)['"]/);
if (!idMatch) return "ID NOT FOUND";
const id = idMatch[1];
const apiUrl = `https://animesdrive.blog/wp-json/dooplayer/v2/${id}/${endpointType}/1`;
const apiResponse = await fetchv2(apiUrl);
const apiData = await apiResponse.json();
console.log(JSON.stringify(apiData));
const embedResponse = await fetchv2(apiData.embed_url);
const embedHtml = await embedResponse.text();
const match = embedHtml.match(/<source\s+src="([^"]+)"\s+type="video\/mp4"/i);
const finalUrl = match ? match[1] : null;
console.log("Final URL: " + finalUrl);
return finalUrl
} catch (err) {
console.error("Error extracting stream URL:"+ err);
return "{ error: err.message }";
}
}
function cleanHtmlSymbols(string) {
if (!string) return "";
return string
.replace(/&#8217;/g, "'")
.replace(/&#8211;/g, "-")
.replace(/&#[0-9]+;/g, "")
.replace(/\r?\n|\r/g, " ")
.replace(/\s+/g, " ")
.trim();
}

View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimesDrive",
"iconUrl": "https://animesdrive.blog/wp-content/uploads/2025/08/cropped-ico-1-192x192.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Portuguese",
"streamType": "mp4",
"quality": "1080p",
"baseUrl": "https://animesdrive.blog/",
"searchBaseUrl": "https://animesdrive.blog/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animesdrive/animesdrive.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true
}

View file

@ -0,0 +1,144 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://animesonline.cloud/?s=" + encodeURIComponent(keyword));
const html = await response.text();
const regex = /<div class="result-item">[\s\S]*?<a href="([^"]+)"[^>]*>\s*<img src="([^"]+)"[^>]*alt="([^"]+)"/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
image: match[2].trim(),
title: cleanHtmlSymbols(match[3].trim())
});
}
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*Sinopse\s*:\s*([\s\S]*?)<\/p>/i;
const match = regex.exec(html);
const description = match ? match[1].trim() : "fuck off you don't need a description";
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 = [];
const regex = /<div class=['"]?numerando['"]?[^>]*>\d+\s*-\s*(\d+)<\/div>[\s\S]*?<a\s+href=['"]([^'"]+)['"][^>]*>/g;
try {
const response = await fetchv2(url);
const html = await response.text();
let match;
while ((match = regex.exec(html)) !== null) {
const episodeNumber = parseInt(match[1], 10);
const href = match[2].trim();
results.push({
href: "episode: " + href,
number: episodeNumber
});
}
if (results.length === 0) {
results.push({
href: "movie: " + url,
number: 1
});
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
let endpointType;
if (url.startsWith("movie: ")) {
url = url.replace("movie: ", "");
endpointType = "movie";
} else if (url.startsWith("episode: ")) {
url = url.replace("episode: ", "");
endpointType = "tv";
} else {
return "ERROR";
}
const response = await fetchv2(url);
const html = await response.text();
const idMatch = html.match(/<link rel=['"]shortlink['"] href=['"][^?]+\?p=(\d+)['"]/);
if (!idMatch) return "ID NOT FOUND";
const id = idMatch[1];
const apiUrl = `https://animesonline.cloud/wp-json/dooplayer/v2/${id}/${endpointType}/1`;
const apiResponse = await fetchv2(apiUrl);
const apiData = await apiResponse.json();
console.log(JSON.stringify(apiData));
if (!apiData.embed_url) return { error: "embed_url not found" };
const embedResponse = await fetchv2(apiData.embed_url);
const embedHtml = await embedResponse.text();
console.log(embedHtml);
const fileMatch = embedHtml.match(/"file":"(https?:\\\/\\\/[^"]+)"/);
if (!fileMatch) return { error: "file not found" };
const fileUrl = fileMatch[1].replace(/\\\//g, "/");
let finalUrl = encodeURI(fileUrl);
console.log(finalUrl);
return finalUrl;
} catch (err) {
console.error("Error extracting stream URL:"+ err);
return "{ error: err.message }";
}
}
function cleanHtmlSymbols(string) {
if (!string) return "";
return string
.replace(/&#8217;/g, "'")
.replace(/&#8211;/g, "-")
.replace(/&#[0-9]+;/g, "")
.replace(/\r?\n|\r/g, " ")
.replace(/\s+/g, " ")
.trim();
}

View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimesOnline",
"iconUrl": "https://animesonline.cloud/wp-content/uploads/2025/06/Icone.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Portuguese",
"streamType": "mp4",
"quality": "1080p",
"baseUrl": "https://animesonline.cloud",
"searchBaseUrl": "https://animesonline.cloud",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animesonline/animesonline.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true
}

132
animesrbija/animesrbija.js Normal file
View file

@ -0,0 +1,132 @@
async function searchResults(keyword) {
const results = [];
const baseUrl = "https://www.animesrbija.com";
const response = await fetchv2("https://www.animesrbija.com/filter?search=" + encodeURIComponent(keyword));
const html = await response.text();
const animeItems = html.match(/<div class="ani-item">.*?<\/h3><\/a><\/div>/gs) || [];
animeItems.forEach(itemHtml => {
const titleMatch = itemHtml.match(/<h3 class="ani-title" title="([^"]+)"/);
const hrefMatch = itemHtml.match(/<a href="([^"]+)"/);
const imgMatch = itemHtml.match(/<noscript>.*?src="([^"]+)".*?<\/noscript>/s);
const title = titleMatch ? titleMatch[1].trim() : '';
const href = hrefMatch ? baseUrl + hrefMatch[1].trim() : '';
let imageUrl = '';
if (imgMatch) {
let srcUrl = imgMatch[1];
if (srcUrl.includes('/_next/image?url=')) {
const urlParam = srcUrl.match(/url=([^&]+)/);
if (urlParam) {
imageUrl = baseUrl + decodeURIComponent(urlParam[1]);
}
} else {
imageUrl = srcUrl.startsWith('http') ? srcUrl : baseUrl + srcUrl;
}
}
if (title && href) {
results.push({
title,
image: imageUrl,
href
});
}
});
console.log(results);
return JSON.stringify(results);
}
async function extractDetails(url) {
const details = [];
const response = await fetchv2(url);
const html = await response.text();
const descriptionMatch = html.match(/<div class="anime-description">([\s\S]*?)<\/div>/);
let description = descriptionMatch ? descriptionMatch[1]
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/<br \/>\n/g, ' ')
.replace(/\s+/g, ' ')
.trim() : '';
const nameMatch = html.match(/<h2 class="anime-name[^>]*>([^<]+)<\/h2>/);
const engNameMatch = html.match(/<h3 class="anime-eng-name">([^<]+)<\/h3>/);
const airdateMatch = html.match(/<span class="bt">Datum:<\/span>([^<]+)/);
let airdate = airdateMatch ? airdateMatch[1].trim() : '';
let name = nameMatch ? nameMatch[1].trim() : '';
let engName = engNameMatch ? engNameMatch[1].trim() : '';
let aliases = name === engName ? 'N/A' : engName;
if (description || airdate) {
details.push({
description: description,
aliases: aliases,
airdate: airdate
});
}
console.log(details);
return JSON.stringify(details);
}
async function extractEpisodes(url) {
const episodes = [];
const response = await fetchv2(url);
const html = await response.text();
const baseUrl = 'https://www.animesrbija.com';
const episodeRegex = /<li\s+class="anime-episode-item">\s*<span\s+class="anime-episode-num">([^<]+)<\/span>\s*<a\s+class="anime-episode-link"\s+href="([^"]+)"/g;
let match;
while ((match = episodeRegex.exec(html)) !== null) {
const episodeText = match[1].trim();
const href = baseUrl + match[2];
let number;
if (episodeText.toLowerCase() === 'film') {
number = 1;
} else {
const numberMatch = episodeText.match(/\d+/);
number = numberMatch ? parseInt(numberMatch[0], 10) : null;
}
episodes.push({
href: href,
number: number
});
}
episodes.reverse();
console.log(JSON.stringify(episodes));
return JSON.stringify(episodes);
}
async function extractStreamUrl(url) {
const response = await fetchv2(url);
const html = await response.text();
const playerRegex = /"player1":\s*"?(!?https?:\/\/[^"\n]+\.m3u8)"/i;
const match = html.match(playerRegex);
if (match) {
let playerUrl = match[1].trim();
if (playerUrl.startsWith('!')) {
playerUrl = playerUrl.substring(1);
}
console.log("URL", playerUrl);
return playerUrl;
} else {
console.log("Link not found");
return null;
}
}

View file

@ -0,0 +1,17 @@
{
"sourceName": "AnimeSRBIJA",
"iconUrl": "https://emojigraph.org/media/joypixels/flag-serbia_1f1f7-1f1f8.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.1",
"language": "Serbian",
"streamType": "m3u8",
"quality": "720p",
"baseUrl": "https://www.animesrbija.com/",
"searchBaseUrl": "https://www.animesrbija.com/filter?search=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animesrbija/animesrbija.js",
"asyncJS": true,
"type": "anime"
}

BIN
animesrbija/iconalt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

171
animesroll/animesroll.js Normal file
View file

@ -0,0 +1,171 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2(
"https://api-search.anroll.net/data?q=" + encodeURIComponent(keyword)
);
const json = await response.json();
if (json.code === 200 && json.data && Array.isArray(json.data)) {
for (const item of json.data) {
if (item.generic_path && item.generic_path.startsWith("/f/")) {
continue;
}
results.push({
title: item.title,
image: `https://www.anroll.net/_next/image?url=${encodeURIComponent(
"https://static.anroll.net/images/animes/capas/" + item.slug + ".jpg"
)}&w=384&q=75`,
href: "https://www.anroll.net" + item.generic_path,
});
}
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([
{
title: "Error",
image: "Error",
href: "Error",
},
]);
}
}
async function extractDetails(url) {
try {
console.log(url);
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/<div class="sinopse">(.*?)<\/div>/s);
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) {
try {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const episodesMatch = html.match(/"episodes"\s*:\s*(\d+)/);
const episodes = episodesMatch ? parseInt(episodesMatch[1], 10) : null;
const idMatch = html.match(/"id_serie"\s*:\s*(\d+)/);
const serieId = idMatch ? parseInt(idMatch[1], 10) : null;
if (!episodes || !serieId) {
console.log("Failed to extract episode count or serie ID.");
return JSON.stringify([{ href: "Error", number: "Error" }]);
}
const totalPages = Math.ceil(episodes / 25);
const pagePromises = [];
for (let page = 1; page <= totalPages; page++) {
const apiUrl = `https://apiv3-prd.anroll.net/animes/${serieId}/episodes?page=${page}&order=desc`;
pagePromises.push(
fetchv2(apiUrl)
.then(response => response.json())
.then(json => {
console.log(`Fetched page ${page} of ${totalPages}...`);
if (json && json.data && Array.isArray(json.data)) {
return json.data.map(ep => ({
href: "https://www.anroll.net/watch/e/" + ep.generate_id,
number: parseInt(ep.n_episodio, 10),
}));
}
return [];
})
.catch(error => {
console.log(`Error fetching page ${page}:`, error);
return [];
})
);
}
const pageResults = await Promise.all(pagePromises);
pageResults.forEach(pageEpisodes => {
results.push(...pageEpisodes);
});
return JSON.stringify(results.reverse());
} catch (err) {
console.log("Error during fetch:", err);
return JSON.stringify([{ href: "Error", number: "Error" }]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const patterns = [
/"streamUrl"\s*:\s*"([^"]+\.m3u8)"/,
/"streamUrl\\?"\s*:\s*\\?"([^"\\]+\.m3u8)\\?"/,
/streamUrl['"]\s*:\s*['"](https?:\/\/[^'"]+\.m3u8)['"]/,
/streamUrl\s*:\s*"([^"]+\.m3u8)"/,
/\\"streamUrl\\":\\"([^"\\]+\.m3u8)\\"/
];
for (const pattern of patterns) {
const match = html.match(pattern);
if (match && match[1]) {
const streamUrl = match[1]
.replace(/\\"/g, '"')
.replace(/\\\//g, '/')
.replace(/\\\\/g, '\\');
return streamUrl;
}
}
const nextDataMatch = html.match(/self\.__next_f\.push\(\[1,"([^"]+)"\]\)/g);
if (nextDataMatch) {
for (const match of nextDataMatch) {
const dataMatch = match.match(/self\.__next_f\.push\(\[1,"([^"]+)"\]\)/);
if (dataMatch && dataMatch[1]) {
const decodedData = dataMatch[1]
.replace(/\\"/g, '"')
.replace(/\\\//g, '/')
.replace(/\\n/g, '\n');
const streamMatch = decodedData.match(/streamUrl['"]\s*:\s*['"](https?:\/\/[^'"]+\.m3u8)['"]/);
if (streamMatch && streamMatch[1]) {
return streamMatch[1];
}
}
}
}
return "https://files.catbox.moe/avolvc.mp4";
} catch (err) {
console.error('Error extracting stream URL:', err);
return "https://files.catbox.moe/avolvc.mp4";
}
}

View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimesRoll",
"iconUrl": "https://cdn.countryflags.com/thumbs/portugal/flag-button-round-250.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Portuguese",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://www.anroll.net/",
"searchBaseUrl": "https://www.anroll.net/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animesroll/animesroll.js",
"type": "animes",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

94
animetoast/animetoast.js Normal file
View file

@ -0,0 +1,94 @@
async function searchResults(keyword) {
const results = [];
const response = await fetchv2(`https://www.animetoast.cc/?s=${keyword}`);
const html = await response.text();
const regex = /<a href="(https:\/\/www\.animetoast\.cc\/[^"]+)"[^>]*title="([^"]+)"[^>]*>[\s\S]*?<img[^>]*src="([^"]+)"[^>]*>[\s\S]*?<\/a>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[2].trim(),
image: match[3].trim(),
href: match[1].trim()
});
}
return JSON.stringify(results);
}
async function extractDetails(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
let description = '';
const descriptionRegex = /<p>(?:<img[^>]*>)?(.*?)<\/p>/s;
const descriptionMatch = html.match(descriptionRegex);
if (descriptionMatch && descriptionMatch[1]) {
description = descriptionMatch[1].trim();
}
results.push({
description: description,
aliases: 'N/A',
airdate: 'N/A'
});
return JSON.stringify(results);
}
async function extractEpisodes(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const tabRegex = /<li[^>]*>\s*<a[^>]*href=["']([^"']+)["'][^>]*>Voe<\/a>\s*<\/li>/g;
const tabMatches = [...html.matchAll(tabRegex)];
if (tabMatches.length > 0) {
const tabHref = tabMatches[0][1].trim();
const tabId = tabHref.startsWith('#') ? tabHref.substring(1) : tabHref;
console.error(tabHref);
const divRegex = new RegExp(`<div id="${tabId}"[^>]*>(.*?)<\/div>`, 's');
const divMatch = html.match(divRegex);
if (divMatch) {
const epRegex = /<a[^>]*href=["']([^"']+)["'][^>]*>[\s\S]*?Ep\.\s*(\d+)\s*<\/a>/g;
const epMatches = [...divMatch[1].matchAll(epRegex)];
results.push(...epMatches.map(match => ({
href: match[1],
number: parseInt(match[2], 10)
})));
}
}
console.error(JSON.stringify(results));
return JSON.stringify(results);
}
async function extractStreamUrl(url) {
const response = await fetchv2(url);
const html = await response.text();
const voeRegex = /<a href="https:\/\/voe\.sx\/([a-zA-Z0-9]+)"[^>]*>/;
const match = html.match(voeRegex);
if (match && match[1]) {
const videoId = match[1];
const streamUrl = `https://kristiesoundsimply.com/e/${videoId}`;
const streamResponse = await fetchv2(streamUrl);
const streamHtml = await streamResponse.text();
const mp4Regex = /'mp4': '([^']+)'/;
const mp4Match = streamHtml.match(mp4Regex);
if (mp4Match && mp4Match[1]) {
const decodedUrl = atob(mp4Match[1]);
return decodedUrl;
}
}
return null;
}

View file

@ -0,0 +1,17 @@
{
"sourceName": "AnimeToast",
"iconUrl": "https://www.animetoast.cc/wp-content/uploads/2018/03/toastfavi-300x300.png",
"author": {
"name": "50/50 & Cufiy",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.2.12",
"language": "German (DUB/SUB)",
"streamType": "MP4",
"quality": "1080p",
"baseUrl": "https://www.animetoast.cc/",
"searchBaseUrl": "https://www.animetoast.cc/?s=the%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animetoast/animetoast_v2.js",
"asyncJS": true,
"type": "anime"
}

956
animetoast/animetoast_v2.js Normal file
View file

@ -0,0 +1,956 @@
async function searchResults(keyword) {
const results = [];
const response = await soraFetch(`https://www.animetoast.cc/?s=${keyword}`);
const html = await response.text();
const regex = /<a href="(https:\/\/www\.animetoast\.cc\/[^"]+)"[^>]*title="([^"]+)"[^>]*>[\s\S]*?<img[^>]*src="([^"]+)"[^>]*>[\s\S]*?<\/a>/g;
let match;
while ((match = regex.exec(html)) !== null) {
let title = match[2].trim();
// if title contains "Ger Dub" or "Ger Sub" or "Eng Dub" or "Eng Sub", remove it and then place it at the beginning of the title
if (title.includes("Ger Dub") || title.includes("Ger Sub") || title.includes("Eng Dub") || title.includes("Eng Sub")) {
let lang = '';
if (title.includes("Ger Dub")) {
lang = 'DUB';
} else if (title.includes("Ger Sub")) {
lang = 'SUB';
} else if (title.includes("Eng Dub")) {
lang = 'EN-DUB';
} else if (title.includes("Eng Sub")) {
lang = 'EN-SUB';
}
title = `${lang} ${title.replace(/(Ger Dub|Ger Sub|Eng Dub|Eng Sub)/, '').trim()}`;
}
results.push({
title: title,
image: match[3].trim(),
href: match[1].trim()
});
}
return JSON.stringify(results);
}
async function extractDetails(url) {
const results = [];
const response = await soraFetch(url);
const html = await response.text();
let description = '';
const descriptionRegex = /<p>(?:<img[^>]*>)?(.*?)<\/p>/s;
const descriptionMatch = html.match(descriptionRegex);
if (descriptionMatch && descriptionMatch[1]) {
description = descriptionMatch[1].trim();
}
results.push({
description: description,
aliases: 'N/A',
airdate: 'N/A'
});
return JSON.stringify(results);
}
async function extractEpisodes(url) {
const results = [];
const response = await soraFetch(url);
const html = await response.text();
let episodes = [];
try {
episodes = await extractEpisodeHosts(html, url);
} catch (error) {
sendLog("Error extracting episodes: " + error.message);
return JSON.stringify([{ error: "Failed to extract episodes" }]);
}
sendLog(JSON.stringify(episodes));
if (episodes.length === 0) {
sendLog("No episodes found");
return JSON.stringify([{ error: "No episodes found" }]);
}
let count = 0;
for (const episodeUrl of episodes) {
count++;
results.push({
href: episodeUrl,
number: count
});
}
sendLog("Extracted " + count + " episodes");
return JSON.stringify(results);
}
async function extractEpisodeHosts(html, url) {
// <li class="active">
// <a data-toggle="tab" href="#multi_link_tab0">Voe</a>
// </li>
const results = {}
const tabRegex = /<a[^>]*data-toggle=["']tab["'][^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/g;
const tabMatches = [...html.matchAll(tabRegex)];
sendLog("Tab matches: " + JSON.stringify(tabMatches));
if (tabMatches.length === 0) {
sendLog("No tab matches found");
return results; // Return empty array if no tabs found
}
if (!tabMatches[0]) {
sendLog("No tab match found");
return results; // Return empty array if no tab match found
}
for (const match of tabMatches) {
const tabHref = match[1].trim();
sendLog("Tab Href: " + tabHref);
const tabId = tabHref.startsWith('#') ? tabHref.substring(1) : tabHref;
sendLog("Tab ID: " + tabId);
const provider = match[2].trim().toLowerCase();
// The issue is here - the regex is capturing only the number part after "multi_link_tab"
// but we need to match the full ID
const divRegex = /<div id="(multi_link_tab[^"]+)"[^>]*>([\s\S]*?)<\/div>/gs;
const divMatch = [...html.matchAll(divRegex)];
// sendLog("Div matches: " + JSON.stringify(divMatch));
// Find the matching div by comparing the full ID
const matchingDiv = divMatch.filter(div => div[1] === tabId);
// sendLog("Matching Div: " + JSON.stringify(matchingDiv));
if (!matchingDiv || matchingDiv.length === 0) {
sendLog("No div match found for tab ID: " + tabId);
continue; // Skip if no matching div found
}
const epRegex = /<a[^>]*href=["']([^"']+)["'][^>]*>[\s\S]*?Ep\.\s*(\d+)\s*<\/a>/g;
const epMatches = [...matchingDiv[0][2].matchAll(epRegex)];
// https://www.animetoast.cc/xxx/?link=0
if (!results[provider]) {
results[provider] = [];
}
results[provider].push(...epMatches.map(match => {
const url = match[1];
const linkMatch = url.match(/[?&]link=(\d+)/);
return linkMatch ? linkMatch[1] : null;
}).filter(Boolean));
sendLog(`Extracted ${epMatches.length} episodes for provider ${provider}`);
}
let newResults = [];
// build new urls out of results like this:
/*
https://www.animetoast.cc/xxx/#voe=0,doodstream=12,playn=24,fmoon=36,mp4upload=48
https://www.animetoast.cc/xxx/#voe=1,doodstream=13,playn=25,fmoon=37,mp4upload=49
...
*/
// loop through results and build new urls
const maxLength = Math.max(...Object.values(results).map(arr => arr.length));
for (let i = 0; i < maxLength; i++) {
let newUrl = url.split('#')[0] + '#';
for (const [provider, links] of Object.entries(results)) {
if (links[i]) {
newUrl += `${provider}=${links[i]},`;
}
}
newUrl = newUrl.slice(0, -1); // Remove trailing comma
newResults.push(newUrl);
}
return newResults;
}
async function extractStreamUrl(url) {
try {
// now we need to extract the providers from the url
// e.g. https://www.animetoast.cc/sword-art-online-alternative-gun-gale-online-ii-ger-dub/#voe=2,doodstream=14,playn=26,fmoon=38,mp4upload=50
const baseUrl = url.split('#')[0];
const providersString = url.split('#')[1];
if (!providersString) {
sendLog("No providers found in URL: " + url);
return JSON.stringify([{ provider: "Error", link: "No providers found in URL" }]);
}
sendLog("Base URL: " + baseUrl);
sendLog("Providers String: " + providersString);
const providersArray = providersString.split(',');
sendLog("Providers Array: " + JSON.stringify(providersArray));
// Create a providers object from the providersArray
let tempProviders = {};
providersArray.forEach(provider => {
const [name, id] = provider.split('=');
tempProviders[name] = id;
});
if (!_0xCheck()) return 'https://files.catbox.moe/avolvc.mp4';
// rename fmoon to filemoon
if (tempProviders['fmoon']) {
tempProviders['filemoon'] = tempProviders['fmoon'];
delete tempProviders['fmoon'];
}
if (tempProviders['doodstream']) {
delete tempProviders['doodstream']; // Idk why, but it just crashes the app
}
// remove any providers that are not in the list of available providers
for (const provider in tempProviders) {
if (eval(`typeof ${provider}Extractor`) !== "function") {
sendLog(`Extractor for provider ${provider} is not defined, removing...`);
delete tempProviders[provider];
}
}
let providers = await extractProviders(tempProviders, baseUrl);
sendLog("Extracted Providers: " + JSON.stringify(providers));
if (Object.keys(providers).length === 0) {
sendLog("No valid providers found, returning error");
return JSON.stringify([{ provider: "Error", link: "No valid providers found" }]);
}
// Multiple extractor (recommended)
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: "" }]);
}
} catch (error) {
sendLog("Fetch error:", error);
return null;
}
}
async function extractProviders(tempProviders, baseUrl) {
let providers = {};
for (const [name, id] of Object.entries(tempProviders)) {
try {
const response = await fetch(`${baseUrl}?link=${id}`);
const data = response.text ? await response.text() : response;
// get the iframe src from the data
const iframeRegex = /<iframe[^>]+src="([^"]+)"[^>]*>/;
const iframeMatch = data.match(iframeRegex);
if (iframeMatch && iframeMatch[1]) {
const iframeSrc = iframeMatch[1];
// check if the iframeSrc is a valid URL
if (iframeSrc.startsWith("http") || iframeSrc.startsWith("https")) {
providers[iframeSrc] = name; // Use the name as the key
sendLog(`Provider ${name} found: ${iframeSrc}`);
}
} else {
// get the href from:
/*<div id="player-embed" >
<a href="https://voe.sx/" target="_blank">
*/
// get the div with id player-embed
const divRegex = /<div id="player-embed"[^>]*>\s*<a href="([^"]+)"[^>]*>/;
const divMatch = data.match(divRegex);
if (divMatch && divMatch[1]) {
const href = divMatch[1];
// check if the href is a valid URL
if (href.startsWith("http") || href.startsWith("https")) {
providers[href] = name; // Use the name as the key
sendLog(`Provider ${name} found: ${href}`);
}
} else {
sendLog(`No iframe or div found for provider ${name}, skipping...`);
continue; // Skip if no iframe or div found
}
}
} catch (error) {
sendLog("Error fetching provider " + name + ": " + error);
}
}
return providers;
}
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))(_)}
// Debugging function to send logs
async function sendLog(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.3} */
/**
* @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-07-23 17:47:48
* @version 1.1.3
* @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;
}
} 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;
}
const streamUrl = await extractStreamUrlByProvider(url, provider);
// check if streamUrl is not null, a string, and starts with http or https
// 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";
}
// 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 "filemoon":
try {
return await filemoonExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from filemoon:", 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 "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;
}
/* --- 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;
}
}
/* --- 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;
}
}
/* --- 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;
}
}
}
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;
}
}
/* {GE END} */

122
animeunity/animeunity.js Normal file
View file

@ -0,0 +1,122 @@
async function searchResults(keyword) {
const response = await fetchv2(
`https://www.animeunity.so/archivio?title=${keyword}`
);
const html = await response.text();
const regex = /<archivio[^>]*records="([^"]*)"/;
const match = regex.exec(html);
if (!match || !match[1]) {
return { results: [] };
}
const items = JSON.parse(match[1].replaceAll(`&quot;`, `"`));
const results =
items.map((item) => ({
title: item.title ?? item.title_eng,
image: item.imageurl,
href: `https://www.animeunity.so/info_api/${item.id}`,
})) || [];
return JSON.stringify(results);
}
async function extractDetails(url) {
const response = await fetchv2(url);
const json = JSON.parse(await response.text());
return JSON.stringify([
{
description: json.plot,
aliases: "N/A",
airdate: json.date,
},
]);
}
async function extractEpisodes(url) {
try {
const episodes = [];
const apiResponse = await fetchv2(url);
const apiJson = JSON.parse(await apiResponse.text());
const slug = apiJson.slug;
const idAnime = apiJson.id;
if (!slug) {
console.log("No slug found in API response");
return episodes;
}
const pageResponse = await fetchv2(
`https://www.animeunity.so/anime/${idAnime}-${slug}`
);
const html = await pageResponse.text();
const videoPlayerRegex =
/<video-player[^>]*anime="([^"]*)"[^>]*episodes="([^"]*)"/;
const videoPlayerMatch = html.match(videoPlayerRegex);
if (!videoPlayerMatch) {
console.log("No video-player tag found");
return episodes;
}
const decodeHtml = (str) =>
str.replace(/&quot;/g, '"').replace(/\\\//g, "/");
const animeJsonStr = decodeHtml(videoPlayerMatch[1]);
const episodesJsonStr = decodeHtml(videoPlayerMatch[2]);
const animeData = JSON.parse(animeJsonStr);
const episodesData = JSON.parse(episodesJsonStr);
episodesData.forEach((episode) => {
episodes.push({
href: `https://animeunity.so/anime/${idAnime}-${slug}/${episode.id}`,
number: parseInt(episode.number),
});
});
return JSON.stringify(episodes);
} catch (error) {
console.log("Error extracting episodes:", error);
return [];
}
}
async function extractStreamUrl(url) {
try {
const response1 = await fetchv2(url);
const html = await response1.text();
const vixcloudMatch = html.match(
/embed_url="(https:\/\/vixcloud\.co\/embed\/\d+\?[^"]+)"/
);
if (!vixcloudMatch) {
console.log("No vixcloud.co URL found in the HTML.");
return null;
}
let vixcloudUrl = vixcloudMatch[1];
vixcloudUrl = vixcloudUrl.replace(/&amp;/g, "&");
const response = await fetch(vixcloudUrl);
const downloadUrlMatch = response.match(
/window\.downloadUrl\s*=\s*['"]([^'"]+)['"]/
);
if (!downloadUrlMatch) {
console.log("No downloadUrl found in the response.");
return null;
}
const downloadURL = downloadUrlMatch[1];
console.log(downloadURL);
return downloadURL;
} catch (error) {
console.log("Fetch error:", error);
return null;
}
}

View file

@ -0,0 +1,17 @@
{
"sourceName": "AnimeUnity",
"iconUrl": "https://github.com/cottonable/Ryu-preservation/blob/2f10226aa087154974a70c1ec78aa83a47daced9/Ryu/Assets.xcassets/Sources/AnimeUnity.imageset/apple-touch-icon.jpg?raw=true",
"author": {
"name": "sobet",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRQPQ1qIiALbM3xDWGsuJzu6ItaQGwb9ICRRw&s"
},
"version": "1.0.4",
"language": "Italian",
"streamType": "mp4",
"quality": "720p",
"baseUrl": "https://animeunity.so",
"searchBaseUrl": "https://www.animeunity.so/archivio?title=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animeunity/animeunity.js",
"asyncJS": true,
"type": "anime"
}

BIN
animeunity/iconalt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

239
animeworld/animeworld.js Normal file
View file

@ -0,0 +1,239 @@
extractStreamUrl(
"https://animeworld.ac/play/seishun-buta-yarou-wa-randoseru-girl-no-yume-wo-minai.UZycE/w-YYif"
);
async function searchResults(keyword) {
const results = [];
const baseUrl = "https://animeworld.ac";
try {
const response = await soraFetch(
`${baseUrl}/search?keyword=${encodeURIComponent(keyword)}`
);
const html = await response.text();
const filmListRegex =
/<div class="film-list">([\s\S]*?)<div class="clearfix"><\/div>\s*<\/div>/;
const filmListMatch = html.match(filmListRegex);
if (!filmListMatch) {
return JSON.stringify(results);
}
const filmListContent = filmListMatch[1];
const itemRegex = /<div class="item">[\s\S]*?<\/div>[\s]*<\/div>/g;
const items = filmListContent.match(itemRegex) || [];
items.forEach((itemHtml) => {
const imgMatch = itemHtml.match(/src="([^"]+)"/);
let imageUrl = imgMatch ? imgMatch[1] : "";
const titleMatch = itemHtml.match(/class="name">([^<]+)</);
const title = titleMatch ? titleMatch[1] : "";
const hrefMatch = itemHtml.match(/href="([^"]+)"/);
let href = hrefMatch ? hrefMatch[1] : "";
if (imageUrl && title && href) {
if (!imageUrl.startsWith("https")) {
if (imageUrl.startsWith("/")) {
imageUrl = baseUrl + imageUrl;
} else {
imageUrl = baseUrl + "/" + href;
}
}
if (!href.startsWith("https")) {
if (href.startsWith("/")) {
href = baseUrl + href;
} else {
href = baseUrl + "/" + href;
}
}
results.push({
title: title.trim(),
image: imageUrl,
href: href,
});
}
});
console.log(JSON.stringify(results));
return JSON.stringify(results);
} catch (error) {
console.log("Search error:", error);
return JSON.stringify([]);
}
}
async function extractDetails(url) {
try {
const response = await soraFetch(url);
const html = await response.text();
const details = [];
const descriptionMatch = html.match(/<div class="desc">([\s\S]*?)<\/div>/);
let description = descriptionMatch ? descriptionMatch[1] : "";
const aliasesMatch = html.match(/<h2 class="title" data-jtitle="([^"]+)">/);
let aliases = aliasesMatch ? aliasesMatch[1] : "";
const airdateMatch = html.match(
/<dt>Data di Uscita:<\/dt>\s*<dd>([^<]+)<\/dd>/
);
let airdate = airdateMatch ? airdateMatch[1] : "";
if (description && aliases && airdate) {
details.push({
description: description,
aliases: aliases,
airdate: airdate,
});
}
console.log(JSON.stringify(details));
return JSON.stringify(details);
} catch (error) {
console.log("Details error:", error);
return JSON.stringify([]);
}
}
async function extractEpisodes(url) {
try {
const response = await soraFetch(url);
const html = await response.text();
const episodes = [];
const baseUrl = "https://animeworld.ac";
const serverActiveRegex =
/<div class="server active"[^>]*>([\s\S]*?)<\/ul>\s*<\/div>/;
const serverActiveMatch = html.match(serverActiveRegex);
if (!serverActiveMatch) {
return JSON.stringify(episodes);
}
const serverActiveContent = serverActiveMatch[1];
const episodeRegex =
/<li class="episode">\s*<a[^>]*?href="([^"]+)"[^>]*?>([^<]+)<\/a>/g;
let match;
while ((match = episodeRegex.exec(serverActiveContent)) !== null) {
let href = match[1];
const number = parseInt(match[2], 10);
if (!href.startsWith("https")) {
if (href.startsWith("/")) {
href = baseUrl + href;
} else {
href = baseUrl + "/" + href;
}
}
episodes.push({
href: href,
number: number,
});
}
console.log(JSON.stringify(episodes));
return JSON.stringify(episodes);
} catch (error) {
console.log("Episodes error:", error);
return JSON.stringify([]);
}
}
async function extractStreamUrl(url) {
if (!_0xCheck()) return "https://files.catbox.moe/avolvc.mp4";
try {
const response = await soraFetch(url);
const html = await response.text();
const idRegex = /<a[^>]+href="([^"]+)"[^>]*id="alternativeDownloadLink"/;
const match = html.match(idRegex);
return match ? match[1] : null;
} catch (error) {
console.log("Stream URL error:", error);
return "https://files.catbox.moe/avolvc.mp4";
}
}
async function soraFetch(
url,
options = { headers: {}, method: "GET", body: null, encoding: "utf-8" }
) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? "GET",
options.body ?? null,
true,
options.encoding ?? "utf-8"
);
} catch (e) {
try {
return await fetch(url, options);
} catch (error) {
return null;
}
}
}
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
))(_);
}

View file

@ -0,0 +1,17 @@
{
"sourceName": "AnimeWorld",
"iconUrl": "https://raw.githubusercontent.com/cranci1/Ryu/d48d716ec6c5ef9ae7b3711c117f920b0c7eb1ce/Ryu/Assets.xcassets/Sources/AnimeWorld.imageset/animeworld.png",
"author": {
"name": "sobet",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRQPQ1qIiALbM3xDWGsuJzu6ItaQGwb9ICRRw&s"
},
"version": "1.0.4",
"language": "Italian",
"streamType": "MP4",
"quality": "1080p",
"baseUrl": "https://animeworld.ac/",
"searchBaseUrl": "https://www.animeworld.ac/search?keyword=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animeworld/animeworld.js",
"type": "anime",
"asyncJS": true
}

BIN
animeworld/iconalt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

133
animexin/animexin.js Normal file
View file

@ -0,0 +1,133 @@
async function searchResults(keyword) {
const results = [];
const response = await fetchv2(`https://animexin.dev/?s=${keyword}`);
const html = await response.text();
const regex = /<article class="bs"[^>]*>.*?<a href="([^"]+)"[^>]*>.*?<img src="([^"]+)"[^>]*>.*?<h2[^>]*>(.*?)<\/h2>/gs;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3].trim(),
image: match[2].trim(),
href: match[1].trim()
});
}
return JSON.stringify(results);
}
async function extractDetails(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/<div class="entry-content"[^>]*>([\s\S]*?)<\/div>/);
let description = "N/A";
if (match) {
description = match[1]
.replace(/<[^>]+>/g, '')
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&amp;/g, "&")
.trim();
}
results.push({
description: description,
aliases: 'N/A',
airdate: 'N/A'
});
return JSON.stringify(results);
}
async function extractEpisodes(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const regex = /<a href="([^"]+)">\s*<div class="epl-num">([\d.]+)<\/div>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
number: parseInt(match[2], 10)
});
}
results.reverse();
return JSON.stringify(results);
}
async function extractStreamUrl(url) {
const response = await fetchv2(url);
const html = await response.text();
const optionRegex = /<option value="([^"]+)"[^>]*>\s*([\s\S]*?)\s*<\/option>/g;
const allowedLabels = ["Hardsub English Dailymotion", "Hardsub Indonesia Dailymotion"];
const videoOptions = [];
let match;
while ((match = optionRegex.exec(html)) !== null) {
const base64 = match[1];
const label = match[2].trim();
if (!base64 || !allowedLabels.includes(label)) continue;
const decodedValue = atob(base64);
if (!decodedValue) continue;
const idMatch = decodedValue.match(/dailymotion\.com\/embed\/video\/([a-zA-Z0-9]+)/);
const videoId = idMatch ? idMatch[1] : null;
if (!videoId) continue;
videoOptions.push({ videoId, label: label.replace(" Dailymotion", "") });
}
async function getBestHls(hlsUrl) {
try {
const res = await fetchv2(hlsUrl);
const text = await res.text();
const regex = /#EXT-X-STREAM-INF:.*RESOLUTION=(\d+)x(\d+).*?\n(https?:\/\/[^\n]+)/g;
let match;
const streams = [];
while ((match = regex.exec(text)) !== null) {
const width = parseInt(match[1]);
const height = parseInt(match[2]);
const url = match[3];
streams.push({ width, height, url });
}
if (streams.length === 0) return hlsUrl;
streams.sort((a, b) => b.height - a.height);
return streams[0].url;
} catch (err) {
return hlsUrl;
}
}
const streams = [];
for (const option of videoOptions) {
try {
const metaRes = await fetchv2(`https://www.dailymotion.com/player/metadata/video/${option.videoId}`);
const metaJson = await metaRes.json();
const hlsLink = metaJson.qualities?.auto?.[0]?.url;
if (!hlsLink) continue;
const bestHls = await getBestHls(hlsLink);
streams.push(option.label.toUpperCase().startsWith("HARDSUB ENGLISH") ? "HardSub English" : "HardSub Indonesian");
streams.push(bestHls);
} catch (err) {
continue;
}
}
return JSON.stringify({
streams: streams,
subtitles: ""
});
}

19
animexin/animexin.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimeXin",
"iconUrl": "https://animexin.dev/wp-content/uploads/2020/06/cropped-index-192x192.jpg",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "English/Indonesian",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animexin.dev/",
"searchBaseUrl": "https://animexin.dev/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animexin/animexin.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

315
animeytx/animeytx.js Normal file
View file

@ -0,0 +1,315 @@
function cleanTitle(title) {
return title
.replace(/&#8217;/g, "'")
.replace(/&#8211;/g, "-")
.replace(/&#[0-9]+;/g, "");
}
async function searchResults(keyword) {
const results = [];
const response = await fetchv2(`https://animeytx.net/?s=${keyword}`);
const html = await response.text();
const regex = /<article class="bs"[^>]*>[\s\S]*?<a href="([^"]+)"[^>]*>[\s\S]*?(?:data-src|src)="([^"]+)"[^>]*>[\s\S]*?<h2[^>]*>(.*?)<\/h2>/gs;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: cleanTitle(match[3].trim()),
image: match[2].trim(),
href: match[1].trim()
});
}
return JSON.stringify(results);
}
async function extractDetails(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/<div class="entry-content"[^>]*>([\s\S]*?)<\/div>/);
let description = "N/A";
if (match) {
description = match[1]
.replace(/<[^>]+>/g, '')
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&amp;/g, "&")
.trim();
}
results.push({
description: description,
aliases: 'N/A',
airdate: 'N/A'
});
return JSON.stringify(results);
}
async function extractEpisodes(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const regex = /<li data-index="\d+">[\s\S]*?<a href="([^"]+)">/g;
let match;
let count = 1;
while ((match = regex.exec(html)) !== null) {
results.push({
href: encodeURI(match[1].trim()),
number: count
});
count++;
}
const total = results.length;
results.forEach((ep, i) => {
ep.number = total - i;
});
return JSON.stringify(results.reverse());
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(`https://passthrough-worker.simplepostrequest.workers.dev/?simple=${encodeURIComponent(url)}`);
const html = await response.text();
// Try primary method first
let iframeMatch = html.match(/<iframe[^>]+data-src="([^"]+)"/);
let filemoonUrl;
if (iframeMatch) {
const iframeUrl = iframeMatch[1];
const valueMatch = iframeUrl.match(/value=([^&]+)/);
if (valueMatch) {
const valueId = valueMatch[1];
const containerResponse = await fetchv2(`https://mytsumi.com/multiplayer/contenedor.php?id=${valueId}`);
const containerHtml = await containerResponse.text();
const filemoonMatch = containerHtml.match(/"tab_name":"Moon","url":"([^"]+)"/);
if (filemoonMatch) filemoonUrl = filemoonMatch[1].replace(/\\\//g, '/');
}
}
// Fallback if no Filemoon found
if (!filemoonUrl) {
const optionMatch = html.match(/<option[^>]+value="([^"]+)"[^>]*>\s*Moon\s*<\/option>/);
if (!optionMatch) return "https://error.org/";
const decoded = atob(optionMatch[1]);
const srcMatch = decoded.match(/src="([^"]+)"/);
if (!srcMatch) return "https://error.org/";
const fallbackUrl = srcMatch[1];
const fallbackResponse = await fetchv2(fallbackUrl);
const fallbackHtml = await fallbackResponse.text();
const filemoonMatch2 = fallbackHtml.match(/<a href="(https:\/\/filemoon\.to\/e\/[^"]+)">/);
if (!filemoonMatch2) return "https://error.org/";
filemoonUrl = filemoonMatch2[1];
}
console.log(filemoonUrl);
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": url
};
const finalResponse = await fetchv2(filemoonUrl, headers);
const finalHtml = await finalResponse.text();
const streamUrl = await filemoonExtractor(finalHtml, filemoonUrl);
if (streamUrl) return streamUrl;
return "filemoonUrl";
} 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 = /<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;
}
}
/* 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<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) {
return null;
}
}
}
/* REMOVE_END */
/* SCHEME END */

19
animeytx/animeytx.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "AnimeYTX",
"iconUrl": "https://i1.wp.com/animeytx.net/wp-content/uploads/2024/09/cropped-hgn584ghj45-1-192x192.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Spanish (DUB/SUB)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animeytx.net/",
"searchBaseUrl": "https://animeytx.net/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/animeytx/animeytx.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

108
anitube/anitube.js Normal file
View file

@ -0,0 +1,108 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://www.anitube.news/?s=" + keyword);
const html = await response.text();
const regex = /<div class="aniItem">\s*<a href="([^"]+)"[^>]*>[\s\S]*?<img src="([^"]+)"[^>]*>[\s\S]*?<div class="aniItemNome">\s*([^<]+)\s*<\/div>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
image: match[2].trim(),
title: match[3].trim()
});
}
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 = /<div id="sinopse2">(.*?)<\/div>/s;
const match = regex.exec(html);
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) {
try {
const response = await fetchv2(url);
const html = await response.text();
const episodes = [];
const epRegex = /<a href="([^"]+)" title="([^"]+)">/g;
let match;
let counter = 1;
while ((match = epRegex.exec(html)) !== null) {
const href = match[1].trim();
if (href === "https://www.anitube.news") continue;
const title = match[2];
const numMatch = /Episódio\s+(\d+)/.exec(title);
const number = numMatch ? parseInt(numMatch[1], 10) : counter++;
episodes.push({
number: number,
href: href
});
}
if (episodes.length > 1 && episodes[0].number > episodes[1].number) {
episodes.reverse();
}
return JSON.stringify(episodes);
} catch (err) {
return JSON.stringify([{
number: -1,
href: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const regex = /src="https:\/\/api\.anivideo\.net\/videohls\.php\?d=([^"&]+\.m3u8)[^"]*"/;
const match = regex.exec(html);
if (!match) {
return "Error: stream not found";
}
const hlsUrl = decodeURIComponent(match[1]);
return hlsUrl;
} catch (err) {
return "Error: " + err.message;
}
}

19
anitube/anitube.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "AniTube",
"iconUrl": "https://www.anitube.news/wp-content/uploads/cropped-Favicon6-192x192.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Portuguese",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://www.anitube.news/",
"searchBaseUrl": "https://www.anitube.news/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/anitube/anitube.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

View file

@ -0,0 +1,18 @@
{
"sourceName": "AniWorld (ENG SUB)",
"iconUrl": "https://gitlab.com/50n50/sources/-/raw/main/aniworld/aniworld.png",
"author": {
"name": "Hamzo & Cufiy",
"icon": "https://cdn.discordapp.com/avatars/623644371819954226/591ecab10b0b4535e859bb0b9bbe62e5?size=1024"
},
"version": "0.2.7",
"language": "English (SUB)",
"streamType": "HLS",
"quality": "720p",
"baseUrl": "https://google.com",
"searchBaseUrl": "https://aniworld.to/ajax/seriesSearch?keyword=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/aniworld/v2/AniWorldEngSub_v2.js",
"asyncJS": true,
"streamAsyncJS": false,
"type": "anime"
}

View file

@ -0,0 +1,17 @@
{
"sourceName": "AniWorld (fixed)",
"iconUrl": "https://gitlab.com/50n50/sources/-/raw/main/aniworld/aniworld.png",
"author": {
"name": "Cufiy",
"icon": "https://cdn.discordapp.com/avatars/623644371819954226/591ecab10b0b4535e859bb0b9bbe62e5?size=1024"
},
"version": "0.2.52",
"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-sources2/aniworld/v2/AniWorldGerDub_v2.js",
"asyncJS": true,
"type": "anime"
}

View file

@ -0,0 +1,18 @@
{
"sourceName": "AniWorld (GER DUB)",
"iconUrl": "https://gitlab.com/50n50/sources/-/raw/main/aniworld/aniworld.png",
"author": {
"name": "Hamzo & Cufiy",
"icon": "https://cdn.discordapp.com/avatars/623644371819954226/591ecab10b0b4535e859bb0b9bbe62e5?size=1024"
},
"version": "0.2.7",
"language": "German (DUB)",
"streamType": "HLS",
"quality": "720p",
"baseUrl": "https://google.com",
"searchBaseUrl": "https://aniworld.to/ajax/seriesSearch?keyword=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/aniworld/v2/AniWorldGerDub_v2.js",
"asyncJS": true,
"streamAsyncJS": false,
"type": "anime"
}

View file

@ -0,0 +1,18 @@
{
"sourceName": "AniWorld (GER SUB)",
"iconUrl": "https://gitlab.com/50n50/sources/-/raw/main/aniworld/aniworld.png",
"author": {
"name": "Hamzo & Cufiy",
"icon": "https://cdn.discordapp.com/avatars/623644371819954226/591ecab10b0b4535e859bb0b9bbe62e5?size=1024"
},
"version": "0.2.7",
"language": "German (SUB)",
"streamType": "HLS",
"quality": "720p",
"baseUrl": "https://google.com",
"searchBaseUrl": "https://aniworld.to/ajax/seriesSearch?keyword=%s",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/aniworld/v2/AniWorldGerSub_v2.js",
"asyncJS": true,
"streamAsyncJS": false,
"type": "anime"
}

BIN
aniworld/aniworld.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
aniworld/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
aniworld/iconSub.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
aniworld/iconSubEng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -0,0 +1,611 @@
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// 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);
const data = await JSON.parse(responseText);
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) {
console.log("Fetch error:" + error);
return JSON.stringify([{ title: "Error", image: "", href: "" }]);
}
}
async function extractDetails(url) {
try {
const fetchUrl = `${url}`;
const text = await fetch(fetchUrl);
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) {
console.log("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 html = await fetch(fetchUrl);
const finishedList = [];
const seasonLinks = getSeasonLinks(html);
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) {
console.log("Fetch error:" + error);
return JSON.stringify([{ number: "0", href: "" }]);
}
}
async function extractStreamUrl(url) {
try {
const baseUrl = "https://aniworld.to";
const fetchUrl = `${url}`;
const text = await fetch(fetchUrl);
const finishedList = [];
const languageList = getAvailableLanguages(text);
const videoLinks = getVideoLinks(text);
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
const selectedHoster = selectHoster(finishedList);
const provider = selectedHoster.provider;
const providerLink = selectedHoster.href;
if (provider === "Error") {
console.log("No video found");
return JSON.stringify([{ provider: "Error", link: "" }]);
}
console.log("Selected provider: " + provider);
console.log("Selected link: " + providerLink);
const videoPage = await fetch(providerLink);
console.log("Video Page: " + videoPage.length);
const winLocRegex = /window\.location\.href\s*=\s*['"]([^'"]+)['"]/;
const winLocMatch = winLocRegex.exec(videoPage);
let winLocUrl = null;
if (!winLocMatch) {
winLocUrl = providerLink;
} else {
winLocUrl = winLocMatch[1];
}
const hlsSourceResponse = await fetch(winLocUrl);
const hlsSourcePage =
typeof hlsSourceResponse === "object"
? await hlsSourceResponse.text()
: await hlsSourceResponse;
console.log("Provider: " + provider);
console.log("URL: " + winLocUrl);
console.log("HLS Source Page: " + hlsSourcePage.length);
switch (provider) {
case "VOE":
try {
const voeJson = voeExtractor(hlsSourcePage);
return voeJson?.source || JSON.stringify([{ provider: "Error", link: "" }]);
} catch (error) {
console.log("VOE extractor error: " + error);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
case "SpeedFiles":
try {
const speedfilesUrl = await speedfilesExtractor(hlsSourcePage);
return speedfilesUrl || JSON.stringify([{ provider: "Error", link: "" }]);
} catch (error) {
console.log("Speedfiles extractor error: " + error);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
case "Vidmoly":
try {
const vidmolyUrl = vidmolyExtractor(hlsSourcePage);
return vidmolyUrl || JSON.stringify([{ provider: "Error", link: "" }]);
} catch (error) {
console.log("Vidmoly extractor error: " + error);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
default:
console.log("Unsupported provider:", provider);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
// END OF VOE EXTRACTOR
// Extract the sources variable and decode the hls value from base64
const sourcesRegex = /var\s+sources\s*=\s*({[^}]+})/;
const sourcesMatch = sourcesRegex.exec(hlsSourcePage);
let sourcesString = sourcesMatch
? sourcesMatch[1].replace(/'/g, '"')
: null;
return sourcesString;
} catch (error) {
console.log("ExtractStreamUrl error:" + error);
return JSON.stringify([{ provider: "Error1", link: "" }]);
}
}
function selectHoster(finishedList) {
let firstVideo = null;
let provider = null;
// Define the preferred providers and languages
const providerList = ["Vidmoly", "SpeedFiles", "VOE"];
const languageList = ["mit Untertitel Englisch", "mit Untertitel Deutsch"];
for (const providerName of providerList) {
for (const language of languageList) {
const video = finishedList.find(
(video) => video.provider === providerName && video.language === language
);
if (video) {
provider = providerName;
firstVideo = video;
break;
}
}
if (firstVideo) break;
}
// Default to the first video if no match is found
if (!firstVideo) {
firstVideo = finishedList[0];
}
if (firstVideo) {
return {
provider: provider,
href: firstVideo.href,
};
} else {
console.log("No video found");
return {
provider: "Error",
href: "https://error.org",
};
}
}
//Thanks to Ibro and Cufiy
async function vidmolyExtractor(html) {
console.log("Vidmoly extractor");
console.log(html);
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) {
console.log("Vidmoly extractor: Match found");
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];
console.log("Vidmoly extractor: Stream URL: " + streamUrl);
const responseTwo = await fetchv2(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
console.log(m3u8Match ? m3u8Match[1] : null);
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;
}
}
// Thanks to Cufiy
function speedfilesExtractor(sourcePageHtml) {
// get var _0x5opu234 = "THIS_IS_AN_ENCODED_STRING"
const REGEX = /var\s+_0x5opu234\s*=\s*"([^"]+)"/;
const match = sourcePageHtml.match(REGEX);
if (match == null || match[1] == null) {
console.log("Could not extract from Speedfiles source");
return null;
}
const encodedString = match[1];
console.log("Encoded String:" + encodedString);
// Step 1: Base64 decode the initial string
let step1 = atob(encodedString);
console.log("Step 1:" + step1);
// Step 2: Swap character cases and reverse
let step2 = step1
.split("")
.map((c) =>
/[a-zA-Z]/.test(c)
? c === c.toLowerCase()
? c.toUpperCase()
: c.toLowerCase()
: c
)
.join("");
console.log("Step 2:" + step2);
let step3 = step2.split("").reverse().join("");
console.log("Step 3:" + step3);
// Step 3: Base64 decode again and reverse
let step4 = atob(step3);
console.log("Step 4:" + step4);
let step5 = step4.split("").reverse().join("");
console.log("Step 5:" + step5);
// Step 4: Hex decode pairs
let step6 = "";
for (let i = 0; i < step5.length; i += 2) {
step6 += String.fromCharCode(parseInt(step5.substr(i, 2), 16));
}
console.log("Step 6:" + step6);
// Step 5: Subtract 3 from character codes
let step7 = step6
.split("")
.map((c) => String.fromCharCode(c.charCodeAt(0) - 3))
.join("");
console.log("Step 7:" + step7);
// Step 6: Final case swap, reverse, and Base64 decode
let step8 = step7
.split("")
.map((c) =>
/[a-zA-Z]/.test(c)
? c === c.toLowerCase()
? c.toUpperCase()
: c.toLowerCase()
: c
)
.join("");
console.log("Step 8:" + step8);
let step9 = step8.split("").reverse().join("");
console.log("Step 9:" + step9);
// return atob(step9);
let decodedUrl = atob(step9);
console.log("Decoded URL:" + decodedUrl);
return decodedUrl;
}
// Thanks to https://github.com/ShadeOfChaos
/**
* Extracts a JSON object from the given source page by finding the
* encoded string marked with the regex /MKGMa="([\s\S]+?)"/ and
* decoding it using the voeDecoder function.
* @param {string} sourcePageHtml - The source page to be parsed.
* @returns {object|null} The extracted JSON object if successful,
* otherwise null.
*/
function voeExtractor(sourcePageHtml) {
const REGEX = /MKGMa="([\s\S]+?)"/;
const match = sourcePageHtml.match(REGEX);
if (match == null || match[1] == null) {
console.log("Could not extract from Voe source");
return null;
}
const encodedString = match[1];
const decodedJson = voeDecoder(encodedString);
return decodedJson;
}
/**
* Decodes the given MKGMa string, which is a custom encoded string used
* by VOE. This function applies the following steps to the input string to
* decode it:
* 1. Apply ROT13 to each alphabetical character in the string.
* 2. Remove all underscores from the string.
* 3. Decode the string using the Base64 algorithm.
* 4. Apply a character shift of 0x3 to each character in the decoded string.
* 5. Reverse the order of the characters in the shifted string.
* 6. Decode the reversed string using the Base64 algorithm again.
* 7. Parse the decoded string as JSON.
* @param {string} MKGMa_String - The input string to be decoded.
* @returns {object} The decoded JSON object.
*/
function voeDecoder(MKGMa_String) {
let ROT13String = ROT13(MKGMa_String);
let sanitizedString = voeSanitizer(ROT13String);
let UnderscoreRemoved = sanitizedString.split("_").join("");
let base64DecodedString = atob(UnderscoreRemoved);
let charShiftedString = shiftCharacter(base64DecodedString, 0x3);
let reversedString = charShiftedString.split("").reverse().join("");
let base64DecodedStringAgain = atob(reversedString);
let decodedJson;
try {
decodedJson = JSON.parse(base64DecodedStringAgain);
} catch (error) {
console.log("JSON parse error:", error);
decodedJson = {};
}
return decodedJson;
}
/**
* Encodes a given string using the ROT13 cipher, which shifts each letter
* 13 places forward in the alphabet. Only alphabetical characters are
* transformed; other characters remain unchanged.
*
* @param {string} string - The input string to be encoded.
* @returns {string} The encoded string with ROT13 applied.
*/
function ROT13(string) {
let ROT13String = "";
for (let i = 0; i < string.length; i++) {
let currentCharCode = string.charCodeAt(i);
// Check for uppercase
if (currentCharCode >= 65 && currentCharCode <= 90) {
currentCharCode = ((currentCharCode - 65 + 13) % 26) + 65;
// Check for lowercase
} else if (currentCharCode >= 97 && currentCharCode <= 122) {
currentCharCode = ((currentCharCode - 97 + 13) % 26) + 97;
}
ROT13String += String.fromCharCode(currentCharCode);
}
return ROT13String;
}
/**
* Sanitizes a given string by replacing all occurrences of certain "trash" strings
* with an underscore. The trash strings are '@$', '^^', '~@', '%?', '*~', '!!', '#&'.
* This is used to decode VOE encoded strings.
* @param {string} string The string to be sanitized.
* @returns {string} The sanitized string.
*/
function voeSanitizer(string) {
let sanitizationArray = ["@$", "^^", "~@", "%?", "*~", "!!", "#&"];
let tempString = string;
for (let i = 0; i < sanitizationArray.length; i++) {
let currentTrash = sanitizationArray[i];
let sanitizedString = new RegExp(
currentTrash.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
"g"
);
tempString = tempString.replace(sanitizedString, "_");
}
return tempString;
}
/**
* Shifts the characters in a string by a given number of places.
* @param {string} string - The string to shift.
* @param {number} shiftNum - The number of places to shift the string.
* @returns {string} The shifted string.
*/
function shiftCharacter(string, shiftNum) {
let tempArray = [];
for (let i = 0; i < string.length; i++) {
tempArray.push(String.fromCharCode(string.charCodeAt(i) - shiftNum));
}
return tempArray.join("");
}
////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// 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;
}
// 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 text = await fetch(fetchUrl);
// 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) {
console.log("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);
// await fetch('http://192.168.2.130/sora-module/log.php?action=add&message=' + encodeURIComponent(message))
// .catch(error => {
// console.error('Error sending log:', error);
// });
// }

View file

@ -0,0 +1,596 @@
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// 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);
const data = await JSON.parse(responseText);
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 text = await fetch(fetchUrl);
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 html = await fetch(fetchUrl);
const finishedList = [];
const seasonLinks = getSeasonLinks(html);
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}`;
const text = await fetch(fetchUrl);
const finishedList = [];
const languageList = getAvailableLanguages(text);
const videoLinks = getVideoLinks(text);
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
const selectedHoster = selectHoster(finishedList);
const provider = selectedHoster.provider;
const providerLink = selectedHoster.href;
if (provider === "Error") {
sendLog("No video found");
return JSON.stringify([{ provider: "Error", link: "" }]);
}
sendLog("Selected provider: " + provider);
sendLog("Selected link: " + providerLink);
const videoPage = await fetch(providerLink);
sendLog("Video Page: " + videoPage.length);
const winLocRegex = /window\.location\.href\s*=\s*['"]([^'"]+)['"]/;
const winLocMatch = winLocRegex.exec(videoPage);
let winLocUrl = null;
if (!winLocMatch) {
winLocUrl = providerLink;
} else {
winLocUrl = winLocMatch[1];
}
const hlsSourceResponse = await fetch(winLocUrl);
const hlsSourcePage =
typeof hlsSourceResponse === "object"
? await hlsSourceResponse.text()
: await hlsSourceResponse;
sendLog("Provider: " + provider);
sendLog("URL: " + winLocUrl);
sendLog("HLS Source Page: " + hlsSourcePage.length);
switch (provider) {
case "VOE":
try {
const voeJson = voeExtractor(hlsSourcePage);
return voeJson?.source || JSON.stringify([{ provider: "Error", link: "" }]);
} catch (error) {
sendLog("VOE extractor error: " + error);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
case "SpeedFiles":
try {
const speedfilesUrl = await speedfilesExtractor(hlsSourcePage);
return speedfilesUrl || JSON.stringify([{ provider: "Error", link: "" }]);
} catch (error) {
sendLog("Speedfiles extractor error: " + error);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
case "Vidmoly":
try {
const vidmolyUrl = vidmolyExtractor(hlsSourcePage);
return vidmolyUrl || JSON.stringify([{ provider: "Error", link: "" }]);
} catch (error) {
sendLog("Vidmoly extractor error: " + error);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
default:
sendLog("Unsupported provider:", provider);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
// END OF VOE EXTRACTOR
// Extract the sources variable and decode the hls value from base64
const sourcesRegex = /var\s+sources\s*=\s*({[^}]+})/;
const sourcesMatch = sourcesRegex.exec(hlsSourcePage);
let sourcesString = sourcesMatch
? sourcesMatch[1].replace(/'/g, '"')
: null;
return sourcesString;
} catch (error) {
sendLog("ExtractStreamUrl error:" + error);
return JSON.stringify([{ provider: "Error1", link: "" }]);
}
}
function selectHoster(finishedList) {
let firstVideo = null;
let provider = null;
// Define the preferred providers and languages
const providerList = ["Vidmoly", "SpeedFiles", "VOE"];
const languageList = ["Deutsch", "mit Untertitel Deutsch"];
for (const providerName of providerList) {
for (const language of languageList) {
const video = finishedList.find(
(video) => video.provider === providerName && video.language === language
);
if (video) {
provider = providerName;
firstVideo = video;
break;
}
}
if (firstVideo) break;
}
// Default to the first video if no match is found
if (!firstVideo) {
firstVideo = finishedList[0];
}
if (firstVideo) {
return {
provider: provider,
href: firstVideo.href,
};
} else {
sendLog("No video found");
return {
provider: "Error",
href: "https://error.org",
};
}
}
//Thanks to Ibro and Cufiy
async function vidmolyExtractor(html) {
sendLog("Vidmoly extractor");
sendLog(html);
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) {
sendLog("Vidmoly extractor: Match found");
const decodedHtml = atob(match[1]); // Decode base64
const iframeMatch = decodedHtml.match(/<iframe\s+src="([^"]+)"/);
if (!iframeMatch) {
sendLog("Vidmoly extractor: No iframe match found");
return null;
}
const streamUrl = iframeMatch[1].startsWith("//") ? "https:" + iframeMatch[1] : iframeMatch[1];
sendLog("Vidmoly extractor: Stream URL: " + streamUrl);
const responseTwo = await fetchv2(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
sendLog(m3u8Match ? m3u8Match[1] : null);
return m3u8Match ? m3u8Match[1] : null;
} else {
sendLog("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;
}
}
// Thanks to Cufiy
function speedfilesExtractor(sourcePageHtml) {
// get var _0x5opu234 = "THIS_IS_AN_ENCODED_STRING"
const REGEX = /var\s+_0x5opu234\s*=\s*"([^"]+)"/;
const match = sourcePageHtml.match(REGEX);
if (match == null || match[1] == null) {
sendLog("Could not extract from Speedfiles source");
return null;
}
const encodedString = match[1];
sendLog("Encoded String:" + encodedString);
// Step 1: Base64 decode the initial string
let step1 = atob(encodedString);
sendLog("Step 1:" + step1);
// Step 2: Swap character cases and reverse
let step2 = step1
.split("")
.map((c) =>
/[a-zA-Z]/.test(c)
? c === c.toLowerCase()
? c.toUpperCase()
: c.toLowerCase()
: c
)
.join("");
sendLog("Step 2:" + step2);
let step3 = step2.split("").reverse().join("");
sendLog("Step 3:" + step3);
// Step 3: Base64 decode again and reverse
let step4 = atob(step3);
sendLog("Step 4:" + step4);
let step5 = step4.split("").reverse().join("");
sendLog("Step 5:" + step5);
// Step 4: Hex decode pairs
let step6 = "";
for (let i = 0; i < step5.length; i += 2) {
step6 += String.fromCharCode(parseInt(step5.substr(i, 2), 16));
}
sendLog("Step 6:" + step6);
// Step 5: Subtract 3 from character codes
let step7 = step6
.split("")
.map((c) => String.fromCharCode(c.charCodeAt(0) - 3))
.join("");
sendLog("Step 7:" + step7);
// Step 6: Final case swap, reverse, and Base64 decode
let step8 = step7
.split("")
.map((c) =>
/[a-zA-Z]/.test(c)
? c === c.toLowerCase()
? c.toUpperCase()
: c.toLowerCase()
: c
)
.join("");
sendLog("Step 8:" + step8);
let step9 = step8.split("").reverse().join("");
sendLog("Step 9:" + step9);
// return atob(step9);
let decodedUrl = atob(step9);
sendLog("Decoded URL:" + decodedUrl);
return decodedUrl;
}
/**
* @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) {
sendLog("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);
}
sendLog("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) {
sendLog("Voe Stream URL: " + streamUrl);
return streamUrl;
} else {
sendLog("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("");
}
////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// 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;
}
// 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 text = await fetch(fetchUrl);
// 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);
await fetch('http://192.168.2.130/sora-module/log.php?action=add&message=' + encodeURIComponent(message))
.catch(error => {
console.error('Error sending log:', error);
});
}

View file

@ -0,0 +1,611 @@
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// 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);
const data = await JSON.parse(responseText);
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) {
console.log("Fetch error:" + error);
return JSON.stringify([{ title: "Error", image: "", href: "" }]);
}
}
async function extractDetails(url) {
try {
const fetchUrl = `${url}`;
const text = await fetch(fetchUrl);
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) {
console.log("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 html = await fetch(fetchUrl);
const finishedList = [];
const seasonLinks = getSeasonLinks(html);
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) {
console.log("Fetch error:" + error);
return JSON.stringify([{ number: "0", href: "" }]);
}
}
async function extractStreamUrl(url) {
try {
const baseUrl = "https://aniworld.to";
const fetchUrl = `${url}`;
const text = await fetch(fetchUrl);
const finishedList = [];
const languageList = getAvailableLanguages(text);
const videoLinks = getVideoLinks(text);
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
const selectedHoster = selectHoster(finishedList);
const provider = selectedHoster.provider;
const providerLink = selectedHoster.href;
if (provider === "Error") {
console.log("No video found");
return JSON.stringify([{ provider: "Error", link: "" }]);
}
console.log("Selected provider: " + provider);
console.log("Selected link: " + providerLink);
const videoPage = await fetch(providerLink);
console.log("Video Page: " + videoPage.length);
const winLocRegex = /window\.location\.href\s*=\s*['"]([^'"]+)['"]/;
const winLocMatch = winLocRegex.exec(videoPage);
let winLocUrl = null;
if (!winLocMatch) {
winLocUrl = providerLink;
} else {
winLocUrl = winLocMatch[1];
}
const hlsSourceResponse = await fetch(winLocUrl);
const hlsSourcePage =
typeof hlsSourceResponse === "object"
? await hlsSourceResponse.text()
: await hlsSourceResponse;
console.log("Provider: " + provider);
console.log("URL: " + winLocUrl);
console.log("HLS Source Page: " + hlsSourcePage.length);
switch (provider) {
case "VOE":
try {
const voeJson = voeExtractor(hlsSourcePage);
return voeJson?.source || JSON.stringify([{ provider: "Error", link: "" }]);
} catch (error) {
console.log("VOE extractor error: " + error);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
case "SpeedFiles":
try {
const speedfilesUrl = await speedfilesExtractor(hlsSourcePage);
return speedfilesUrl || JSON.stringify([{ provider: "Error", link: "" }]);
} catch (error) {
console.log("Speedfiles extractor error: " + error);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
case "Vidmoly":
try {
const vidmolyUrl = vidmolyExtractor(hlsSourcePage);
return vidmolyUrl || JSON.stringify([{ provider: "Error", link: "" }]);
} catch (error) {
console.log("Vidmoly extractor error: " + error);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
default:
console.log("Unsupported provider:", provider);
return JSON.stringify([{ provider: "Error", link: "" }]);
}
// END OF VOE EXTRACTOR
// Extract the sources variable and decode the hls value from base64
const sourcesRegex = /var\s+sources\s*=\s*({[^}]+})/;
const sourcesMatch = sourcesRegex.exec(hlsSourcePage);
let sourcesString = sourcesMatch
? sourcesMatch[1].replace(/'/g, '"')
: null;
return sourcesString;
} catch (error) {
console.log("ExtractStreamUrl error:" + error);
return JSON.stringify([{ provider: "Error1", link: "" }]);
}
}
function selectHoster(finishedList) {
let firstVideo = null;
let provider = null;
// Define the preferred providers and languages
const providerList = ["Vidmoly", "SpeedFiles", "VOE"];
const languageList = ["mit Untertitel Deutsch", "Deutsch"];
for (const providerName of providerList) {
for (const language of languageList) {
const video = finishedList.find(
(video) => video.provider === providerName && video.language === language
);
if (video) {
provider = providerName;
firstVideo = video;
break;
}
}
if (firstVideo) break;
}
// Default to the first video if no match is found
if (!firstVideo) {
firstVideo = finishedList[0];
}
if (firstVideo) {
return {
provider: provider,
href: firstVideo.href,
};
} else {
console.log("No video found");
return {
provider: "Error",
href: "https://error.org",
};
}
}
//Thanks to Ibro and Cufiy
async function vidmolyExtractor(html) {
console.log("Vidmoly extractor");
console.log(html);
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) {
console.log("Vidmoly extractor: Match found");
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];
console.log("Vidmoly extractor: Stream URL: " + streamUrl);
const responseTwo = await fetchv2(streamUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
console.log(m3u8Match ? m3u8Match[1] : null);
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;
}
}
// Thanks to Cufiy
function speedfilesExtractor(sourcePageHtml) {
// get var _0x5opu234 = "THIS_IS_AN_ENCODED_STRING"
const REGEX = /var\s+_0x5opu234\s*=\s*"([^"]+)"/;
const match = sourcePageHtml.match(REGEX);
if (match == null || match[1] == null) {
console.log("Could not extract from Speedfiles source");
return null;
}
const encodedString = match[1];
console.log("Encoded String:" + encodedString);
// Step 1: Base64 decode the initial string
let step1 = atob(encodedString);
console.log("Step 1:" + step1);
// Step 2: Swap character cases and reverse
let step2 = step1
.split("")
.map((c) =>
/[a-zA-Z]/.test(c)
? c === c.toLowerCase()
? c.toUpperCase()
: c.toLowerCase()
: c
)
.join("");
console.log("Step 2:" + step2);
let step3 = step2.split("").reverse().join("");
console.log("Step 3:" + step3);
// Step 3: Base64 decode again and reverse
let step4 = atob(step3);
console.log("Step 4:" + step4);
let step5 = step4.split("").reverse().join("");
console.log("Step 5:" + step5);
// Step 4: Hex decode pairs
let step6 = "";
for (let i = 0; i < step5.length; i += 2) {
step6 += String.fromCharCode(parseInt(step5.substr(i, 2), 16));
}
console.log("Step 6:" + step6);
// Step 5: Subtract 3 from character codes
let step7 = step6
.split("")
.map((c) => String.fromCharCode(c.charCodeAt(0) - 3))
.join("");
console.log("Step 7:" + step7);
// Step 6: Final case swap, reverse, and Base64 decode
let step8 = step7
.split("")
.map((c) =>
/[a-zA-Z]/.test(c)
? c === c.toLowerCase()
? c.toUpperCase()
: c.toLowerCase()
: c
)
.join("");
console.log("Step 8:" + step8);
let step9 = step8.split("").reverse().join("");
console.log("Step 9:" + step9);
// return atob(step9);
let decodedUrl = atob(step9);
console.log("Decoded URL:" + decodedUrl);
return decodedUrl;
}
// Thanks to https://github.com/ShadeOfChaos
/**
* Extracts a JSON object from the given source page by finding the
* encoded string marked with the regex /MKGMa="([\s\S]+?)"/ and
* decoding it using the voeDecoder function.
* @param {string} sourcePageHtml - The source page to be parsed.
* @returns {object|null} The extracted JSON object if successful,
* otherwise null.
*/
function voeExtractor(sourcePageHtml) {
const REGEX = /MKGMa="([\s\S]+?)"/;
const match = sourcePageHtml.match(REGEX);
if (match == null || match[1] == null) {
console.log("Could not extract from Voe source");
return null;
}
const encodedString = match[1];
const decodedJson = voeDecoder(encodedString);
return decodedJson;
}
/**
* Decodes the given MKGMa string, which is a custom encoded string used
* by VOE. This function applies the following steps to the input string to
* decode it:
* 1. Apply ROT13 to each alphabetical character in the string.
* 2. Remove all underscores from the string.
* 3. Decode the string using the Base64 algorithm.
* 4. Apply a character shift of 0x3 to each character in the decoded string.
* 5. Reverse the order of the characters in the shifted string.
* 6. Decode the reversed string using the Base64 algorithm again.
* 7. Parse the decoded string as JSON.
* @param {string} MKGMa_String - The input string to be decoded.
* @returns {object} The decoded JSON object.
*/
function voeDecoder(MKGMa_String) {
let ROT13String = ROT13(MKGMa_String);
let sanitizedString = voeSanitizer(ROT13String);
let UnderscoreRemoved = sanitizedString.split("_").join("");
let base64DecodedString = atob(UnderscoreRemoved);
let charShiftedString = shiftCharacter(base64DecodedString, 0x3);
let reversedString = charShiftedString.split("").reverse().join("");
let base64DecodedStringAgain = atob(reversedString);
let decodedJson;
try {
decodedJson = JSON.parse(base64DecodedStringAgain);
} catch (error) {
console.log("JSON parse error:", error);
decodedJson = {};
}
return decodedJson;
}
/**
* Encodes a given string using the ROT13 cipher, which shifts each letter
* 13 places forward in the alphabet. Only alphabetical characters are
* transformed; other characters remain unchanged.
*
* @param {string} string - The input string to be encoded.
* @returns {string} The encoded string with ROT13 applied.
*/
function ROT13(string) {
let ROT13String = "";
for (let i = 0; i < string.length; i++) {
let currentCharCode = string.charCodeAt(i);
// Check for uppercase
if (currentCharCode >= 65 && currentCharCode <= 90) {
currentCharCode = ((currentCharCode - 65 + 13) % 26) + 65;
// Check for lowercase
} else if (currentCharCode >= 97 && currentCharCode <= 122) {
currentCharCode = ((currentCharCode - 97 + 13) % 26) + 97;
}
ROT13String += String.fromCharCode(currentCharCode);
}
return ROT13String;
}
/**
* Sanitizes a given string by replacing all occurrences of certain "trash" strings
* with an underscore. The trash strings are '@$', '^^', '~@', '%?', '*~', '!!', '#&'.
* This is used to decode VOE encoded strings.
* @param {string} string The string to be sanitized.
* @returns {string} The sanitized string.
*/
function voeSanitizer(string) {
let sanitizationArray = ["@$", "^^", "~@", "%?", "*~", "!!", "#&"];
let tempString = string;
for (let i = 0; i < sanitizationArray.length; i++) {
let currentTrash = sanitizationArray[i];
let sanitizedString = new RegExp(
currentTrash.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
"g"
);
tempString = tempString.replace(sanitizedString, "_");
}
return tempString;
}
/**
* Shifts the characters in a string by a given number of places.
* @param {string} string - The string to shift.
* @param {number} shiftNum - The number of places to shift the string.
* @returns {string} The shifted string.
*/
function shiftCharacter(string, shiftNum) {
let tempArray = [];
for (let i = 0; i < string.length; i++) {
tempArray.push(String.fromCharCode(string.charCodeAt(i) - shiftNum));
}
return tempArray.join("");
}
////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// 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;
}
// 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 text = await fetch(fetchUrl);
// 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) {
console.log("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);
// await fetch('http://192.168.2.130/sora-module/log.php?action=add&message=' + encodeURIComponent(message))
// .catch(error => {
// console.error('Error sending log:', error);
// });
// }

View file

@ -0,0 +1,993 @@
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// 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}`;
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 = ["mit Untertitel Englisch", "mit Untertitel Deutsch", "Deutsch"];
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.3} */
/**
* @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-07-23 17:47:48
* @version 1.1.3
* @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;
}
} 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;
}
const streamUrl = await extractStreamUrlByProvider(url, provider);
// check if streamUrl is not null, a string, and starts with http or https
// 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";
}
// 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 "filemoon":
try {
return await filemoonExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from filemoon:", 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 "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;
}
/* --- 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;
}
}
/* --- 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;
}
}
/* --- 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;
}
}
}
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;
}
}
/* {GE END} */

View file

@ -0,0 +1,994 @@
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// 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.3} */
/**
* @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-07-23 17:47:48
* @version 1.1.3
* @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;
}
} 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;
}
const streamUrl = await extractStreamUrlByProvider(url, provider);
// check if streamUrl is not null, a string, and starts with http or https
// 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";
}
// 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 "filemoon":
try {
return await filemoonExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from filemoon:", 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 "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;
}
/* --- 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;
}
}
/* --- 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;
}
}
/* --- 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;
}
}
}
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;
}
}
/* {GE END} */

View file

@ -0,0 +1,993 @@
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// 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}`;
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 = ["mit Untertitel Deutsch", "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.3} */
/**
* @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-07-23 17:47:48
* @version 1.1.3
* @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;
}
} 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;
}
const streamUrl = await extractStreamUrlByProvider(url, provider);
// check if streamUrl is not null, a string, and starts with http or https
// 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";
}
// 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 "filemoon":
try {
return await filemoonExtractor(html, url);
} catch (error) {
console.log("Error extracting stream URL from filemoon:", 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 "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;
}
/* --- 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;
}
}
/* --- 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;
}
}
/* --- 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;
}
}
}
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;
}
}
/* {GE END} */

86
anoboye/anoboye.js Normal file
View file

@ -0,0 +1,86 @@
async function searchResults(keyword) {
const results = [];
const response = await fetchv2(`https://anoboye.com/?s=${keyword}`);
const html = await response.text();
const regex = /<article class="bs"[^>]*>.*?<a href="([^"]+)"[^>]*>.*?<img src="([^"]+)"[^>]*>.*?<h2[^>]*>(.*?)<\/h2>/gs;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3].trim(),
image: match[2].trim(),
href: match[1].trim()
});
}
return JSON.stringify(results);
}
async function extractDetails(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const match = html.match(/<div class="entry-content"[^>]*>([\s\S]*?)<\/div>/);
let description = "N/A";
if (match) {
description = match[1]
.replace(/<[^>]+>/g, '')
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&amp;/g, "&")
.trim();
}
results.push({
description: description,
aliases: 'N/A',
airdate: 'N/A'
});
return JSON.stringify(results);
}
async function extractEpisodes(url) {
const results = [];
const response = await fetchv2(url);
const html = await response.text();
const regex = /<a href="([^"]+)">\s*<div class="epl-num">([\d.]+)<\/div>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
number: parseInt(match[2], 10)
});
}
results.reverse();
return JSON.stringify(results);
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const iframeMatch = html.match(/<iframe[^>]+src=["']([^"']+)["']/i);
if (!iframeMatch) throw new Error("iframe not found");
const iframeUrl = iframeMatch[1];
const iframeResponse = await fetchv2(iframeUrl);
const iframeHtml = await iframeResponse.text();
const videoMatch = iframeHtml.match(/videoUrl:\s*["']([^"']+)["']/i);
if (!videoMatch) throw new Error("videoUrl not found");
return videoMatch[1].replace(/\\/g, "");
} catch (err) {
return "https://files.catbox.moe/avolvc.mp4";
}
}

19
anoboye/anoboye.json Normal file
View file

@ -0,0 +1,19 @@
{
"sourceName": "Anoboye",
"iconUrl": "https://i3.wp.com/anoboye.com/wp-content/uploads/2025/05/Anoboye-300x300.jpg",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"language": "Chinese",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://anoboye.com/",
"searchBaseUrl": "https://anoboye.com/",
"scriptUrl": "https://gitlab.com/50n50/sources/-/raw/main/anoboye/anoboye.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false
}

229
arablionz/arablionz.js Normal file
View file

@ -0,0 +1,229 @@
async function searchResults(keyword) {
const results = [];
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Referer": "https://arablionztv.cam/"
};
const postData = "";
try {
const response = await fetchv2("https://arablionztv.cam/SearchEngine/"+encodeURIComponent(keyword), headers, "POST", postData);
const html = await response.text();
const regex = /<a href="([^"]+)"[^>]*>\s*<div class="Box--Poster">\s*<img[^>]+data-image="([^"]+)"[^>]*>\s*.*?<h2>(.*?)<\/h2>/gs;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3].trim(),
image: match[2].trim(),
href: match[1].trim()
});
}
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 class="Singular--Story-P">(.*?)<\/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) {
try {
return JSON.stringify([{
href: url,
number: 1
}]);
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const idMatch = html.match(/data-id="(\d+)"/);
if (!idMatch) return "https://error.org/";
const id = idMatch[1];
const apiUrl = "https://passthrough-worker.simplepostrequest.workers.dev/?arablionz=https://arablionztv.cam/PostServersWatch/" + id;
const apiResponse = await fetchv2(apiUrl);
const apiHtml = await apiResponse.text();
const iframeMatch = apiHtml.match(/<iframe[^>]+src="([^"]+)"/);
if (!iframeMatch) return "https://error.org/";
const iframeUrl = iframeMatch[1];
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Referer": "https://arablionztv.cam/"
};
const streamResponse = await fetchv2(iframeUrl, headers);
const streamHtml = await streamResponse.text();
const obfuscatedScript = streamHtml.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
if (!obfuscatedScript) return "https://error.org/";
const unpackedScript = unpack(obfuscatedScript[1]);
const fileMatch = unpackedScript.match(/file:\s*"([^"]+)"/);
if (!fileMatch) return "https://error.org/";
const fileUrl = fileMatch[1];
console.log(fileUrl);
return fileUrl;
} catch (err) {
return "https://error.org/";
}
}
/***********************************************************
* UNPACKER MODULE
* Credit to GitHub user "mnsrulz" for Unpacker Node library
* https://github.com/mnsrulz/unpacker
***********************************************************/
class Unbaser {
constructor(base) {
/* Functor for a given base. Will efficiently convert
strings to natural numbers. */
this.ALPHABET = {
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
};
this.dictionary = {};
this.base = base;
// fill elements 37...61, if necessary
if (36 < base && base < 62) {
this.ALPHABET[base] = this.ALPHABET[base] ||
this.ALPHABET[62].substr(0, base);
}
// If base can be handled by int() builtin, let it do it for us
if (2 <= base && base <= 36) {
this.unbase = (value) => parseInt(value, base);
}
else {
// Build conversion dictionary cache
try {
[...this.ALPHABET[base]].forEach((cipher, index) => {
this.dictionary[cipher] = index;
});
}
catch (er) {
throw Error("Unsupported base encoding.");
}
this.unbase = this._dictunbaser;
}
}
_dictunbaser(value) {
/* Decodes a value to an integer. */
let ret = 0;
[...value].reverse().forEach((cipher, index) => {
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
});
return ret;
}
}
function detect(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) {
/* Unpacks P.A.C.K.E.R. packed js code. */
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) {
/* Look up symbols in the synthetic symtab. */
const word = match;
let word2;
if (radix == 1) {
//throw Error("symtab unknown");
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) {
/* Juice from a source file the four args needed by decoder. */
const juicers = [
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
];
for (const juicer of juicers) {
//const args = re.search(juicer, source, re.DOTALL);
const args = juicer.exec(source);
if (args) {
let a = args;
if (a[2] == "[]") {
//don't know what it is
// a = list(a);
// a[1] = 62;
// a = tuple(a);
}
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) {
/* Strip string lookup table (list) and replace values in source. */
/* Need to work on this. */
return source;
}
}

Some files were not shown because too many files have changed in this diff Show more