update
Fetch and Save Remote Content / fetch (push) Successful in 39s
Sync Versions to index.json / sync-versions (push) Failing after 54s

This commit is contained in:
aka paul
2026-05-27 20:30:23 +02:00
parent caffaabc88
commit 597f279e49
113 changed files with 1207 additions and 980 deletions
@@ -1,7 +1,9 @@
async function getDomainsList() {
try {
console.log("Fetching domains from anime-sama.pw...");
const response = await fetchv2("https://anime-sama.pw/");
const html = await response.text();
console.log("Fetched HTML length:", html.length);
const domainRegex = /{ name: '([^']+)' }/g;
const domains = [];
@@ -10,37 +12,47 @@ async function getDomainsList() {
domains.push(match[1]);
}
console.log("Parsed domains:", domains);
return domains.length > 0 ? domains : ["anime-sama.tv"];
} catch (err) {
console.error("Error in getDomainsList:", err);
return ["anime-sama.tv"];
}
}
async function searchResults(keyword) {
console.log("searchResults keyword:", keyword);
const domains = await getDomainsList();
const regex = /<a[^>]+href="([^"]+)"[\s\S]*?<img[^>]+src="([^"]+)"[\s\S]*?<h3[^>]*>(.*?)<\/h3>/gi;
const firstDomain = domains[0];
console.log("Trying first domain:", firstDomain);
const firstResult = await trySearch(firstDomain, keyword, regex);
if (firstResult && firstResult.length > 0) {
console.log("Results found in first domain:", firstDomain);
return JSON.stringify(firstResult);
}
console.log("No results in first domain, trying others...");
const otherDomains = domains.slice(1);
const promises = otherDomains.map(domain => trySearch(domain, keyword, regex));
const results = await Promise.all(promises);
for (let result of results) {
for (let i = 0; i < results.length; i++) {
const result = results[i];
if (result && result.length > 0) {
console.log("Results found in domain:", otherDomains[i]);
return JSON.stringify(result);
}
}
console.log("No results found in any domain.");
return JSON.stringify([]);
}
async function trySearch(domain, keyword, regex) {
try {
console.log("trySearch domain:", domain, "keyword:", keyword);
const headers = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With": "XMLHttpRequest",
@@ -54,6 +66,7 @@ async function trySearch(domain, keyword, regex) {
`query=${encodeURIComponent(keyword)}`
);
const html = await response.text();
console.log("trySearch HTML length:", html.length);
const results = [];
let match;
@@ -66,8 +79,10 @@ async function trySearch(domain, keyword, regex) {
});
}
console.log("trySearch found results:", results.length);
return results;
} catch (err) {
console.error("trySearch error for domain", domain, ":", err);
return [];
}
}

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

@@ -107,11 +107,8 @@ async function extractEpisodes(url) {
}
}
console.log(`[Debug] Found ${results.length} episodes`);
console.log(`[Debug]`, JSON.stringify(results, null, 2));
return JSON.stringify(results.reverse());
} catch (err) {
console.error('Error extracting episodes:', err);
return JSON.stringify([{ href: "Error", number: "Error" }]);
}
}
@@ -121,7 +118,7 @@ async function extractStreamUrl(url) {
const firstresponse = await fetchv2(url);
const firsthtml = await firstresponse.text();
const idMatch = firsthtml.match(/href=['"]https:\/\/www3\.veziseriale\.org\/\?p=(\d+)['"]/);
const idMatch = firsthtml.match(/href=['"][^'"]+\?p=(\d+)['"]/) || firsthtml.match(/\?p=(\d+)/);
if (!idMatch) throw new Error('ID not found');
const id = idMatch[1];
@@ -160,11 +157,10 @@ async function extractStreamUrl(url) {
});
} catch (err) {
console.error('Error extracting stream URL: ' + err);
return {
stream: "https://files.catbox.moe/avolvc.mp4",
return JSON.stringify({
stream: "",
subtitles: ""
};
});
}
}
@@ -52,10 +52,31 @@ async function extractDetails(url) {
async function extractEpisodes(url) {
const results = [];
function myAtob(input) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let str = '';
let buffer = 0;
let bits = 0;
for (let i = 0; i < input.length; i++) {
const char = input.charAt(i);
if (char === '=') break;
const index = chars.indexOf(char);
if (index === -1) continue;
buffer = (buffer << 6) | index;
bits += 6;
if (bits >= 8) {
bits -= 8;
str += String.fromCharCode((buffer >> bits) & 0xFF);
buffer &= (1 << bits) - 1;
}
}
return str;
}
function decryptEpisodeData(encodedData) {
const parts = encodedData.split('.');
const encryptedData = atob(parts[0]);
const xorKey = atob(parts[1]);
const encryptedData = myAtob(parts[0]);
const xorKey = myAtob(parts[1]);
let decryptedString = '';
@@ -66,6 +87,10 @@ async function extractEpisodes(url) {
decryptedString += decryptedChar;
}
try {
decryptedString = decodeURIComponent(escape(decryptedString));
} catch (e) {}
return JSON.parse(decryptedString);
}
@@ -79,6 +104,7 @@ async function extractEpisodes(url) {
const encodedData = dataMatch ? dataMatch[1] : null;
if (encodedData) {
try {
const decoded = decryptEpisodeData(encodedData);
const addEpisode = (ep) => {
@@ -93,6 +119,43 @@ async function extractEpisodes(url) {
}
return JSON.stringify(results);
} catch (innerErr) {
console.log("Decryption failed, using fallback...");
}
}
const regex = /openEpisode\('([^']+)'\)[\s\S]*?>[^<]*?(\d+)[^<]*?<\/a>/gi;
let match;
const seenHrefs = new Set();
while ((match = regex.exec(html)) !== null) {
const href = myAtob(match[1]);
const number = parseInt(match[2], 10);
if (!seenHrefs.has(href)) {
results.push({
href: href,
number: isNaN(number) ? 0 : number
});
seenHrefs.add(href);
}
}
if (results.length === 0) {
const fallbackRegex = /openEpisode\('([^']+)'\)/gi;
while ((match = fallbackRegex.exec(html)) !== null) {
const href = atob(match[1]);
if (!seenHrefs.has(href)) {
const numMatch = href.match(/-(\d+)\/?$/);
const number = numMatch ? parseInt(numMatch[1], 10) : results.length + 1;
results.push({
href: href,
number: number
});
seenHrefs.add(href);
}
}
}
return JSON.stringify(results);
+27 -11
View File
@@ -1,7 +1,7 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://123animes.ru/search?keyword=" + encodeURIComponent(keyword));
const response = await fetchv2("https://123animehub.cc/search?keyword=" + encodeURIComponent(keyword));
const html = await response.text();
const filmListMatch = html.match(/<div class="film-list">([\s\S]*?)<div class="clearfix"><\/div>/);
@@ -15,8 +15,8 @@ async function searchResults(keyword) {
while ((match = itemRegex.exec(filmList)) !== null) {
results.push({
title: match[2].trim(),
image: "https://123animes.ru" + match[3].trim(),
href: "https://123animes.ru" + match[1].trim()
image: "https://123animehub.cc" + match[3].trim(),
href: "https://123animehub.cc" + match[1].trim()
});
}
@@ -73,7 +73,7 @@ async function extractEpisodes(url) {
try {
const animeId = url.split('/').pop();
const response = await fetchv2("https://123animes.ru/ajax/film/sv?id=" + animeId);
const response = await fetchv2("https://123animehub.cc/ajax/film/sv?id=" + animeId);
const jsonData = await response.json();
const html = jsonData.html;
@@ -111,19 +111,35 @@ async function extractEpisodes(url) {
async function extractStreamUrl(ID) {
try {
const response = await fetchv2("https://123animes.ru/ajax/episode/info?epr=" + ID);
const response = await fetchv2("https://123animehub.cc/ajax/episode/info?epr=" + encodeURIComponent(ID));
const data = await response.json();
const target = data.target;
const streamID = target.split('/').pop();
const responseTwo = await fetchv2("https://play.shipimagesbolt.online/hs/getSources?id=" + streamID);
const dataTwo = await responseTwo.json();
if (!target) throw new Error("No target in response: " + JSON.stringify(data));
const stream = dataTwo.sources;
const responseTarget = await fetchv2(target);
const htmlTarget = await responseTarget.text();
const zrpart2Match = htmlTarget.match(/var\s+zrpart2\s*=\s*'([^']+)';/);
if (!zrpart2Match) throw new Error("zrpart2 not found");
const zrpart2 = zrpart2Match[1];
return stream;
const originMatch = target.match(/^(https?:\/\/[^\/]+)/);
const origin = originMatch ? originMatch[1] : "";
const hsUrl = `${origin}/hs/${zrpart2}`;
const responseHs = await fetchv2(hsUrl);
const htmlHs = await responseHs.text();
const dataIdMatch = htmlHs.match(/id="mg-player"[^>]*data-id="([^"]+)"/);
if (!dataIdMatch) throw new Error("data-id not found");
const dataId = dataIdMatch[1];
const sourcesUrl = `${origin}/hs/getSources?id=${dataId}`;
const responseSources = await fetchv2(sourcesUrl);
const dataSources = await responseSources.json();
return dataSources.sources;
} catch (err) {
console.log("Stream URL Error details:", err.message, err.stack);
return "https://error.org/";
}
}
+4 -4
View File
@@ -1,16 +1,16 @@
{
"sourceName": "123Anime",
"iconUrl": "https://123animes.ru/assets/favicons/apple-touch-icon.png",
"iconUrl": "https://123animehub.cc/assets/favicons/apple-touch-icon.png",
"author": {
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"version": "1.0.1",
"language": "English (DUB/HARDSUB)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://play.shipimagesbolt.online/",
"searchBaseUrl": "https://play.shipimagesbolt.online/",
"baseUrl": "https://123animehub.cc/",
"searchBaseUrl": "https://123animehub.cc/",
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/123anime/123anime.js",
"type": "anime",
"asyncJS": true,
+1 -1
View File
@@ -22,7 +22,7 @@ async function searchResults(keyword) {
if (linkMatch) {
results.push({
href: linkMatch[1].trim(),
title: decodeHtml(linkMatch[2].trim()),
title: decodeHtml(linkMatch[2].trim()).replace(/^Watch\s+/i, ""),
image: image
});
}
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.1",
"version": "1.0.2",
"language": "Tamil",
"streamType": "HLS",
"quality": "1080p",
+51 -53
View File
@@ -71,9 +71,17 @@ async function extractEpisodes(url) {
const movieRegex = /<a[^>]+href=["']([^"']+)["'][^>]+class=["'][^"']*link-btn link-show[^"']*["'][^>]*>/i;
const movieMatch = movieRegex.exec(html);
const buildHref = (extractedHref) => {
const watchMatch = extractedHref.match(/\/watch\/(\d+)/i);
if (watchMatch) {
return url.replace(/\/(movie|series|episode)\//i, `/watch/${watchMatch[1]}/`);
}
return extractedHref;
};
if (movieMatch && movieMatch[1]) {
episodes.push({
href: movieMatch[1],
href: buildHref(movieMatch[1]),
number: 1
});
} else {
@@ -86,7 +94,7 @@ async function extractEpisodes(url) {
const hrefMatch = block.match(/href=["']([^"']+)["']/);
if (hrefMatch) {
episodes.push({
href: hrefMatch[1],
href: buildHref(hrefMatch[1]),
number: index + 1
});
}
@@ -99,67 +107,57 @@ async function extractEpisodes(url) {
}
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]) {
const streams = [];
try {
const shortnerResponse = await fetch(match[1]);
const shortnerHtml = await shortnerResponse;
let streamPageUrl = url;
const finalMatch = shortnerHtml.match(/<div class="d-none d-md-block">\s*<a href="([^"]+)"/);
if (url.includes("/episode/")) {
const epResponse = await fetchv2(url);
const epHtml = await epResponse.text();
if (finalMatch && finalMatch[1]) {
let finalUrl = finalMatch[1].replace("two.akw.cam", "ak.sv");
const linkRegex = /<a[^>]+href=["']([^"']+)["'][^>]+class=["'][^"']*link-btn link-show[^"']*["'][^>]*>/i;
const linkMatch = linkRegex.exec(epHtml);
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;
}
}
if (linkMatch && linkMatch[1]) {
const watchMatch = linkMatch[1].match(/\/watch\/(\d+)/i);
if (watchMatch) {
streamPageUrl = url.replace(/\/(episode)\//i, `/watch/${watchMatch[1]}/`);
} 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"/);
streamPageUrl = linkMatch[1];
}
}
}
if (videoMatch && videoMatch[1]) {
stream = videoMatch[1];
const response = await fetchv2(streamPageUrl);
const html = await response.text();
const sourceTagRegex = /<source[^>]+>/gi;
let match;
while ((match = sourceTagRegex.exec(html)) !== null) {
const sourceHtml = match[0];
const srcMatch = sourceHtml.match(/src=["']([^"']+)["']/i);
const sizeMatch = sourceHtml.match(/size=["']([^"']+)["']/i);
if (srcMatch) {
streams.push({
title: sizeMatch ? sizeMatch[1] : "Default",
streamUrl: srcMatch[1],
headers: {}
});
}
}
} catch (error) {
console.error("Error fetching final URL:", error);
return null;
}
}
console.error("Error fetching stream:", error);
}
console.log(stream);
return stream;
const result = {
streams: streams,
subtitle: ""
};
console.log(JSON.stringify(result));
return JSON.stringify(result);
}
function decodeHTMLEntities(text) {
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.5",
"version": "1.0.6",
"language": "Arabic (SUB)",
"streamType": "MP4",
"quality": "1080p",
+112 -132
View File
@@ -1,57 +1,53 @@
async function searchResults(keyword) {
const results = [];
const headers = {
"Content-Type": "multipart/form-data; boundary=----geckoformboundary38c356867533a17de80e8c65d9125df5"
};
const postData = `------geckoformboundary38c356867533a17de80e8c65d9125df5
Content-Disposition: form-data; name="s_keyword"
${keyword}
------geckoformboundary38c356867533a17de80e8c65d9125df5
Content-Disposition: form-data; name="orderby"
popular
------geckoformboundary38c356867533a17de80e8c65d9125df5
Content-Disposition: form-data; name="order"
DESC
------geckoformboundary38c356867533a17de80e8c65d9125df5
Content-Disposition: form-data; name="action"
advanced_search
------geckoformboundary38c356867533a17de80e8c65d9125df5
Content-Disposition: form-data; name="page"
1
------geckoformboundary38c356867533a17de80e8c65d9125df5--`;
try {
const response = await fetchv2("https://an1me.to/wp-admin/admin-ajax.php", headers, "POST", postData);
const data = await response.json();
const url = "https://an1me.to/wp-admin/admin-ajax.php?action=instant_search&query=" + encodeURIComponent(keyword);
const headers = {
"Accept": "application/json, text/javascript, */*; q=0.01",
"X-Requested-With": "XMLHttpRequest",
"Referer": "https://an1me.to/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
};
const response = await fetchv2(url, headers);
const text = await response.text();
let data;
try {
data = JSON.parse(text);
} catch (e) {
return JSON.stringify(results);
}
if (!data.success || !data.data || !data.data.html) {
return JSON.stringify(results);
}
const html = data.data.html;
const articlePattern = /<article[^>]*class="anime-card[^"]*"[^>]*>([\s\S]*?)<\/article>/g;
let articleMatch;
const anchorPattern = /<a[^>]+href=["']([^"']+)["'][^>]*title=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/g;
let anchorMatch;
let matchCount = 0;
while ((articleMatch = articlePattern.exec(html)) !== null) {
const articleHtml = articleMatch[1];
while ((anchorMatch = anchorPattern.exec(html)) !== null) {
matchCount++;
const href = anchorMatch[1];
const title = anchorMatch[2];
const innerHtml = anchorMatch[3];
const imgMatch = articleHtml.match(/<img[^>]+src=['"]([^'"]+)['"][^>]+alt=['"]([^'"]+)['"]/);
const imgMatch = innerHtml.match(/<img[^>]+src=["']([^"']+)["'][^>]*>/);
const linkMatch = articleHtml.match(/<h3[^>]*>[\s\S]*?<a[^>]+href=['"]([^'"]+)['"][^>]*title=['"]([^'"]+)['"]/);
if (imgMatch && linkMatch) {
if (imgMatch) {
results.push({
title: linkMatch[2].trim(),
title: title.trim(),
image: imgMatch[1].trim(),
href: linkMatch[1].trim()
href: href.trim()
});
}
}
return JSON.stringify(results);
} catch (err) {
console.log(err);
console.log("Search error:", err.message);
return JSON.stringify([{
title: "Error",
image: "Error",
@@ -62,15 +58,18 @@ Content-Disposition: form-data; name="page"
async function extractDetails(url) {
try {
const response = await fetchv2(url);
const headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Referer": "https://an1me.to/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
};
const response = await fetchv2(url, headers);
const html = await response.text();
const match = html.match(
/<div class="anime-synopsis">[\s\S]*?<p>([\s\S]*?)<\/p>/
);
const match = html.match(/aria-label="Anime Overview"[^>]*>([\s\S]*?)<\/section>/);
const description = match
? match[1].replace(/\s+/g, " ").trim()
? match[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim()
: "N/A";
return JSON.stringify([{
@@ -78,7 +77,8 @@ async function extractDetails(url) {
aliases: "N/A",
airdate: "N/A"
}]);
} catch {
} catch (err) {
console.log("extractDetails error:", err.message);
return JSON.stringify([{
description: "Error",
aliases: "Error",
@@ -91,30 +91,64 @@ async function extractDetails(url) {
async function extractEpisodes(url) {
const results = [];
try {
const response = await fetchv2(url);
const headers = {
"Accept": "application/json, text/javascript, */*; q=0.01",
"X-Requested-With": "XMLHttpRequest",
"Referer": "https://an1me.to/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
};
const response = await fetchv2(url, headers);
const html = await response.text();
const postIdMatch = html.match(/postid-(\d+)/) ||
html.match(/post_id['"]\s*,\s*['"](\d+)['"]/) ||
html.match(/anime_id['"]\s*:\s*(\d+)/);
const regex = /<a href="([^"]+)"[^>]*class="episode-list-display-box episode-list-item[^"]*"[^>]*>[\s\S]*?<span class="episode-list-item-number">\s*(\d+)\s*<\/span>/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
number: parseInt(match[2], 10)
});
if (!postIdMatch) {
console.log("Could not find anime_id (postid)");
return JSON.stringify(results);
}
if (results.length === 0 && url) {
const animeId = postIdMatch[1];
const firstPageUrl = `https://an1me.to/wp-admin/admin-ajax.php?action=get_episodes&anime_id=${animeId}&page=1&order=desc`;
const firstPageRes = await fetchv2(firstPageUrl, headers);
const firstPageData = await firstPageRes.json();
if (firstPageData.success && firstPageData.data && Array.isArray(firstPageData.data.episodes)) {
firstPageData.data.episodes.forEach(ep => {
results.push({
href: url,
number: 1
href: ep.url,
number: parseFloat(ep.meta_number)
});
});
const maxPages = firstPageData.data.max_episodes_page || 1;
if (maxPages > 1) {
const promises = [];
for (let i = 2; i <= maxPages; i++) {
const pageUrl = `https://an1me.to/wp-admin/admin-ajax.php?action=get_episodes&anime_id=${animeId}&page=${i}&order=desc`;
promises.push(fetchv2(pageUrl, headers).then(res => res.json()));
}
const otherPagesData = await Promise.all(promises);
otherPagesData.forEach(data => {
if (data.success && data.data && Array.isArray(data.data.episodes)) {
data.data.episodes.forEach(ep => {
results.push({
href: ep.url,
number: parseFloat(ep.meta_number)
});
});
}
});
}
}
return JSON.stringify(results);
} catch (err) {
console.log(err);
console.log("Error in extractEpisodes:", err);
return JSON.stringify([{
href: "Error",
number: "Error"
@@ -124,85 +158,31 @@ async function extractEpisodes(url) {
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Referer": "https://an1me.to/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
};
const response = await fetchv2(url, headers);
const html = await response.text();
const sources = await getSourcesFromEpisode(url);
if(sources == null) return null;
const streams = await extractStreamsFromSources(sources);
if(streams == null) return null;
console.log("Streams: " + JSON.stringify(streams));
const iframeMatch = html.match(/<iframe[^>]+src=["']([^"']+)["']/i);
if (!iframeMatch) throw new Error("Iframe not found");
const streamUrl = streams[0].stream;
console.log("Stream URL: " + streamUrl);
return streamUrl;
let iframeUrl = iframeMatch[1].replace(/&#038;/g, '&').replace(/&amp;/g, '&');
const iframeResponse = await fetchv2(iframeUrl, headers);
const iframeHtml = await iframeResponse.text();
const paramsMatch = iframeHtml.match(/const\s+params\s*=\s*({.*?});/s);
if (!paramsMatch) throw new Error("Params not found");
const params = JSON.parse(paramsMatch[1]);
const streamUrl = params.sources?.[0]?.url;
console.log("Stream URL secured:", streamUrl);
return streamUrl || "https://files.catbox.moe/avolvc.mp4";
} catch (err) {
console.log("extractStreamUrl error:", err.message);
return "https://files.catbox.moe/avolvc.mp4";
}
}
async function getSourcesFromEpisode(url) {
const sources = [];
try {
const res = await fetchv2(url);
const html = await res.text();
const regex = /<span[^>]+data-embed-id="([^"]+)"[^>]*>/gi;
let match;
while ((match = regex.exec(html)) !== null) {
const dataEmbedId = match[1];
const [_, iframeBase64] = dataEmbedId.split(":");
if (!iframeBase64) continue;
const iframeHtml = atob(iframeBase64);
const srcMatch = iframeHtml.match(/src\s*=\s*["']([^"']+)["']/i);
if (!srcMatch) continue;
const isSub = /class="[^"]*player-sub[^"]*"/.test(match[0]);
sources.push({
source: srcMatch[1],
audio: isSub ? 'original' : 'Greek'
});
}
return sources;
} catch (e) {
console.error('Error extracting source: ' + e.message);
return [];
}
}
async function extractStreamsFromSources(sources) {
const streams = [];
for(let source of sources) {
try {
const res = await fetchv2(source.source);
const html = await res.text();
const jsonString = html.match(/params[\s]*=[\s]*(\{[^;]*);/)?.[1];
if(jsonString == null) continue;
const json = JSON.parse(jsonString);
if(json?.sources == null || json.sources.length <= 0) continue;
for(let s of json.sources) {
const resolution = s?.html ?? null;
let arrayLength = streams.push(source);
let i = arrayLength - 1;
streams[i].stream = s.url;
streams[i].resolution = resolution != null ? resolution.slice(0, -1) : null;
}
} catch(e) {
console.warn('Error extracting stream: ' + e.message);
}
}
return streams.filter(source => source.stream != null);
}
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.2",
"version": "1.0.3",
"language": "Greek",
"streamType": "HLS",
"encrypted": true,
+204 -32
View File
@@ -63,10 +63,19 @@ Content-Disposition: form-data; name="page"
async function extractDetails(url) {
try {
const response = await fetchv2(url);
const headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
"Referer": "https://anihq.to/"
};
const response = await fetchv2(url, headers);
const html = await response.text();
const descMatch = html.match(/<div\s+data-synopsis[^>]*>([\s\S]*?)<\/div>/);
let descMatch = html.match(/<div\s+class=["']anime-synopsis["']>[\s\S]*?<div[^>]+class=["']prose[^"']*["'][^>]*>([\s\S]*?)<\/div>/i);
if (!descMatch) {
descMatch = html.match(/<section[^>]+aria-label=["']Anime Overview["'][^>]*>([\s\S]*?)<\/section>/i);
}
let description = "N/A";
if (descMatch) {
@@ -84,7 +93,7 @@ async function extractDetails(url) {
}]);
} catch (err) {
return JSON.stringify([{
description: "Error",
description: "Error: " + err.message,
aliases: "Error",
airdate: "Error"
}]);
@@ -139,46 +148,209 @@ async function extractEpisodes(url) {
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
"Referer": "https://anihq.to/"
};
const response = await fetchv2(url, headers);
const html = await response.text();
const iframeMatch = html.match(/<iframe[^>]+src=['"]https:\/\/([^'"]+\.playerp2p\.com)\/#([^'"]+)['"]/);
const iframeMatch = html.match(/<iframe[^>]+src=['"]([^'"]+)['"]/i);
if (!iframeMatch) {
console.log("No iframe ID found");
return "https://error.org/";
console.log("No iframe found on page");
return null;
}
const domain = iframeMatch[1];
const videoId = iframeMatch[2];
const iframeUrl = iframeMatch[1];
console.log("Found iframe URL:", iframeUrl);
const apiUrl = `https://${domain}/api/v1/video?id=${videoId}&w=1792&h=1120&r=anihq.to`;
const apiResponse = await fetchv2(apiUrl);
const encodedString = await apiResponse.text();
const iframeResponse = await fetchv2(iframeUrl, headers);
const iframeHtml = await iframeResponse.text();
const hasUppercase = /[A-Z]/.test(encodedString);
let streamData = null;
try {
streamData = voeExtractor(iframeHtml);
} catch (error) {
console.log("VOE extraction error:", error.message || error);
return null;
}
let stringToSend;
if (hasUppercase) {
stringToSend = atob(encodedString);
console.log("Decoded string: " + stringToSend);
const streamUrlResult = typeof streamData === "string" ? streamData : getStreamUrl(streamData);
if (streamUrlResult) {
const origin = "https://bryantenunder.com";
console.log("Stream URL secured:", streamUrlResult);
return JSON.stringify({
streams: [
{
title: "Server 1",
streamUrl: streamUrlResult,
headers: {
"Origin": origin,
"Referer": origin + "/"
}
}
]
});
}
console.log("No stream URL found");
return null;
} catch (error) {
console.log("Fetch error:", error.message || error);
return null;
}
}
/* SCHEME START */
/**
* @name voeExtractor
* @author Cufiy
*/
function voeExtractor(html, url = null) {
const regex = /<script[^>]+type=["']application\/json["'][^>]*>([\s\S]*?)<\/script>/gi;
let match;
let obfuscatedString = null;
while ((match = regex.exec(html)) !== null) {
try {
const data = JSON.parse(match[1].trim());
if (Array.isArray(data) && typeof data[0] === "string") {
obfuscatedString = data[0];
break;
}
} catch (e) {
// Ignore syntax/parse errors for other script tags
}
}
if (!obfuscatedString) {
console.log("No valid VOE application/json script tag found");
return null;
}
// 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);
}
// check if direct_access_url is set, not null and starts with http
const streamUrl = getStreamUrl(result);
if (streamUrl) {
console.log("Voe Stream URL: " + streamUrl);
return streamUrl;
} else {
stringToSend = encodedString;
console.log("Using encoded string directly (no uppercase found)");
console.log("No stream URL found in the decoded JSON");
}
return result;
}
const postData = {
text: stringToSend
};
const headers = { "Content-Type": "application/json" };
const response2 = await fetchv2("https://enc-dec.app/api/dec-vidstack", headers, "POST", postData);
const data = await response2.json();
console.log(JSON.stringify(data));
console.log("Final URL: " + data.result.source);
return data.result.cf;
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
);
});
}
} catch (err) {
console.log("Error: " + err.message);
return "https://error.org/";
function voeRemovePatterns(str) {
const patterns = ["@$", "^^", "~@", "%?", "*~", "!!", "#&"];
let result = str;
for (const pat of patterns) {
result = result.split(pat).join("");
}
return result;
}
function voeBase64Decode(str) {
if (typeof atob === "function") {
try {
return atob(str);
} catch (e) {
// fallback if atob fails
}
}
// Pure Javascript Base64 decoding fallback to avoid reliance on Buffer or atob
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let cleaned = str.replace(/=+$/, '').replace(/[^A-Za-z0-9+/]/g, '');
let output = '';
let buffer = 0;
let bits = 0;
for (let i = 0; i < cleaned.length; i++) {
const char = cleaned[i];
const idx = chars.indexOf(char);
if (idx === -1) continue;
buffer = (buffer << 6) | idx;
bits += 6;
if (bits >= 8) {
bits -= 8;
const byte = (buffer >> bits) & 0xFF;
output += String.fromCharCode(byte);
}
}
try {
return decodeURIComponent(escape(output));
} catch (e) {
return output;
}
}
function voeShiftChars(str, shift) {
return str
.split("")
.map((c) => String.fromCharCode(c.charCodeAt(0) - shift))
.join("");
}
function getStreamUrl(result) {
if (!result) return null;
if (typeof result === "string" && result.startsWith("http")) {
return result;
}
if (typeof result === "object") {
if (typeof result.source === "string" && result.source.startsWith("http")) {
return result.source;
}
if (typeof result.direct_access_url === "string" && result.direct_access_url.startsWith("http")) {
return result.direct_access_url;
}
if (Array.isArray(result.source)) {
const url = result.source
.map((source) => typeof source === "string" ? source : (source.direct_access_url || source.file || source.url))
.find((url) => url && url.startsWith("http"));
if (url) return url;
}
}
return null;
}
/* SCHEME END */
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.1",
"version": "1.0.2",
"language": "English",
"streamType": "HLS",
"quality": "1080p",
-26
View File
@@ -1,26 +0,0 @@
{
"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.1.1",
"language": "French",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://anime-sama.fr/",
"searchBaseUrl": "https://anime-sama.fr/",
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anime-sama/anime-sama.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": false,
"supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true,
"supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true
}
+37 -18
View File
@@ -66,7 +66,7 @@ async function extractEpisodes(url) {
server.server_data?.forEach(ep => {
results.push({
number: parseInt(ep.name, 10),
href: "https://anibd.app/playid/" + postid + "/?server=" + server.id + "&slug=" + ep.slug
href: "https://epeng.animeapps.top/apilink.php?data=" + ep.link
});
});
});
@@ -79,26 +79,45 @@ async function extractEpisodes(url) {
async function extractStreamUrl(url) {
try {
const pageHtml = await (await fetchv2(url)).text();
const iframeUrl = pageHtml.match(/id="video-player-iframe"\s+src="([^"]+)"/)?.[1]?.replace(/&#038;/g, '&');
if (!iframeUrl) return JSON.stringify({ streams: [], subtitle: "" });
const response = await fetchv2(url);
const servers = await response.json();
const headers = {
"Referer": "https://anibd.app/"
};
const iframeHtml = await (await fetchv2(iframeUrl, { "Referer": "https://anibd.app/" })).text();
let streamUrl = iframeHtml.match(/url:\s*['"]([^'"]+\.m3u8)['"]/)?.[1] || iframeHtml.match(/videoUrl:\s*["']([^"']+\.m3u8)['"]/)?.[1];
const promises = servers.map(async (server) => {
try {
const res = await fetchv2(server.link, headers);
const html = await res.text();
const match = html.match(/url:\s*['"]([^'"]+)['"]/);
if (match) {
let streamUrl = match[1];
if (!streamUrl.startsWith("http")) {
const baseUrl = server.link.substring(0, server.link.lastIndexOf("/") + 1);
streamUrl = baseUrl + streamUrl;
}
return {
title: server.server,
streamUrl: streamUrl
};
}
} catch (err) {}
return null;
});
const results = await Promise.all(promises);
const streams = results.filter(s => s !== null);
if (streamUrl) {
const fullUrl = streamUrl.startsWith("/") ? "https://playeng.animeapps.top" + streamUrl : streamUrl.startsWith("http") ? streamUrl : "https://playeng.animeapps.top/r2/" + streamUrl;
return JSON.stringify({
streams: [{
title: "Server 1",
streamUrl: fullUrl,
headers: { "Referer": "https://anibd.app/" }
}],
subtitle: ""
type: "servers",
streams: streams,
subtitle: null
});
} catch (err) {
return JSON.stringify({
type: "servers",
streams: [],
subtitle: null
});
}
return JSON.stringify({ streams: [], subtitle: "" });
} catch (err) {
return JSON.stringify({ streams: [], subtitle: "" });
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.4",
"version": "1.0.5",
"language": "English (SUB)",
"streamType": "HLS",
"quality": "1080p",
+1 -2
View File
@@ -58,7 +58,7 @@ async function extractEpisodes(url) {
const html = await response.text();
const episodes = [];
const episodeRegex = /<a[^>]+id="([^"]+)"[^>]*>[\s\S]*?<div class='[^']*watch2 bc'[^>]*>(\d+)<\/div>/g;
const episodeRegex = /<a[^>]+id="([^"]+)"[^>]*>[\s\S]*?<div class='watch2[^']*'>(\d+)<\/div>/g;
let match;
while ((match = episodeRegex.exec(html)) !== null) {
@@ -75,7 +75,6 @@ async function extractEpisodes(url) {
episodes.reverse();
console.log(episodes);
return JSON.stringify(episodes);
}
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.7",
"version": "1.0.8",
"language": "English (SUB)",
"streamType": "HLS",
"quality": "720p",
+16 -5
View File
@@ -49,17 +49,28 @@ async function extractEpisodes(url) {
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);
const episodeRegex = /<li[^>]*>\s*<a href="([^"]+)"[^>]*>\s*<div class="epl-num">(\d+)<\/div>/g;
let match;
if (match) {
while ((match = episodeRegex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
number: 1
number: parseInt(match[2], 10)
});
}
return JSON.stringify(results);
if (results.length === 0) {
const singleRegex = /<div class="inepcx">\s*<a href="([^"#]+)">\s*<span>New Episode<\/span>/;
const singleMatch = singleRegex.exec(html);
if (singleMatch) {
results.push({
href: singleMatch[1].trim(),
number: 1
});
}
}
return JSON.stringify(results.reverse());
}
async function extractStreamUrl(url) {
try {
+41 -6
View File
@@ -1,8 +1,13 @@
async function searchResults(keyword) {
const results = [];
const headers = {
"Referer": "https://v3.animelib.org/",
"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"
};
try {
const response = await fetchv2(
"https://hapi.hentaicdn.org/api/anime?fields[]=rate_avg&fields[]=rate&fields[]=releaseDate&q=" + keyword
"https://hapi.hentaicdn.org/api/anime?fields[]=rate_avg&fields[]=rate&fields[]=releaseDate&q=" + keyword,
headers
);
const json = await response.json();
@@ -15,7 +20,7 @@ async function searchResults(keyword) {
title,
image: "https://passthrough-worker.simplepostrequest.workers.dev/?simple=" +
image +
"&referer=https://animelib.org/",
"&referer=https://v3.animelib.org/",
href: item.slug_url || item.slug || item.id,
_score: scoreTitle(title, keyword)
});
@@ -47,16 +52,38 @@ function scoreTitle(title, keyword) {
}
async function extractDetails(slug) {
const headers = {
"Referer": "https://v3.animelib.org/",
"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"
};
try {
const response = await fetchv2(
"https://hapi.hentaicdn.org/api/anime/" + slug +
"?fields[]=background&fields[]=eng_name&fields[]=otherNames&fields[]=summary&fields[]=releaseDate&fields[]=type_id&fields[]=caution&fields[]=views&fields[]=close_view&fields[]=rate_avg&fields[]=rate&fields[]=genres&fields[]=tags&fields[]=teams&fields[]=user&fields[]=franchise&fields[]=authors&fields[]=publisher&fields[]=userRating&fields[]=moderated&fields[]=metadata&fields[]=metadata.count&fields[]=metadata.close_comments&fields[]=anime_status_id&fields[]=time&fields[]=episodes&fields[]=episodes_count&fields[]=episodesSchedule&fields[]=shiki_rate"
);
, headers);
const json = await response.json();
const data = json.data || {};
let description = "No summary available";
if (data.summary) {
if (typeof data.summary === "string") {
description = data.summary;
} else if (data.summary.content && Array.isArray(data.summary.content)) {
description = data.summary.content
.map(block => {
if (block.content && Array.isArray(block.content)) {
return block.content.map(t => t.text || "").join("");
}
return "";
})
.join("\n")
.trim();
}
}
const aliases = Array.isArray(data.otherNames) ? data.otherNames.join(", ") : "";
return JSON.stringify([{
description: data.summary || "No summary available",
description: description,
airdate: data.releaseDate || "Unknown",
aliases: aliases
}]);
@@ -71,8 +98,12 @@ async function extractDetails(slug) {
async function extractEpisodes(slug) {
const results = [];
const headers = {
"Referer": "https://v3.animelib.org/",
"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"
};
try {
const response = await fetchv2("https://hapi.hentaicdn.org/api/episodes?anime_id=" + slug);
const response = await fetchv2("https://hapi.hentaicdn.org/api/episodes?anime_id=" + slug, headers);
const json = await response.json();
if (json && Array.isArray(json.data)) {
@@ -94,9 +125,13 @@ async function extractEpisodes(slug) {
}
async function extractStreamUrl(ID) {
const headers = {
"Referer": "https://v3.animelib.org/",
"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"
};
try {
const url = "https://hapi.hentaicdn.org/api/episodes/" + ID;
const response = await fetchv2(url);
const response = await fetchv2(url, headers);
const json = await response.json();
const data = json.data || {};
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.4",
"version": "1.0.5",
"language": "Russian",
"streamType": "MP4 & HLS",
"quality": "4K",
+65 -15
View File
@@ -56,7 +56,7 @@ async function extractEpisodes(url) {
while ((match = regex.exec(html)) !== null) {
results.push({
href: match[1].trim(),
number: parseInt(match[2], 10)
number: parseFloat(match[2])
});
}
results.reverse();
@@ -65,28 +65,78 @@ async function extractEpisodes(url) {
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const streams = [];
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>/;
const optionRegex = /<option value="([^"]+)"[^>]*>\s*([^<]*Omega[^<]*)\s*<\/option>/gi;
let optionMatch;
while ((optionMatch = optionRegex.exec(html)) !== null) {
const base64Value = optionMatch[1];
const label = optionMatch[2].trim();
let match = html.match(regexSub) || html.match(regexFallback) || html.match(anotherFallbackDawggggWhatsWrongWithTHisWebsite);
if (!match) return null;
if (!base64Value) continue;
const decodedHtml = atob(match[1]); // Decode base64
const iframeMatch = decodedHtml.match(/<iframe\s+src="([^"]+)"/);
try {
const decodedHtml = atob(base64Value);
const iframeMatch = decodedHtml.match(/<iframe[^>]+src=["']([^"']+)["']/i);
if (!iframeMatch) return null;
if (iframeMatch) {
let iframeUrl = iframeMatch[1];
if (iframeUrl.startsWith("//")) iframeUrl = "https:" + iframeUrl;
const streamUrl = iframeMatch[1].startsWith("//") ? "https:" + iframeMatch[1] : iframeMatch[1];
const responseTwo = await fetchv2(streamUrl);
const responseTwo = await fetchv2(iframeUrl);
const htmlTwo = await responseTwo.text();
const m3u8Match = htmlTwo.match(/sources:\s*\[\{file:"([^"]+\.m3u8)"/);
console.error(m3u8Match ? m3u8Match[1] : null);
return m3u8Match ? m3u8Match[1] : null;
const m3u8Match = htmlTwo.match(/sources\s*:\s*\[\s*\{\s*file\s*:\s*['"]([^'"]+master\.m3u8[^'"]*)['"]/i) ||
htmlTwo.match(/file\s*:\s*['"]([^'"]+\.m3u8[^'"]*)['"]/i);
if (m3u8Match) {
streams.push({
title: label,
streamUrl: m3u8Match[1],
headers: {
"Referer": "https://vidmoly.biz/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
});
}
}
} catch (innerErr) {
}
}
return JSON.stringify({
streams: streams,
subtitle: ""
});
} catch (err) {
return JSON.stringify({
streams: [],
subtitle: ""
});
}
}
function atob(input) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let str = '';
let buffer = 0;
let bits = 0;
for (let i = 0; i < input.length; i++) {
const char = input.charAt(i);
if (char === '=') break;
const index = chars.indexOf(char);
if (index === -1) continue;
buffer = (buffer << 6) | index;
bits += 6;
if (bits >= 8) {
bits -= 8;
str += String.fromCharCode((buffer >> bits) & 0xFF);
buffer &= (1 << bits) - 1;
}
}
return str;
}
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.4",
"version": "1.0.5",
"language": "English (SUB/DUB)",
"streamType": "HLS",
"quality": "1080p",
+100 -37
View File
@@ -124,79 +124,142 @@ async function extractStreamUrl(url) {
const responseText = await ddosInterceptor.fetchWithBypass(url);
const dataText = await responseText.text();
const buttonRegex = /<button[^>]*data-src="([^"]+)"[^>]*data-fansub="([^"]+)"[^>]*data-resolution="([^"]+)"[^>]*data-audio="([^"]+)"[^>]*>/g;
const buttons = [];
let match;
while ((match = buttonRegex.exec(dataText)) !== null) {
buttons.push({
src: match[1],
fansub: match[2],
resolution: match[3],
audio: match[4]
});
}
if (buttons.length === 0) {
const buttonMatches = dataText.match(/<button[^>]*data-src="([^"]*)"[^>]*>/g);
if (!buttonMatches) {
return JSON.stringify({ streams: [], subtitle: "" });
}
return JSON.stringify({ streams: [], subtitle: "" });
}
const streamPromises = buttonMatches.map(async (buttonHtml) => {
const srcMatch = buttonHtml.match(/data-src="([^"]*)"/);
const resMatch = buttonHtml.match(/data-resolution="([^"]*)"/);
const audioMatch = buttonHtml.match(/data-audio="([^"]*)"/);
const fansubMatch = buttonHtml.match(/data-fansub="([^"]*)"/);
const deepUnpack = (source) => {
let decoded = source;
let safety = 0;
while (/eval\(function\(p,a,c,k,e,d\)/.test(decoded) && safety < 5) {
try {
decoded = unpack(decoded);
safety++;
} catch (e) {
console.warn("[Animepahe] Unpack error at depth " + safety + ": " + e.message);
break;
}
}
return decoded;
};
if (!srcMatch || !srcMatch[1].includes('kwik.cx')) return null;
const kwikUrl = srcMatch[1];
const resolution = resMatch ? resMatch[1] : "Unknown";
const audio = audioMatch ? audioMatch[1] : "jpn";
const fansub = fansubMatch ? fansubMatch[1] : "Unknown";
const streamPromises = buttons.map(async (btn) => {
const kwikUrl = btn.src;
const audioType = btn.audio === "jpn" ? "Hardsub" : "Dub";
const title = btn.resolution + "p • " + audioType;
try {
const html = await fetchv2(kwikUrl).then(r => r.text());
const scriptMatch = html.match(/<script>(.*?)<\/script>/s);
const headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
"Referer": "https://animepahe.pw/",
"Origin": "https://kwik.cx",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
};
const resp = await fetchv2(kwikUrl, headers);
const html = await resp.text();
const evalRegex = /eval\(function\(p,a,c,k,e,d\)\{[^}]*\}\('[^']*',\d+,\d+,'[^']*'\.split\('\|'\)[^)]*\)/g;
const evalBlocks = [...html.matchAll(evalRegex)].map(m => m[0]);
if (evalBlocks.length === 0) {
const scriptMatch = html.match(/<script>(.*?)<\/script>/s);
if (scriptMatch) {
const scriptContent = scriptMatch[1];
let unpacked = null;
if (scriptContent.includes('));eval(')) {
const parts = scriptContent.split('));eval(');
if (parts.length === 2) {
const layer2Packed = parts[1].substring(0, parts[1].length - 1);
unpacked = unpack(layer2Packed);
try { unpacked = unpack(layer2Packed); } catch(e) {}
}
} else {
unpacked = unpack(scriptContent);
try { unpacked = unpack(scriptContent); } catch(e) {}
}
if (unpacked) {
const urlMatch = unpacked.match(/const source=\\?['"]([^'"]+)['"]/) ||
unpacked.match(/https:\/\/[^\s'";]+\.m3u8/);
if (urlMatch) {
let hlsUrl = (urlMatch[1] || urlMatch[0]).replace(/\\+$/, '');
const audioType = audio === "eng" ? "Dub" : "Hardsub";
const title = `${resolution}p • ${audioType}`;
hlsUrl = hlsUrl
.replace("/stream/", "/hls/")
.replace("uwu.m3u8", "owo.m3u8");
hlsUrl = hlsUrl.replace("/stream/", "/hls/").replace("uwu.m3u8", "owo.m3u8");
return {
title: title,
streamUrl: hlsUrl,
headers: {
"Referer": "https://kwik.cx/",
"Origin": "https://kwik.cx"
}
headers: { "Referer": "https://kwik.cx/", "Origin": "https://kwik.cx" }
};
}
}
}
} catch (e) {
// Continue to next
}
return null;
}
let hlsUrl = null;
for (const block of evalBlocks) {
try {
const unpacked = deepUnpack(block);
console.log("[Animepahe] Unpacked snippet: " + unpacked.substring(0, 150));
const sourceMatch = unpacked.match(/(?:source\s*=\s*['"]([^'"]+\.m3u8)['"])/i);
if (sourceMatch) {
hlsUrl = sourceMatch[1];
break;
}
const directMatch = unpacked.match(/https?:\/\/[^\s'"<>]+\.m3u8[^\s'"<>]*/i);
if (directMatch) {
hlsUrl = directMatch[0];
break;
}
} catch (e) {
}
}
if (!hlsUrl) {
return null;
}
hlsUrl = hlsUrl.replace(/\\+$/, '');
hlsUrl = hlsUrl.replace("/stream/", "/hls/").replace("uwu.m3u8", "owo.m3u8");
return {
title: title,
streamUrl: hlsUrl,
headers: { "Referer": "https://kwik.cx/", "Origin": "https://kwik.cx" }
};
} catch (e) {
return null;
}
});
const streamResults = await Promise.all(streamPromises);
const streams = streamResults.filter(s => s !== null);
const results = await Promise.allSettled(streamPromises);
const streams = results
.filter(r => r.status === "fulfilled" && r.value)
.map(r => r.value);
return JSON.stringify({
streams: streams,
subtitle: ""
streams.sort((a, b) => {
const aIsSub = a.title.includes("Hardsub") ? 0 : 1;
const bIsSub = b.title.includes("Hardsub") ? 0 : 1;
if (aIsSub !== bIsSub) return aIsSub - bIsSub;
const aRes = parseInt(a.title.match(/(\d+)p/)?.[1] || 0);
const bRes = parseInt(b.title.match(/(\d+)p/)?.[1] || 0);
return bRes - aRes;
});
const finalResult = JSON.stringify({ streams: streams, subtitle: "" });
return finalResult;
} catch (err) {
return JSON.stringify({ streams: [], subtitle: "" });
}
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.3",
"version": "1.0.4",
"language": "English",
"streamType": "HLS",
"quality": "1080p",
+57 -39
View File
@@ -29,12 +29,17 @@ async function extractDetails(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const regex = /<div class="view-stocon"><div class="c" id="animeContents">([\s\S]*?)<\/div><\/div>/;
const regex = /<div class="view-stocon"><div class="c"[^>]*>([\s\S]*?)<\/div>/;
let description = "N/A";
const match = regex.exec(html);
if (match) {
description = match[1].replace(/<br>/g, "\n").replace(/&nbsp;/g, " ").replace(/<[^>]*>/g, "").replace(/\n\n+/g, "\n").trim();
description = match[1]
.replace(/<br\s*\/?>/gi, "\n")
.replace(/&nbsp;/g, " ")
.replace(/<[^>]*>/g, "")
.replace(/\n\s*\n+/g, "\n")
.trim();
}
return JSON.stringify([{
@@ -74,57 +79,70 @@ async function extractEpisodes(url) {
}]);
}
}
async function extractStreamUrl(url) {
try {
const response = await fetchv2(url);
const html = await response.text();
const regex = /<input type="hidden" name="vurl" value="https:\/\/michealcdn\.com\/video\/([^"]+)"/;
const match = regex.exec(html);
if (match) {
try {
const videoId = match[1];
console.log("Video ID: " + videoId);
const postData = "hash=" + videoId + "&r=https%3A%2F%2Fmm.viaproducciones.net%2F"
const vMatch = html.match(/<input type="hidden" name="v" value="([^"]+)"/);
if (!vMatch) {
return JSON.stringify({ streams: [], subtitle: "" });
}
const vValue = vMatch[1];
const glamovUrl = `https://ani.glamov.com/tv-show.php?v=${vValue}`;
const glamovResponse = await fetchv2(glamovUrl);
const glamovHtml = await glamovResponse.text();
const iframeMatch = glamovHtml.match(/<iframe src="([^"]+)"/);
if (!iframeMatch) {
return JSON.stringify({ streams: [], subtitle: "" });
}
let iframeUrl = iframeMatch[1];
if (iframeUrl.startsWith("//")) iframeUrl = "https:" + iframeUrl;
const videoIdMatch = iframeUrl.match(/\/video\/([^/?#]+)/);
if (!videoIdMatch) {
return JSON.stringify({ streams: [], subtitle: "" });
}
const videoId = videoIdMatch[1];
let iframeOrigin = "";
const originMatch = iframeUrl.match(/^(https?:\/\/[^/]+)/);
if (originMatch) {
iframeOrigin = originMatch[1];
} else {
return JSON.stringify({ streams: [], subtitle: "" });
}
const apiUrl = `${iframeOrigin}/player/index.php?data=${videoId}&do=getVideo`;
const postData = `hash=${videoId}&r=https%3A%2F%2Fani.glamov.com%2F`;
const headers = {
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
};
const response = await fetchv2("https://michealcdn.com/player/index.php?data=" + videoId + "&do=getVideo", headers, "POST", postData);
const data = await response.json();
console.log("Fetched video data: " + JSON.stringify(data));
const streamUrl = data.securedLink;
const apiResponse = await fetchv2(apiUrl, headers, "POST", postData);
const apiJson = await apiResponse.json();
if (apiJson && apiJson.videoSource) {
return JSON.stringify({
"streams": [
{
"title": "Stream 1",
"streamUrl": streamUrl,
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Referer": "https://michealcdn.com/",
"Origin": "https://michealcdn.com",
"Sec-Fetch-Dest": "video",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"Range": "bytes=0-"
}
}
],
"subtitle": ""
streams: [{
title: "Stream 1",
streamUrl: apiJson.videoSource.replace(/\\\//g, "/"),
headers: {}
}],
subtitle: ""
});
} catch (err) {
console.log("Error fetching video URL:"+ err);
}
return JSON.stringify({"streams": [{"title": "Error", "streamUrl": "https://error.org/", "headers": {}}], "subtitle": ""});
}
return JSON.stringify({"streams": [{"title": "Error", "streamUrl": "https://error.org/", "headers": {}}], "subtitle": ""});
return JSON.stringify({ streams: [], subtitle: "" });
} catch (err) {
return JSON.stringify({"streams": [{"title": "Error", "streamUrl": "https://error.org/", "headers": {}}], "subtitle": ""});
return JSON.stringify({ streams: [], subtitle: "" });
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.8",
"version": "1.0.9",
"language": "Korean",
"streamType": "HLS",
"quality": "1080p",
+1
View File
@@ -131,3 +131,4 @@ async function extractStreamUrl(url) {
subtitles: ""
});
}
+24 -5
View File
@@ -33,7 +33,17 @@ async function extractDetails(url) {
const regex = /<div id="sinopse2">(.*?)<\/div>/s;
const match = regex.exec(html);
const description = match ? match[1].trim() : "N/A";
let description = "N/A";
if (match) {
description = match[1]
.replace(/<br\s*\/?>/gi, "\n")
.replace(/<p[^>]*>/gi, "")
.replace(/<\/p>/gi, "\n")
.replace(/&nbsp;/g, " ")
.replace(/<[^>]*>/g, "")
.replace(/\n\s*\n+/g, "\n")
.trim();
}
return JSON.stringify([{
description: description,
@@ -62,7 +72,7 @@ async function extractEpisodes(url) {
while ((match = epRegex.exec(html)) !== null) {
const href = match[1].trim();
if (href === "https://www.anitube.news") continue;
if (!href.includes("/video/")) continue;
const title = match[2];
const numMatch = /Episódio\s+(\d+)/.exec(title);
@@ -96,13 +106,22 @@ async function extractStreamUrl(url) {
const match = regex.exec(html);
if (!match) {
return "Error: stream not found";
return JSON.stringify({ streams: [], subtitle: "" });
}
const hlsUrl = decodeURIComponent(match[1]);
return hlsUrl;
return JSON.stringify({
streams: [{
title: "Standard",
streamUrl: hlsUrl,
headers: {
"Referer": "https://api.anivideo.net/"
}
}],
subtitle: ""
});
} catch (err) {
return "Error: " + err.message;
return JSON.stringify({ streams: [], subtitle: "" });
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.0.0",
"version": "1.0.1",
"language": "Portuguese",
"streamType": "HLS",
"quality": "1080p",
+50 -180
View File
@@ -76,194 +76,64 @@ async function extractEpisodes(url) {
async function extractStreamUrl(url) {
try {
console.log("Fetching URL: " + url);
const response = await fetchv2(url);
const html = await response.text();
const streams = [];
const fmMatch = html.match(/https:\/\/filemoon\.to\/e\/[a-zA-Z0-9]+/);
if (fmMatch) {
const fmEmbedUrl = fmMatch[0];
const fmResp = await fetchv2(fmEmbedUrl);
const fmHtml = await fmResp.text();
const okMatch = html.match(/https?:\/\/ok\.ru\/(?:videoembed|video)\/\d+/);
console.log("OK.ru match: " + (okMatch ? okMatch[0] : "none"));
return filemoonExtractor(fmHtml, url);
}
if (okMatch) {
let okUrl = okMatch[0].replace("/video/", "/videoembed/");
console.log("Fetching OK.ru Embed: " + okUrl);
const okResp = await fetchv2(okUrl);
const okHtml = await okResp.text();
return "No video found";
} catch (err) {
return "Error";
}
}
let hlsUrl = null;
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 {
const optionsMatch = okHtml.match(/data-options="([^"]+)"/);
if (optionsMatch) {
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);
const optionsJson = optionsMatch[1].replace(/&quot;/g, '"');
const options = JSON.parse(optionsJson);
const metadata = JSON.parse(options.flashvars.metadata);
hlsUrl = metadata.hlsManifestUrl;
} catch (e) {
try {
return await fetch(url, options);
} catch(error) {
return null;
console.log("Failed parsing OK.ru JSON metadata");
}
}
}
/* REMOVE_END */
/* SCHEME END */
if (!hlsUrl) {
const match = okHtml.match(/hlsManifestUrl.*?(https.*?.m3u8.*?)(?:\\\\&quot;|\\&quot;|&quot;|"|\\")/);
if (match) hlsUrl = match[1];
}
console.log("HLS match: " + (hlsUrl ? "Found" : "Not Found"));
if (hlsUrl) {
hlsUrl = hlsUrl
.replace(/\\u0026/g, "&")
.replace(/&amp;/g, "&")
.replace(/\\\//g, "/");
if (hlsUrl.startsWith("//")) hlsUrl = "https:" + hlsUrl;
console.log("Final HLS URL: " + hlsUrl);
streams.push({
title: "OK.ru",
streamUrl: hlsUrl,
headers: { "Referer": "https://ok.ru/" }
});
}
}
return JSON.stringify({
streams: streams,
subtitle: ""
});
} catch (err) {
console.log("Error in extractStreamUrl: " + err.message);
return JSON.stringify({ streams: [], subtitle: "" });
}
}

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