1
0
forked from 50n50/sources

merge upstream

This commit is contained in:
Cufiy
2026-03-09 00:01:25 +00:00
33 changed files with 1915 additions and 7111 deletions
+106
View File
@@ -0,0 +1,106 @@
name: Sync Versions to index.json
on:
push:
branches:
- main
paths:
- '*/*.json'
- '!index.json'
workflow_dispatch:
jobs:
sync-versions:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Sync versions from individual JSON files
run: |
node << 'EOF'
const fs = require('fs');
const path = require('path');
const { readdirSync } = require('fs');
// Read index.json
const indexPath = './index.json';
const indexData = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
// Get all directories in current folder
const entries = readdirSync('.', { withFileTypes: true });
const directoriesWithJsonFiles = entries
.filter(entry => entry.isDirectory() && !entry.name.startsWith('.'))
.map(entry => entry.name)
.filter(dirName => {
const jsonPath = path.join(dirName, `${dirName}.json`);
return fs.existsSync(jsonPath);
});
console.log(`Found ${directoriesWithJsonFiles.length} directories with matching JSON files\n`);
directoriesWithJsonFiles.forEach(dir => {
const filePath = path.join(dir, `${dir}.json`);
try {
const sourceData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
const indexKey = `${dir}/${dir}.json`;
// Check if this entry exists in index.json
if (indexData[indexKey]) {
// Update version in index.json
if (sourceData.version) {
const oldVersion = indexData[indexKey].version;
indexData[indexKey].version = sourceData.version;
if (oldVersion !== sourceData.version) {
console.log(`✓ ${indexKey}: ${oldVersion} → ${sourceData.version}`);
} else {
console.log(`= ${indexKey}: ${sourceData.version} (no change)`);
}
}
} else {
console.log(`⚠ ${indexKey}: Not found in index.json`);
}
} catch (error) {
console.error(`✗ ${filePath}: ${error.message}`);
}
});
// Write updated index.json with pretty formatting
fs.writeFileSync(indexPath, JSON.stringify(indexData, null, 2) + '\n');
console.log('\n✓ index.json updated successfully');
EOF
- name: Check for changes
id: verify
run: |
if git diff --quiet; then
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
git diff
fi
- name: Configure Git
if: steps.verify.outputs.changed == 'true'
run: |
git config user.name "Gitea Actions Bot"
git config user.email "actions@gitea.local"
- name: Commit and push changes
if: steps.verify.outputs.changed == 'true'
run: |
git add index.json
git commit -m "chore: sync version strings from individual JSON files" -m "Automatically updated version fields in index.json to match their respective source files" -m "Generated by sync-versions workflow"
git push origin main
env:
GIT_AUTHOR_NAME: Gitea Actions Bot
GIT_AUTHOR_EMAIL: actions@gitea.local
GIT_COMMITTER_NAME: Gitea Actions Bot
GIT_COMMITTER_EMAIL: actions@gitea.local
+129
View File
@@ -0,0 +1,129 @@
async function searchResults(keyword) {
const results = [];
try {
const response = await fetchv2("https://123animes.ru/search?keyword=" + encodeURIComponent(keyword));
const html = await response.text();
const filmListMatch = html.match(/<div class="film-list">([\s\S]*?)<div class="clearfix"><\/div>/);
if (!filmListMatch) {
return JSON.stringify(results);
}
const filmList = filmListMatch[1];
const itemRegex = /<div class="item">[\s\S]*?<a href="([^"]+)"[^>]*class="poster"[\s\S]*?<img[^>]*alt="([^"]+)"[^>]*src="([^"]+)"/g;
let match;
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()
});
}
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();
let description = "N/A";
let aliases = "N/A";
let airdate = "N/A";
const descMatch = html.match(/<div class="desc">([\s\S]*?)<\/div>/);
if (descMatch) {
description = descMatch[1].replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
}
const aliasMatch = html.match(/<p class="alias">([^<]*)<\/p>/);
if (aliasMatch) {
aliases = aliasMatch[1].trim();
}
const airdateMatch = html.match(/<dt>Released:<\/dt>\s*<dd>\s*<a[^>]*>(\d+)<\/a>/);
if (airdateMatch) {
airdate = airdateMatch[1].trim();
}
return JSON.stringify([{
description: description,
aliases: aliases,
airdate: airdate
}]);
} catch (err) {
return JSON.stringify([{
description: "Error",
aliases: "Error",
airdate: "Error"
}]);
}
}
async function extractEpisodes(url) {
const results = [];
try {
const animeId = url.split('/').pop();
const response = await fetchv2("https://123animes.ru/ajax/film/sv?id=" + animeId);
const jsonData = await response.json();
const html = jsonData.html;
const episodesMatch = html.match(/<ul class="episodes range"[^>]*>([\s\S]*?)<\/ul>/);
if (!episodesMatch) {
return JSON.stringify(results);
}
const episodesHTML = episodesMatch[1];
const episodeRegex = /data-pop='(\d+)'/g;
let match;
const seenEpisodes = new Set();
while ((match = episodeRegex.exec(episodesHTML)) !== null) {
const episodeNum = parseInt(match[1], 10);
if (!seenEpisodes.has(episodeNum)) {
seenEpisodes.add(episodeNum);
results.push({
href: animeId + "/" + episodeNum + "/vidstreaming.io",
number: episodeNum
});
}
}
return JSON.stringify(results);
} catch (err) {
return JSON.stringify([{
href: "Error",
number: "Error"
}]);
}
}
async function extractStreamUrl(ID) {
try {
const response = await fetchv2("https://123animes.ru/ajax/episode/info?epr=" + 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();
const stream = dataTwo.sources;
return stream;
} catch (err) {
return "https://error.org/";
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"sourceName": "123Anime",
"iconUrl": "https://123animes.ru/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",
"language": "English (DUB/HARDSUB)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://play.shipimagesbolt.online/",
"searchBaseUrl": "https://play.shipimagesbolt.online/",
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/123anime/123anime.js",
"type": "anime",
"asyncJS": true,
"softsub": true,
"downloadSupport": true,
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
+23 -9
View File
@@ -1,12 +1,23 @@
// AniLiberty module for Sora (AsyncJS)
// Author: emp0ry
// Version: 1.0.1
// Version: 1.0.2
const IMAGE_HOST = "https://anilibria.top";
const DEFAULT_IMAGE_HOST = "https://aniliberty.top";
function fullImg(path) {
function originFromApi(urlOrBase) {
if (!urlOrBase) return DEFAULT_IMAGE_HOST;
const raw = String(urlOrBase);
const m = raw.match(/^(https?:\/\/[^/]+)\/api\/v1\/?/i);
if (m && m[1]) return m[1];
const m2 = raw.match(/^(https?:\/\/[^/]+)/i);
return (m2 && m2[1]) ? m2[1] : DEFAULT_IMAGE_HOST;
}
function fullImg(path, host) {
if (!path) return;
return path.startsWith("http") ? path : `${IMAGE_HOST}${path}`;
if (path.startsWith("http")) return path;
const base = host || DEFAULT_IMAGE_HOST;
return `${base}${path}`;
}
function pickBestHls(ep) {
@@ -18,8 +29,8 @@ function pickBestHls(ep) {
// ------------------------------------------------------------
async function checkApiStatus() {
const domains = [
"https://anilibria.top/api/v1/",
"https://aniliberty.top/api/v1/",
"https://anilibria.top/api/v1/",
"https://anilibria.wtf/api/v1/"
];
for (const base of domains) {
@@ -29,7 +40,7 @@ async function checkApiStatus() {
if (data?.is_alive || data?.result === "ok") return base;
} catch (_) {}
}
return "https://anilibria.top/api/v1/";
return "https://aniliberty.top/api/v1/";
}
// ------------------------------------------------------------
@@ -38,6 +49,7 @@ async function checkApiStatus() {
async function searchResults(keyword) {
try {
const base = await checkApiStatus();
const origin = originFromApi(base);
const url = `${base}app/search/releases?query=${encodeURIComponent(keyword)}&include=id,name.main,poster.src`;
const res = await fetchv2(url);
@@ -45,7 +57,7 @@ async function searchResults(keyword) {
const out = (Array.isArray(data) ? data : []).map(it => ({
title: it?.name?.main || "Unknown title",
image: fullImg(it?.poster?.src),
image: fullImg(it?.poster?.src, origin),
href:
`${base}anime/releases/${it.id}?` +
[
@@ -99,7 +111,9 @@ async function extractEpisodes(url) {
const res = await fetchv2(url);
const data = await res.json();
const seriesPoster = fullImg(data?.poster?.src);
const origin = originFromApi(url);
const seriesPoster = fullImg(data?.poster?.src, origin);
const eps = Array.isArray(data?.episodes) ? data.episodes : [];
const out = eps.map((ep, idx) => {
@@ -111,7 +125,7 @@ async function extractEpisodes(url) {
: (Number.isFinite(ep?.sort_order) ? ep.sort_order : (idx + 1));
const title = ep?.name ? String(ep.name) : `Episode ${num}`;
const image = fullImg(ep?.preview?.src) || seriesPoster;
const image = fullImg(ep?.preview?.src, origin) || seriesPoster;
// Build skip blocks only if numbers present
const opening = (ep?.opening && Number.isFinite(ep.opening.start) && Number.isFinite(ep.opening.stop))
+5 -5
View File
@@ -1,16 +1,16 @@
{
"sourceName": "AniLiberty",
"iconUrl": "https://anilibria.top/static/apple-touch-icon.png",
"iconUrl": "https://aniliberty.top/static/favicon-96x96.png",
"author": {
"name": "emp0ry",
"icon": "https://avatars.githubusercontent.com/u/64217088"
},
"version": "1.0.2",
"version": "1.0.3",
"language": "Russian",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://anilibria.top/api/v1/",
"searchBaseUrl": "https://anilibria.top/api/v1/app/search/releases?query=%s",
"baseUrl": "https://aniliberty.top/api/v1/",
"searchBaseUrl": "https://aniliberty.top/api/v1/app/search/releases?query=%s",
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/aniliberty/aniliberty.js",
"asyncJS": true,
"streamAsyncJS": false,
@@ -20,4 +20,4 @@
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
}
+8 -3
View File
@@ -7,8 +7,12 @@ async function searchResults(keyword) {
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) {
let title = match[3].trim();
title = title.replace(/\s*\(Uncensored\)\s*$/i, '');
title = title.replace(/\s*BD\s*$/i, '');
title = title.trim();
results.push({
title: match[3].trim(),
title: title,
image: match[2].trim(),
href: "https://animedefenders.me" + match[1].trim()
});
@@ -85,11 +89,12 @@ async function extractStreamUrl(url) {
const response = await fetchv2(url);
const html = await response.text();
const subUrlRegex = /data-url="(https:\/\/ee\.anih1\.top\/bb\/sub[^"]+)"/;
const subUrlRegex = /"actual_url":"([^"]+)"/;
const match = html.match(subUrlRegex);
if (match && match[1]) {
const subUrl = match[1];
const subUrl = match[1].replace(/\\\//g, '/');
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"
+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.2",
"language": "English (SUB)",
"streamType": "HLS",
"quality": "1080p",
+324
View File
@@ -0,0 +1,324 @@
async function searchResults(query) {
const encodeQuery = keyword => encodeURIComponent(keyword);
const searchBaseUrl = "https://anikai.to/browser?keyword=";
const baseUrl = "https://anikai.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("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(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: "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(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("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(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(url) {
try {
const actualUrl = url.replace("Animekai:", "").trim();
const htmlText = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl))).text();
const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
if (!animeIdMatch) return JSON.stringify([{ error: "AniID not found" }]);
const tokenResponse = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(animeIdMatch)}`);
const tokenData = await tokenResponse.json();
const token = tokenData.result;
const episodeListUrl = `https://anikai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
const episodeListData = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(episodeListUrl))).json();
const cleanedHtml = cleanJsonHtml(episodeListData.result);
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];
const episodes = episodeMatches.map(([_, episodeNum, episodeToken]) => ({
number: parseInt(episodeNum, 10),
href: `https://anikai.to/ajax/links/list?token=${episodeToken}&_=ENCRYPT_ME`
}));
return JSON.stringify(episodes);
} catch (err) {
console.error("Error fetching episodes:" + err);
return [{
number: 1,
href: "Error fetching episodes"
}];
}
}
async function extractStreamUrl(url) {
let actualUrl = url;
try {
const tokenMatch = actualUrl.match(/token=([^&]+)/);
if (tokenMatch && tokenMatch[1]) {
const rawToken = tokenMatch[1];
const encryptResponse = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(rawToken)}`);
const encryptData = await encryptResponse.json();
const encryptedToken = encryptData.result;
actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
}
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl));
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 tokenPromises = tokenRequestData.map(item =>
fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(item.data)}`)
.then(res => res.json())
.then(json => ({ name: item.name, data: json.result }))
.catch(err => ({ name: item.name, error: err.toString() }))
);
const tokenResults = await Promise.all(tokenPromises);
const streamUrls = tokenResults.map(result => {
const serverIdMap = {
"Dub": serverIdDub,
"Softsub": serverIdSoftsub,
"Sub": serverIdSub
};
return {
type: result.name,
url: `https://anikai.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("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(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 decryptPromises = decryptRequestData.map(item =>
fetchv2(`https://enc-dec.app/api/dec-kai?text=${encodeURIComponent(item.data)}`)
.then(res => res.json())
.then(json => ({ name: item.name, data: JSON.stringify(json.result) }))
.catch(err => ({ name: item.name, error: err.toString() }))
);
const decryptResults = await Promise.all(decryptPromises);
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;
const decryptedDub = decryptedUrls.Dub;
const decryptedRaw = decryptedUrls.Softsub;
const headers = {
"Referer": "https://anikai.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"
};
async function getStream(url) {
try {
const response = await fetchv2(url.replace("/e/", "/media/"), headers);
const responseJson = await response.json();
const result = responseJson?.result;
const postData = {
"text": result,
"agent": headers["User-Agent"]
};
const finalResponse = await fetchv2(
"https://enc-dec.app/api/dec-mega",
{ "Content-Type": "application/json" },
"POST",
JSON.stringify(postData)
);
const finalJson = await finalResponse.json();
return finalJson?.result?.sources?.[0]?.file || null;
} catch {
return null;
}
}
const streams = [];
const [subStream, dubStream, rawStream] = await Promise.all([
decryptedSub ? getStream(decryptedSub) : Promise.resolve(null),
decryptedDub ? getStream(decryptedDub) : Promise.resolve(null),
decryptedRaw ? getStream(decryptedRaw) : Promise.resolve(null)
]);
if (subStream) streams.push({ title: "Hardsub English", streamUrl: subStream });
if (dubStream) streams.push({ title: "Dubbed English", streamUrl: dubStream });
if (rawStream) streams.push({ title: "Original audio", streamUrl: rawStream });
const final = {
streams,
subtitles: ""
};
console.log("RETURN: " + JSON.stringify(final));
return JSON.stringify(final);
} catch (error) {
console.log("Animekai 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, " ");
}
+22
View File
@@ -0,0 +1,22 @@
{
"sourceName": "AnimeKai",
"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.0",
"language": "English",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animekai.to/",
"searchBaseUrl": "https://animekai.to/",
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animekai/animekai.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true,
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}
+9 -8
View File
@@ -225,23 +225,23 @@ async function extractStreamUrl(url) {
const decryptedUrls = await processStreams(streamUrls);
const decryptedSoftsub = decryptedUrls.Softsub || decryptedUrls.Dub || decryptedUrls.Sub;
console.log(decryptedSoftsub);
const headers = {
"Referer": "https://anikai.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 (decryptedSoftsub) {
const response = await fetchv2(decryptedSoftsub.replace("/e/", "/media/"), headers);
const mediaUrl = decryptedSoftsub.replace("/e/", "/media/");
const response = await fetchv2(mediaUrl, 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"
"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 finalResponse = await fetchv2("https://ilovekai.simplepostrequest.workers.dev/ilovebush", {}, "POST", JSON.stringify(postData));
const finalResponse = await fetchv2("https://enc-dec.app/api/dec-mega", { "Content-Type": "application/json" }, "POST", JSON.stringify(postData));
const finalJson = await finalResponse.json();
const m3u8Link = finalJson?.result?.sources?.[0]?.file;
@@ -258,10 +258,11 @@ async function extractStreamUrl(url) {
}
}
return JSON.stringify({
stream: m3u8Link,
subtitles: "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url="+ encodeURIComponent(subtitleUrl)
});
const finalResult = {
stream: m3u8Link,
subtitles: subtitleUrl ? "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url="+ encodeURIComponent(subtitleUrl) : null
};
return JSON.stringify(finalResult);
}
return "error";
+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": "Arabic",
"streamType": "HLS",
"quality": "1080p",
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.1.1",
"version": "1.1.3",
"language": "English",
"streamType": "HLS",
"quality": "1080p",
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "emp0ry",
"icon": "https://avatars.githubusercontent.com/u/64217088"
},
"version": "1.0.0",
"version": "1.0.1",
"language": "Russian",
"streamType": "MP4",
"quality": "720p",
+1 -61
View File
@@ -1,7 +1,3 @@
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";
@@ -147,8 +143,6 @@ async function extractEpisodes(url) {
}
async function extractStreamUrl(url) {
if (!_0xCheck()) return "https://files.catbox.moe/avolvc.mp4";
try {
const response = await soraFetch(url);
const html = await response.text();
@@ -182,58 +176,4 @@ async function soraFetch(
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
))(_);
}
}
+3 -2
View File
@@ -5,7 +5,7 @@
"name": "sobet",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRQPQ1qIiALbM3xDWGsuJzu6ItaQGwb9ICRRw&s"
},
"version": "1.0.4",
"version": "1.0.5",
"language": "Italian",
"streamType": "MP4",
"quality": "1080p",
@@ -16,5 +16,6 @@
"asyncJS": true,
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
"supportsLuna": true,
"downloadSupport": false
}
+32 -2
View File
@@ -15,6 +15,36 @@ const allowEnglishInLanguages = false; // [true, false]
const removeUnknownLanguages = false; // [true, false]
// Settings end
function btoa(str) {
if (typeof globalThis !== "undefined" && typeof globalThis.btoa === "function" && globalThis.btoa !== btoa) {
return globalThis.btoa(str);
}
if (typeof Buffer !== "undefined") {
return Buffer.from(str, "utf8").toString("base64");
}
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let result = "";
let i = 0;
while (i < str.length) {
const byte1 = str.charCodeAt(i++) & 0xff;
const hasByte2 = i < str.length;
const byte2 = hasByte2 ? str.charCodeAt(i++) & 0xff : 0;
const hasByte3 = i < str.length;
const byte3 = hasByte3 ? str.charCodeAt(i++) & 0xff : 0;
const chunk = (byte1 << 16) | (byte2 << 8) | byte3;
result += chars[(chunk >> 18) & 63];
result += chars[(chunk >> 12) & 63];
result += hasByte2 ? chars[(chunk >> 6) & 63] : "=";
result += hasByte3 ? chars[chunk & 63] : "=";
}
return result;
}
async function searchResults(keyword) {
try {
const moviesresponse = await fetchv2(
@@ -186,8 +216,8 @@ async function extractStreamUrl(ID) {
try {
const endpoint = type === "movie"
? "https://comet.elfhosted.com/" + encodedConfig + "/stream/movie/" + actualID + ".json"
: "https://comet.elfhosted.com/" + encodedConfig + "/stream/series/" + actualID + ".json";
? "https://comet.feels.legal/" + encodedConfig + "/stream/movie/" + actualID + ".json"
: "https://comet.feels.legal/" + encodedConfig + "/stream/series/" + actualID + ".json";
const response = await fetchv2(endpoint);
const data = await response.json();
+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": "MKV",
"quality": "4K",
+6 -6
View File
@@ -1,4 +1,4 @@
async function searchContent(keyword, page = 0) {
async function searchResults(keyword, page = 0) {
try {
const response = await fetch("https://comix.to/api/v2/manga?order[relevance]=desc&keyword=" + encodeURIComponent(keyword) + "&limit=100");
const data = await response.json();
@@ -15,7 +15,7 @@ async function searchContent(keyword, page = 0) {
}
}
async function getContentData(slug) {
async function extractDetails(slug) {
try {
const response = await fetch("https://comix.to/title/" + slug);
const html = await response.text();
@@ -54,7 +54,7 @@ async function getContentData(slug) {
}
}
async function getChapters(url) {
async function extractChapters(url) {
const hash = url.split("-")[0]
const results = [];
try {
@@ -97,17 +97,17 @@ async function getChapters(url) {
}
}
async function getChapterImages(url) {
async function extractImages(url) {
const results = [];
try {
const response = await fetch("https://comix.to/title/" + url);
const html = await response.text();
const match = html.match(/\\"images\\":\[([^\]]+)\]/);
if (match) {
const imagesJson = '[' + match[1].replace(/\\"/g, '"') + ']';
const images = JSON.parse(imagesJson);
const imageUrls = images.map(img => img.url);
const imageUrls = images.map(img => `https://passthrough-worker.simplepostrequest.workers.dev/?simple=${encodeURIComponent(img.url)}&referer=comix.to`);
results.push(...imageUrls);
return results;
} else {
+44
View File
@@ -1,4 +1,48 @@
{
"123anime/123anime.json": {
"sourceName": "123Anime",
"iconUrl": "https://123animes.ru/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",
"language": "English (DUB/HARDSUB)",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://play.shipimagesbolt.online/",
"searchBaseUrl": "https://play.shipimagesbolt.online/",
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/123anime/123anime.js",
"type": "anime",
"asyncJS": true,
"softsub": true,
"downloadSupport": true,
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
},
"animekai/animekai.json": {
"sourceName": "AnimeKai",
"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.0",
"language": "English",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://animekai.to/",
"searchBaseUrl": "https://animekai.to/",
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animekai/animekai.js",
"type": "anime",
"asyncJS": true,
"softsub": false,
"downloadSupport": true,
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
},
"kickassanimes/kickassanimes.json": {
"sourceName": "KickAssAnimes",
"iconUrl": "https://files.catbox.moe/qh522x.png",
+15 -15
View File
@@ -5,13 +5,13 @@ async function searchResults(keyword) {
"Content-Type": "application/json"
};
try {
const response = await fetchv2("https://kickass-anime.ro/api/search", headers, "POST", postData);
const response = await fetchv2("https://kaa.lt/api/search", headers, "POST", postData);
const data = await response.json();
data.forEach(item => {
results.push({
title: item.title_en || item.title,
image: `https://kickass-anime.ro/image/poster/${item.poster.hq}.webp`,
image: `https://kaa.lt/image/poster/${item.poster.hq}.webp`,
href: `${item.slug}`
});
});
@@ -28,28 +28,28 @@ async function searchResults(keyword) {
async function extractDetails(slug) {
try {
const response = await fetchv2("https://kickass-anime.ro/" + slug);
const response = await fetchv2("https://kaa.lt/" + slug);
const html = await response.text();
const synopsisMatch = html.match(/f\.synopsis\s*=\s*"([^"]*)"/);
const description = synopsisMatch ? synopsisMatch[1] : "N/A";
const synopsisMatch = html.match(/g\.synopsis\s*=\s*"([^"\\]*(?:\\.[^"\\]*)*)"/);
const description = synopsisMatch ? synopsisMatch[1].replace(/\\n/g, "\n") : "N/A";
const aliasesMatch = html.match(/f\.title_original\s*=\s*"([^"]*)"/);
const aliasesMatch = html.match(/g\.title_original\s*=\s*"([^"]*)"/);
const aliases = aliasesMatch ? aliasesMatch[1] : "N/A";
const airdateMatch = html.match(/f\.start_date\s*=\s*"([^"]*)"/);
const airdate = airdateMatch ? airdateMatch[1] : "N/A";
const airdateMatch = html.match(/g\.start_date\s*=\s*"([^"]*)"/);
const airdate = airdateMatch ? airdateMatch[1].split('T')[0] : "N/A";
return JSON.stringify([{
description: description,
aliases: aliases,
airdate: airdate
airdate: airdate,
}]);
} catch (err) {
return JSON.stringify([{
description: "Error",
aliases: "Error",
airdate: "Error"
airdate: "Error",
}]);
}
}
@@ -58,9 +58,9 @@ async function extractEpisodes(slug) {
const results = [];
try {
const cleanSlug = slug.replace("anime/", "");
const responseEn = await fetchv2(`https://kickass-anime.ro/api/show/${cleanSlug}/episodes?ep=1&page=1&lang=en-US`);
const responseEn = await fetchv2(`https://kaa.lt/api/show/${cleanSlug}/episodes?ep=1&page=1&lang=en-US`);
const dataEn = await responseEn.json();
const responseJa = await fetchv2(`https://kickass-anime.ro/api/show/${cleanSlug}/episodes?ep=1&page=1&lang=ja-JP`);
const responseJa = await fetchv2(`https://kaa.lt/api/show/${cleanSlug}/episodes?ep=1&page=1&lang=ja-JP`);
const dataJa = await responseJa.json();
const enMap = {};
@@ -72,7 +72,7 @@ async function extractEpisodes(slug) {
const enSlug = enMap[episode.episode_number];
const hrefSlug = enSlug ? `${enSlug}|${episode.slug}` : episode.slug;
results.push({
href: `https://kickass-anime.ro/${cleanSlug}/ep-${episode.episode_number}-${hrefSlug}`,
href: `https://kaa.lt/${cleanSlug}/ep-${episode.episode_number}-${hrefSlug}`,
number: episode.episode_number
});
});
@@ -125,7 +125,7 @@ async function extractStreamUrl(url) {
let enDetails = null;
if (enSlug) {
const enUrl = `https://kickass-anime.ro/${cleanSlug}/ep-${number}-${enSlug}`;
const enUrl = `https://kaa.lt/${cleanSlug}/ep-${number}-${enSlug}`;
const src = await fetchSrc(enUrl);
if (src) {
enDetails = await fetchDetails(src);
@@ -141,7 +141,7 @@ async function extractStreamUrl(url) {
}
}
const jaUrl = `https://kickass-anime.ro/${cleanSlug}/ep-${number}-${jaSlug}`;
const jaUrl = `https://kaa.lt/${cleanSlug}/ep-${number}-${jaSlug}`;
const jaSrc = await fetchSrc(jaUrl);
let subtitle = "https://placeholder.com/subtitles.vtt";
if (jaSrc) {
+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": "English",
"streamType": "HLS",
"quality": "1080p",
+126 -127
View File
@@ -1,155 +1,154 @@
async function searchResults(input,page=0){
function parseSearchResults(html) {
const results = [];
const regex = /<div class="unit item-\d+">[\s\S]*?<a href="\/manga\/([\w\-\.]+)"[^>]*class="poster"[\s\S]*?<img src="([^"]*)"[^>]*alt="([^"]*)"/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3],
imageURL: match[2],
id: match[1]
});
async function searchResults(input, page = 0) {
function parseSearchResults(html) {
const results = [];
const regex = /<div class="unit item-\d+">[\s\S]*?<a href="\/manga\/([\w\-\.]+)"[^>]*class="poster"[\s\S]*?<img src="([^"]*)"[^>]*alt="([^"]*)"/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3],
imageURL: match[2],
id: match[1]
});
}
return results;
}
return results;
} const vrf = generate_vrf(input);
const response = await fetch("https://mangafire.to/filter?keyword=" + encodeURIComponent(input) + "&vrf=" + vrf);
const data = await response.text();
console.log(JSON.stringify(parseSearchResults(data)));
return parseSearchResults(data);
const vrf = generate_vrf(input);
const response = await fetch("https://mangafire.to/filter?keyword=" + encodeURIComponent(input) + "&vrf=" + vrf);
const data = await response.text();
console.log(JSON.stringify(parseSearchResults(data)));
return parseSearchResults(data);
}
async function extractDetails(url) {
function parseHtmlData(htmlContent) {
const genreRegex = /<a href="\/genre\/[^"]*">([^<]+)<\/a>/g;
const tags = [];
let match;
function parseHtmlData(htmlContent) {
const genreRegex = /<a href="\/genre\/[^"]*">([^<]+)<\/a>/g;
const tags = [];
let match;
while ((match = genreRegex.exec(htmlContent)) !== null) {
if (match[1].trim()) tags.push(match[1].trim());
while ((match = genreRegex.exec(htmlContent)) !== null) {
if (match[1].trim()) tags.push(match[1].trim());
}
const uniqueTags = [...new Set(tags)];
const ogDescriptionRegex = /<meta property="og:description" content=['"]([^'"]*)['"]/i;
const ogMatch = htmlContent.match(ogDescriptionRegex);
let description = ogMatch ? ogMatch[1] : "";
if (!description) {
const metaDescriptionRegex = /<meta name="description" content=['"]([^'"]*)['"]/i;
const metaMatch = htmlContent.match(metaDescriptionRegex);
description = metaMatch ? metaMatch[1] : "";
}
description = description
.replace(/(&quot;)/g, '"')
.replace(/(&amp;)/g, '&')
.replace(/(&lt;)/g, '<')
.replace(/(&gt;)/g, '>')
.replace(/\s+/g, ' ')
.trim();
if (uniqueTags.length === 0) {
uniqueTags.push("Unknown");
}
return {
description: description,
tags: uniqueTags
};
}
const uniqueTags = [...new Set(tags)];
const ogDescriptionRegex =
/<meta property="og:description" content=['"]([^'"]*)['"]/i;
const ogMatch = htmlContent.match(ogDescriptionRegex);
let description = ogMatch ? ogMatch[1] : "";
if (!description) {
const metaDescriptionRegex =
/<meta name="description" content=['"]([^'"]*)['"]/i;
const metaMatch = htmlContent.match(metaDescriptionRegex);
description = metaMatch ? metaMatch[1] : "";
}
description = description
.replace(/(&quot;)/g, '"')
.replace(/(&amp;)/g, '&')
.replace(/(&lt;)/g, '<')
.replace(/(&gt;)/g, '>')
.replace(/\s+/g, ' ')
.trim();
if (uniqueTags.length === 0) {
uniqueTags.push("Unknown");
}
return {
description: description,
tags: uniqueTags
};
}
const response = await fetch(`https://mangafire.to${url}`);
const data = await response.text();
console.log(JSON.stringify(parseHtmlData(data)));
return parseHtmlData(data);
const response = await fetch(`https://mangafire.to${url}`);
const data = await response.text();
console.log(JSON.stringify(parseHtmlData(data)));
return parseHtmlData(data);
}
async function extractChapters(url) {
const mangaIdMatch = url.match(/\.([a-z0-9]+)$/);
const mangaId = mangaIdMatch ? mangaIdMatch[1] : null;
const mangaIdMatch = url.match(/\.([a-z0-9]+)$/);
const mangaId = mangaIdMatch ? mangaIdMatch[1] : null;
vrf = generate_vrf(`${mangaId}@chapter@en`);
if (!mangaId) {
console.error("Could not extract manga ID from URL");
return null;
}
if (!mangaId) {
console.error("Could not extract manga ID from URL");
return null;
}
const vrf = generate_vrf(`${mangaId}@chapter@en`);
function parseChapters(htmlContent) {
const chapters = {};
const chapters = {};
const chapterRegex =
/<a href="\/read\/[^"]+\/([^/]+)\/chapter-[^"]*" data-number="([^"]+)" data-id="([^"]+)"[^>]*>(.*?)<\/a>/gs;
const chapterRegex = /<a href="\/read\/[^"]+\/([^/]+)\/chapter-[^"]*" data-number="([^"]+)" data-id="([^"]+)"[^>]*>(.*?)<\/a>/gs;
let match;
while ((match = chapterRegex.exec(htmlContent)) !== null) {
const langCode = match[1];
const chapterNumber = match[2];
const chapterId = match[3];
const title = match[4].replace(/<[^>]*>/g, '').trim();
let match;
while ((match = chapterRegex.exec(htmlContent)) !== null) {
const langCode = match[1];
const chapterNumber = match[2];
const chapterId = match[3];
const title = match[4].replace(/<[^>]*>/g, '').trim();
if (!chapters[langCode]) chapters[langCode] = [];
if (!chapters[langCode]) chapters[langCode] = [];
chapters[langCode].push([
chapterNumber,
[
{
id: chapterId,
title: title,
chapter: Number(chapterNumber),
scanlation_group: "Mangafire"
}
]
]);
chapters[langCode].push([
chapterNumber,
[
{
id: chapterId,
title: title,
chapter: Number(chapterNumber),
scanlation_group: "Mangafire"
}
]
]);
}
Object.keys(chapters).forEach(lang => chapters[lang].reverse());
return chapters;
}
Object.keys(chapters).forEach(lang => chapters[lang].reverse());
try {
const response = await fetch(`https://mangafire.to/ajax/read/${mangaId}/chapter/en?vrf=${vrf}`);
const data = await response.json();
return chapters;
if (data.status === 200 && data.result && data.result.html) {
const chapters = parseChapters(data.result.html);
console.log(JSON.stringify(chapters));
return chapters;
} else {
console.error("Invalid response from server");
return null;
}
} catch (error) {
console.error("Error fetching chapters:" + error);
return null;
}
try {
const response = await fetch(`https://mangafire.to/ajax/read/${mangaId}/chapter/en?vrf=${vrf}`);
const data = await response.json();
if (data.status === 200 && data.result && data.result.html) {
const chapters = parseChapters(data.result.html);
console.log(JSON.stringify(chapters));
return chapters;
} else {
console.error("Invalid response from server");
return null;
}
} catch (error) {
console.error("Error fetching chapters:" + error);
return null;
}
}
async function extractImages(ID) {
vrf = generate_vrf(`chapter@${ID}`);
async function extractImages(ID) {
const vrf = generate_vrf(`chapter@${ID}`);
try {
const response = await fetch(`https://mangafire.to/ajax/read/chapter/${ID}?vrf=${vrf}`);
const data = await response.json();
if (data.status === 200 && data.result && data.result.images) {
const images = data.result.images.map(img => img[0]);
console.log(JSON.stringify(images));
return images;
} else {
console.error("Invalid response from server");
return null;
try {
const response = await fetch(`https://mangafire.to/ajax/read/chapter/${ID}?vrf=${vrf}`);
const data = await response.json();
if (data.status === 200 && data.result && data.result.images) {
const images = data.result.images.map(img => img[0]);
console.log(JSON.stringify(images));
return images;
} else {
console.error("Invalid response from server");
return null;
}
} catch (error) {
console.error("Error fetching images:" + error);
return null;
}
} catch (error) {
console.error("Error fetching chapters:" + error);
return null;
}
}
function b64encode(data) {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"sourceName": "MangaFire",
"iconURL": "https://s.mfcdn.cc/assets/sites/mangafire/favicon.png?v3",
"version": "1.0",
"version": "1.0.1",
"language": "English",
"scriptURL": "https://git.luna-app.eu/50n50/sources/raw/branch/main/mangafire/mangafire.js",
"author": {
+1 -1
View File
@@ -47,7 +47,7 @@ async function extractChapters(url) {
const html = await response.text();
const regex = /<tr>\s*<td><a href="([^"]+)">([^<]+)<\/a><\/td>\s*<td>[^<]+<\/td>\s*<\/tr>/g;
let match;
let index = 0;
let index = 1;
while ((match = regex.exec(html)) !== null) {
results.push([
+1 -1
View File
@@ -1,7 +1,7 @@
{
"sourceName": "MangaFreak",
"iconURL": "https://files.catbox.moe/903u7e.png",
"version": "1.0",
"version": "1.0.1",
"language": "English",
"scriptURL": "https://git.luna-app.eu/50n50/sources/raw/branch/main/mangafreak/mangafreak.js",
"author": {
+203 -192
View File
@@ -1,230 +1,241 @@
async function getLandingWebsiteHref() {
var response = await soraFetch("https://previtera.vercel.app/");
var href = await response.text();
return href;
}
async function searchResults(keyword) {
const response = await soraFetch(
`https://streamingcommunityz.kitchen/it/archive?search=${keyword}`
);
const html = await response.text();
const landingUrl = await getLandingWebsiteHref()
const regex = /<div[^>]*id="app"[^>]*data-page="([^"]*)"/;
const match = regex.exec(html);
if (!match || !match[1]) {
return JSON.stringify([]);
}
const dataPage = match[1].replaceAll(`&quot;`, `"`);
const pageData = JSON.parse(dataPage);
const titles = pageData.props?.titles || [];
const results =
titles
.map((item) => {
const posterImage = item.images?.find((img) => img.type === "poster");
return {
title:
item.name?.replaceAll("amp;", "").replaceAll("&#39;", "'") || "",
image: posterImage?.filename
? `https://cdn.streamingcommunityz.kitchen/images/${posterImage.filename}`
: "",
href: `https://streamingcommunityz.kitchen/it/titles/${item.id}-${item.slug}`,
};
})
.filter((item) => item.image) || [];
return JSON.stringify(results);
}
async function extractDetails(url) {
const response = await soraFetch(`${url}/season-1`);
const html = await response.text();
const regex = /<div[^>]*id="app"[^>]*data-page="([^"]*)"/;
const match = regex.exec(html);
if (!match || !match[1]) {
return JSON.stringify([]);
}
const dataPage = match[1].replaceAll(`&quot;`, `"`);
const pageData = JSON.parse(dataPage);
const titleData = pageData.props?.title;
if (!titleData) {
return JSON.stringify([]);
}
return JSON.stringify([
{
description:
titleData.plot?.replaceAll("amp;", "").replaceAll("&#39;", "'") ||
"N/A",
aliases:
titleData.original_name
?.replaceAll("amp;", "")
.replaceAll("&#39;", "'") || "N/A",
airdate: titleData.release_date || "N/A",
},
]);
}
async function extractEpisodes(url) {
try {
const episodes = [];
const baseUrl = url.replace(/\/season-\d+$/, "");
const response = await soraFetch(`${baseUrl}/season-1`);
const response = await soraFetch(
`https://${landingUrl}/it/archive?search=${keyword}`
);
const html = await response.text();
const regex = /<div[^>]*id="app"[^>]*data-page="([^"]*)"/;
const match = regex.exec(html);
if (!match?.[1]) return JSON.stringify([]);
if (!match || !match[1]) {
return JSON.stringify([]);
}
const pageData = JSON.parse(match[1].replaceAll(`&quot;`, `"`));
const dataPage = match[1].replaceAll(`&quot;`, `"`);
const pageData = JSON.parse(dataPage);
const titles = pageData.props?.titles || [];
const results =
titles
.map((item) => {
const posterImage = item.images?.find((img) => img.type === "poster");
return {
title: item.name?.replaceAll("amp;", "").replaceAll("&#39;", "'") || "",
image: posterImage?.filename ?
`https://cdn.${landingUrl}/images/${posterImage.filename}` :
"",
href: `https://${landingUrl}/it/titles/${item.id}-${item.slug}`,
};
})
.filter((item) => item.image) || [];
return JSON.stringify(results);
}
async function extractDetails(url) {
const baseUrl = await getLandingWebsiteHref()
const response = await soraFetch(`${url}/season-1`);
const html = await response.text();
const regex = /<div[^>]*id="app"[^>]*data-page="([^"]*)"/;
const match = regex.exec(html);
if (!match || !match[1]) {
return JSON.stringify([]);
}
const dataPage = match[1].replaceAll(`&quot;`, `"`);
const pageData = JSON.parse(dataPage);
const titleData = pageData.props?.title;
if (!titleData) return JSON.stringify([]);
const titleId = titleData.id;
const totalSeasons = titleData.seasons_count || 1;
if (!titleData) {
return JSON.stringify([]);
}
let hasEpisodes = false;
return JSON.stringify([{
description: titleData.plot?.replaceAll("amp;", "").replaceAll("&#39;", "'") ||
"N/A",
aliases: titleData.original_name
?.replaceAll("amp;", "")
.replaceAll("&#39;", "'") || "N/A",
airdate: titleData.release_date || "N/A",
}, ]);
}
for (let season = 1; season <= totalSeasons; season++) {
try {
const seasonResponse = await soraFetch(`${baseUrl}/season-${season}`);
const seasonHtml = await seasonResponse.text();
const seasonMatch = regex.exec(seasonHtml);
async function extractEpisodes(url) {
try {
const landingUrl = await getLandingWebsiteHref()
if (seasonMatch?.[1]) {
const seasonData = JSON.parse(
seasonMatch[1].replaceAll(`&quot;`, `"`)
);
const seasonEpisodes = seasonData.props?.loadedSeason?.episodes || [];
const episodes = [];
const baseUrl = url.replace(/\/season-\d+$/, "");
if (seasonEpisodes.length > 0) {
hasEpisodes = true;
seasonEpisodes.forEach((episode) => {
episodes.push({
href: `https://streamingcommunityz.kitchen/it/iframe/${titleId}?episode_id=${episode.id}`,
number: episode.number || episodes.length + 1,
});
});
}
const response = await soraFetch(`${baseUrl}/season-1`);
const html = await response.text();
const regex = /<div[^>]*id="app"[^>]*data-page="([^"]*)"/;
const match = regex.exec(html);
if (!match?.[1]) return JSON.stringify([]);
const pageData = JSON.parse(match[1].replaceAll(`&quot;`, `"`));
const titleData = pageData.props?.title;
if (!titleData) return JSON.stringify([]);
const titleId = titleData.id;
const totalSeasons = titleData.seasons_count || 1;
let hasEpisodes = false;
for (let season = 1; season <= totalSeasons; season++) {
try {
const seasonResponse = await soraFetch(`${baseUrl}/season-${season}`);
const seasonHtml = await seasonResponse.text();
const seasonMatch = regex.exec(seasonHtml);
if (seasonMatch?.[1]) {
const seasonData = JSON.parse(
seasonMatch[1].replaceAll(`&quot;`, `"`)
);
const seasonEpisodes = seasonData.props?.loadedSeason?.episodes || [];
if (seasonEpisodes.length > 0) {
hasEpisodes = true;
seasonEpisodes.forEach((episode) => {
episodes.push({
href: `https://${landingUrl}/it/iframe/${titleId}?episode_id=${episode.id}`,
number: episode.number || episodes.length + 1,
});
});
}
}
} catch (error) {
console.log(`Error fetching season ${season}:`, error);
}
}
} catch (error) {
console.log(`Error fetching season ${season}:`, error);
}
}
if (!hasEpisodes) {
episodes.push({
href: `https://streamingcommunityz.kitchen/it/iframe/${titleId}`,
number: 1,
});
}
if (!hasEpisodes) {
episodes.push({
href: `https://${landingUrl}/it/iframe/${titleId}`,
number: 1,
});
}
return JSON.stringify(episodes);
} catch (error) {
console.log("Error extracting episodes:", error);
return JSON.stringify([]);
}
return JSON.stringify(episodes);
} catch (error) {
console.log("Error extracting episodes:", error);
return JSON.stringify([]);
}
}
async function extractStreamUrl(url) {
try {
let modifiedUrl = url;
if (!url.includes("/it/iframe") && !url.includes("/en/iframe")) {
modifiedUrl = url.replace("/iframe", "/it/iframe");
}
const response1 = await soraFetch(modifiedUrl);
const html1 = await response1.text();
try {
let modifiedUrl = url;
if (!url.includes("/it/iframe") && !url.includes("/en/iframe")) {
modifiedUrl = url.replace("/iframe", "/it/iframe");
}
const response1 = await soraFetch(modifiedUrl);
const html1 = await response1.text();
const iframeMatch = html1.match(/<iframe[^>]*src="([^"]*)"/);
if (!iframeMatch) {
console.log("No iframe found in the HTML.");
return null;
}
const iframeMatch = html1.match(/<iframe[^>]*src="([^"]*)"/);
if (!iframeMatch) {
console.log("No iframe found in the HTML.");
return null;
}
const embedUrl = iframeMatch[1].replace(/amp;/g, "");
console.log("Embed URL:", embedUrl);
const embedUrl = iframeMatch[1].replace(/amp;/g, "");
console.log("Embed URL:", embedUrl);
const response2 = await soraFetch(embedUrl);
const html2 = await response2.text();
const response2 = await soraFetch(embedUrl);
const html2 = await response2.text();
let finalUrl = null;
let finalUrl = null;
if (html2.includes("window.masterPlaylist")) {
const urlMatch = html2.match(/url:\s*['"]([^'"]+)['"]/);
const tokenMatch = html2.match(/['"]?token['"]?\s*:\s*['"]([^'"]+)['"]/);
const expiresMatch = html2.match(
/['"]?expires['"]?\s*:\s*['"]([^'"]+)['"]/
);
if (html2.includes("window.masterPlaylist")) {
const urlMatch = html2.match(/url:\s*['"]([^'"]+)['"]/);
const tokenMatch = html2.match(/['"]?token['"]?\s*:\s*['"]([^'"]+)['"]/);
const expiresMatch = html2.match(
/['"]?expires['"]?\s*:\s*['"]([^'"]+)['"]/
);
if (urlMatch && tokenMatch && expiresMatch) {
const baseUrl = urlMatch[1];
const token = tokenMatch[1];
const expires = expiresMatch[1];
if (urlMatch && tokenMatch && expiresMatch) {
const baseUrl = urlMatch[1];
const token = tokenMatch[1];
const expires = expiresMatch[1];
if (baseUrl.includes("?b=1")) {
finalUrl = `${baseUrl}&token=${token}&expires=${expires}&h=1`;
if (baseUrl.includes("?b=1")) {
finalUrl = `${baseUrl}&token=${token}&expires=${expires}&h=1`;
} else {
finalUrl = `${baseUrl}?token=${token}&expires=${expires}&h=1`;
}
}
}
if (!finalUrl) {
const m3u8Match = html2.match(/(https?:\/\/[^'"\s]+\.m3u8[^'"\s]*)/);
if (m3u8Match) {
finalUrl = m3u8Match[1];
}
}
if (!finalUrl) {
const scriptMatches = html2.match(/<script[^>]*>(.*?)<\/script>/gs);
if (scriptMatches) {
for (const script of scriptMatches) {
const streamMatch = script.match(
/['"]?(https?:\/\/[^'"\s]+(?:\.m3u8|playlist)[^'"\s]*)/
);
if (streamMatch) {
finalUrl = streamMatch[1];
break;
}
}
}
}
if (!finalUrl) {
const videoMatch = html2.match(
/(?:src|source|url)['"]?\s*[:=]\s*['"]?(https?:\/\/[^'"\s]+(?:\.mp4|\.m3u8|\.mpd)[^'"\s]*)/
);
if (videoMatch) {
finalUrl = videoMatch[2] || videoMatch[1];
}
}
if (finalUrl) {
console.log("Final URL found:", finalUrl);
return finalUrl;
} else {
finalUrl = `${baseUrl}?token=${token}&expires=${expires}&h=1`;
console.log(
"No stream URL found. HTML content:",
html2.substring(0, 1000)
);
return null;
}
}
} catch (error) {
console.log("Fetch error:", error);
return null;
}
if (!finalUrl) {
const m3u8Match = html2.match(/(https?:\/\/[^'"\s]+\.m3u8[^'"\s]*)/);
if (m3u8Match) {
finalUrl = m3u8Match[1];
}
}
if (!finalUrl) {
const scriptMatches = html2.match(/<script[^>]*>(.*?)<\/script>/gs);
if (scriptMatches) {
for (const script of scriptMatches) {
const streamMatch = script.match(
/['"]?(https?:\/\/[^'"\s]+(?:\.m3u8|playlist)[^'"\s]*)/
);
if (streamMatch) {
finalUrl = streamMatch[1];
break;
}
}
}
}
if (!finalUrl) {
const videoMatch = html2.match(
/(?:src|source|url)['"]?\s*[:=]\s*['"]?(https?:\/\/[^'"\s]+(?:\.mp4|\.m3u8|\.mpd)[^'"\s]*)/
);
if (videoMatch) {
finalUrl = videoMatch[2] || videoMatch[1];
}
}
if (finalUrl) {
console.log("Final URL found:", finalUrl);
return finalUrl;
} else {
console.log(
"No stream URL found. HTML content:",
html2.substring(0, 1000)
);
return null;
}
} catch (error) {
console.log("Fetch error:", error);
return null;
}
}
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
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) {
} catch (e) {
try {
return await fetch(url, options);
} catch(error) {
} catch (error) {
return null;
}
}
+4 -4
View File
@@ -1,11 +1,11 @@
{
"sourceName": "StreamingUnity",
"iconUrl": "https://virginiapertutte.it/templates/sub-ita/images/logo.png",
"iconUrl": "https://play-lh.googleusercontent.com/-BHc2mPj6oRWk0cjwMn5oYSd7rYl5RW1xn8BWdizIHBuh4cBQ6ev_EsxmA4Trt0o2jQ",
"author": {
"name": "sobet",
"name": "sobet & alessio",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRQPQ1qIiALbM3xDWGsuJzu6ItaQGwb9ICRRw&s"
},
"version": "9000000.0.1",
"version": "9000003",
"language": "Italian",
"streamType": "HLS",
"quality": "1080p",
@@ -17,4 +17,4 @@
"supportsSora": true,
"supportsLuna": true,
"downloadSupport": false
}
}
+4 -4
View File
@@ -186,7 +186,7 @@ async function extractEpisodes(url) {
async function extractStreamUrl(ID) {
if (ID.includes('movie')) {
const tmdbID = ID.replace('/movie/', '');
const cinebyResponse = await soraFetch(`https://db.videasy.net/3/movie/${tmdbID}?append_to_response=external_ids&language=en&api_key=ad301b7cc82ffe19273e55e4d4206885`);
const cinebyResponse = await soraFetch(`https://db.videasy.net/3/movie/${tmdbID}?append_to_response=credits,external_ids,videos,recommendations,translations,similar,images&language=en`);
const cinebyData = await cinebyResponse.json();
const title = encodeURIComponent(cinebyData.title);
@@ -194,7 +194,7 @@ async function extractStreamUrl(ID) {
const imdbId = cinebyData.external_ids?.imdb_id || '';
const tmdbId = cinebyData.id;
const fullUrl = `https://api.videasy.net/cdn/sources-with-title?title=${title}&mediaType=movie&year=${year}&episodeId=1&seasonId=1&tmdbId=${tmdbId}&imdbId=${imdbId}`;
const fullUrl = `https://api.videasy.net/myflixerzupcloud/sources-with-title?title=${title}&mediaType=movie&year=${year}&episodeId=1&seasonId=1&tmdbId=${tmdbId}&imdbId=${imdbId}`;
console.log('Full URL:' + fullUrl);
@@ -245,7 +245,7 @@ async function extractStreamUrl(ID) {
const seasonNumber = parts[3];
const episodeNumber = parts[4];
const cinebyResponse = await soraFetch(`https://db.videasy.net/3/tv/${tmdbID}?append_to_response=external_ids&language=en&api_key=ad301b7cc82ffe19273e55e4d4206885`);
const cinebyResponse = await soraFetch(`https://db.videasy.net/3/tv/${tmdbID}?append_to_response=credits,external_ids,videos,recommendations,translations,similar,images&language=en`);
const cinebyData = await cinebyResponse.json();
const title = encodeURIComponent(cinebyData.name);
@@ -253,7 +253,7 @@ async function extractStreamUrl(ID) {
const imdbId = cinebyData.external_ids?.imdb_id || '';
const tmdbId = cinebyData.id;
const fullUrl = `https://api.videasy.net/cdn/sources-with-title?title=${title}&mediaType=tv&year=${year}&episodeId=${episodeNumber}&seasonId=${seasonNumber}&tmdbId=${tmdbId}&imdbId=${imdbId}`;
const fullUrl = `https://api.videasy.net/myflixerzupcloud/sources-with-title?title=${title}&mediaType=tv&year=${year}&episodeId=${episodeNumber}&seasonId=${seasonNumber}&tmdbId=${tmdbId}&imdbId=${imdbId}`;
console.log('Full URL:' + fullUrl);
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.1.0",
"version": "1.1.1",
"language": "English",
"streamType": "HLS",
"quality": "4K",
+344 -6662
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -5,7 +5,7 @@
"name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
},
"version": "1.1.1",
"version": "1.2.0",
"language": "English",
"streamType": "HLS",
"quality": "1080p",
+451
View File
@@ -0,0 +1,451 @@
const API_BASE = "https://api.yani.tv";
const IMAGE_REFERER = "https://site.yummyani.me/";
const PASSTHROUGH = "https://passthrough-worker.simplepostrequest.workers.dev/?simple=";
function _ua() {
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
}
function _absUrl(u) {
if (!u) return "";
const s = String(u);
if (s.startsWith("http://") || s.startsWith("https://")) return s;
if (s.startsWith("//")) return "https:" + s;
return s;
}
function _wrapImage(url) {
const abs = _absUrl(url);
if (!abs) return "";
return (
PASSTHROUGH +
encodeURIComponent(abs) +
"&referer=" +
encodeURIComponent(IMAGE_REFERER)
);
}
function _safeJsonParse(s, fallback) {
try { return JSON.parse(s); } catch (_) { return fallback; }
}
function scoreTitle(title, keyword) {
const t = String(title || "").toLowerCase().trim();
const k = String(keyword || "").toLowerCase().trim();
if (!t || !k) return 99;
if (t === k) return 0;
if (t.startsWith(k)) return 1;
if (t.includes(k)) return 2;
return 3;
}
// Prefer voiceovers order (edit freely)
function _dubbingRank(name) {
const s = String(name || "").toLowerCase();
const order = [
"anilibria",
"aniliberty",
"jam",
"anidub",
"shiza",
"studio band",
"studioband",
"dream cast",
"dreamcast",
"crunchyroll",
"sub",
"субтит"
];
for (let i = 0; i < order.length; i++) {
if (s.includes(order[i])) return i;
}
return 999;
}
// Pack episode payload into href string
function _pack(obj) {
return "yummy:" + encodeURIComponent(JSON.stringify(obj || {}));
}
function _unpack(href) {
const s = String(href || "");
if (!s.startsWith("yummy:")) return null;
return _safeJsonParse(decodeURIComponent(s.slice("yummy:".length)), null);
}
async function _apiGet(url) {
const headers = {
"User-Agent": _ua(),
"Accept": "application/json",
"Accept-Language": "ru-RU,ru;q=0.9,en;q=0.8",
"Referer": IMAGE_REFERER,
"Origin": IMAGE_REFERER
};
return fetchv2(url, headers);
}
// ------------------------- searchResults -------------------------
async function searchResults(keyword) {
const results = [];
try {
const url = `${API_BASE}/search?limit=30&offset=0&q=${encodeURIComponent(keyword)}`;
const res = await _apiGet(url);
const json = await res.json();
const arr = Array.isArray(json?.response) ? json.response : [];
for (const item of arr) {
const title = item?.title || "Unknown";
const poster =
item?.poster?.fullsize ||
item?.poster?.mega ||
item?.poster?.huge ||
item?.poster?.big ||
item?.poster?.medium ||
item?.poster?.small ||
"";
// Use anime_id as stable href
const href = item?.anime_id != null ? String(item.anime_id) : (item?.anime_url || "");
results.push({
title,
image: _wrapImage(poster),
href,
_score: scoreTitle(title, keyword)
});
}
results.sort((a, b) => a._score - b._score);
return JSON.stringify(results.map(({ _score, ...rest }) => rest));
} catch (err) {
return JSON.stringify([{ title: err?.message || "Error", image: "Error", href: "Error" }]);
}
}
// ------------------------- extractDetails -------------------------
async function extractDetails(animeIdOrUrl) {
try {
const url = `${API_BASE}/anime/${encodeURIComponent(String(animeIdOrUrl))}?need_videos=false`;
const res = await _apiGet(url);
const json = await res.json();
const data = json?.response || {};
const other = Array.isArray(data?.other_titles) ? data.other_titles : [];
return JSON.stringify([{
description: data?.description || "No description available",
airdate: data?.year != null ? String(data.year) : "Unknown",
aliases: other.length ? other.join(", ") : ""
}]);
} catch (_) {
return JSON.stringify([{ description: "Error", airdate: "Error", aliases: "" }]);
}
}
// ------------------------- extractEpisodes -------------------------
// UNIQUE by episode number.
// Store ALL voiceover options inside href payload; stream picker shows them.
async function extractEpisodes(animeIdOrUrl) {
try {
const raw = String(animeIdOrUrl || "").trim();
let animeId = null;
if (/^\d+$/.test(raw)) {
animeId = raw;
} else {
const infoUrl = `${API_BASE}/anime/${encodeURIComponent(raw)}?need_videos=false`;
const infoRes = await _apiGet(infoUrl);
const infoJson = await infoRes.json();
const info = infoJson?.response || {};
animeId = info?.anime_id != null ? String(info.anime_id) : null;
}
if (!animeId) return JSON.stringify([]);
const url = `${API_BASE}/anime/${encodeURIComponent(animeId)}/videos`;
const res = await _apiGet(url);
const json = await res.json();
const vids = Array.isArray(json?.response) ? json.response : [];
// Keep only Kodik entries (we parse kodik)
const kodikVids = vids.filter(v => {
const iframe = String(v?.iframe_url || "");
const player = String(v?.data?.player || "");
return iframe.includes("kodik.info") || player.toLowerCase().includes("kodik");
});
// Group by episode number
const byNum = new Map(); // num -> { num, options: [...], opening?, ending?, duration?, skips? }
for (const v of kodikVids) {
const num = parseFloat(v?.number) || 0;
if (!num) continue;
const iframeUrl = _absUrl(v?.iframe_url || "");
if (!iframeUrl) continue;
const dubbing = String(v?.data?.dubbing || "").trim() || "Unknown voiceover";
const player = String(v?.data?.player || "Kodik").trim();
const opening =
v?.skips?.opening &&
Number.isFinite(v.skips.opening.time) &&
Number.isFinite(v.skips.opening.length)
? { start: v.skips.opening.time, stop: v.skips.opening.time + v.skips.opening.length }
: undefined;
const ending =
v?.skips?.ending &&
Number.isFinite(v.skips.ending.time) &&
Number.isFinite(v.skips.ending.length)
? { start: v.skips.ending.time, stop: v.skips.ending.time + v.skips.ending.length }
: undefined;
// raw timecode format (time + length)
const skips =
(v?.skips?.opening && Number.isFinite(v.skips.opening.time) && Number.isFinite(v.skips.opening.length)) ||
(v?.skips?.ending && Number.isFinite(v.skips.ending.time) && Number.isFinite(v.skips.ending.length))
? {
opening:
v?.skips?.opening &&
Number.isFinite(v.skips.opening.time) &&
Number.isFinite(v.skips.opening.length)
? { time: v.skips.opening.time, length: v.skips.opening.length }
: null,
ending:
v?.skips?.ending &&
Number.isFinite(v.skips.ending.time) &&
Number.isFinite(v.skips.ending.length)
? { time: v.skips.ending.time, length: v.skips.ending.length }
: null
}
: undefined;
const duration = Number.isFinite(v?.duration) && v.duration > 0 ? v.duration : undefined;
if (!byNum.has(num)) {
byNum.set(num, { num, options: [], opening, ending, duration, skips });
}
const ep = byNum.get(num);
// Save first available skips/duration
if (!ep.opening && opening) ep.opening = opening;
if (!ep.ending && ending) ep.ending = ending;
if (!ep.duration && duration) ep.duration = duration;
// keep first raw skips object if present
if (!ep.skips && skips) ep.skips = skips;
ep.options.push({
dubbing,
player,
iframe_url: iframeUrl,
opening,
ending
});
}
const out = Array.from(byNum.values())
.sort((a, b) => a.num - b.num)
.map(ep => {
// sort voiceovers
ep.options.sort((x, y) => _dubbingRank(x.dubbing) - _dubbingRank(y.dubbing));
const payload = {
animeId,
number: ep.num,
options: ep.options
};
const item = {
href: _pack(payload),
number: ep.num,
title: `Episode ${ep.num}`
};
// Use skips from the first (sorted) voiceover option
const primary = ep.options[0];
if (primary?.opening) item.opening = primary.opening;
if (primary?.ending) item.ending = primary.ending;
// raw timecodes (time + length)
if (ep.skips) item.skips = ep.skips;
if (ep.duration) item.duration = ep.duration;
return item;
});
return JSON.stringify(out);
} catch (_) {
return JSON.stringify([]);
}
}
// ------------------------- extractStreamUrl -------------------------
// Build streams list: one entry per voiceover option (best quality per option)
async function extractStreamUrl(href) {
try {
const payload = _unpack(href);
const options = Array.isArray(payload?.options) ? payload.options : [];
if (!options.length) {
return JSON.stringify({ streams: [], subtitle: "https://none.com" });
}
options.sort((a, b) => _dubbingRank(a.dubbing) - _dubbingRank(b.dubbing));
const streams = [];
for (const opt of options) {
const iframeUrl = _absUrl(opt?.iframe_url);
if (!iframeUrl || !iframeUrl.includes("kodik.info")) continue;
const qualitiesJson = await kodikParser(iframeUrl);
const qualities = _safeJsonParse(qualitiesJson, {});
let bestUrl = "";
let bestQ = 0;
for (const q in qualities) {
const src = qualities?.[q]?.src;
if (!src) continue;
const n = parseInt(String(q).replace(/[^\d]/g, ""), 10) || 0;
if (n > bestQ) {
bestQ = n;
bestUrl = src;
}
}
if (!bestUrl) continue;
const finalUrl = bestUrl.startsWith("//") ? "https:" + bestUrl : bestUrl;
streams.push({
title: `${opt.dubbing}${bestQ ? ` (${bestQ}p)` : ""} (Kodik)`,
streamUrl: finalUrl,
headers: {
"User-Agent": _ua(),
"Referer": "https://kodik.info/"
}
});
}
return JSON.stringify({
streams,
subtitle: "https://none.com"
});
} catch (_) {
return JSON.stringify({ streams: [], subtitle: "https://none.com" });
}
}
// ------------------------- kodikParser -------------------------
async function kodikParser(url) {
try {
const headers = {
"Referer": IMAGE_REFERER,
"User-Agent": _ua()
};
const response = await fetchv2(url, headers);
const htmlText = await response.text();
const urlParamsMatch = htmlText.match(/var\s+urlParams\s*=\s*'([^']+)'/);
const videoInfoTypeMatch = htmlText.match(/vInfo\.type\s*=\s*'([^']+)'/);
const videoInfoHashMatch = htmlText.match(/vInfo\.hash\s*=\s*'([^']+)'/);
const videoInfoIdMatch = htmlText.match(/vInfo\.id\s*=\s*'([^']+)'/);
const urlParams = urlParamsMatch ? _safeJsonParse(urlParamsMatch[1], {}) : {};
const videoInfo_type = videoInfoTypeMatch ? videoInfoTypeMatch[1] : "";
const videoInfo_hash = videoInfoHashMatch ? videoInfoHashMatch[1] : "";
const videoInfo_id = videoInfoIdMatch ? videoInfoIdMatch[1] : "";
const finalData =
`d=${urlParams.d}` +
`&d_sign=${urlParams.d_sign}` +
`&pd=${urlParams.pd}` +
`&pd_sign=${urlParams.pd_sign}` +
`&ref=${urlParams.ref}` +
`&ref_sign=${urlParams.ref_sign}` +
`&bad_user=false&cdn_is_working=false` +
`&type=${videoInfo_type}&hash=${videoInfo_hash}&id=${videoInfo_id}&info=%7B%7D`;
const headers2 = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Referer": "https://kodik.info",
"User-Agent": _ua(),
"X-Requested-With": "XMLHttpRequest"
};
const apiResponse = await fetchv2("https://kodik.info/ftor", headers2, "POST", finalData);
const apiJson = await apiResponse.json();
const qualities = {};
if (apiJson?.links) {
for (const quality in apiJson.links) {
const qArr = apiJson.links[quality];
const first = Array.isArray(qArr) ? qArr[0] : null;
if (!first?.src) continue;
qualities[quality] = {
src: decode(first.src),
type: first.type || "application/x-mpegURL"
};
}
}
return JSON.stringify(qualities, null, 2);
} catch (_) {
return JSON.stringify({ error: "kodik_parse_failed" });
}
}
// ------------------------- decode (Kodik) -------------------------
function decode(input) {
const map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let out = "", b = 0, c = 0;
// ROT +18 letters
const r = [];
for (let i = 0; i < input.length; i++) {
const ch = input[i];
if (/[a-zA-Z]/.test(ch)) {
const cc = ch.charCodeAt(0);
const max = ch <= "Z" ? 90 : 122;
const sh = cc + 18;
r.push(String.fromCharCode(sh <= max ? sh : sh - 26));
} else r.push(ch);
}
const rot = r.join("");
for (let j = 0; j < rot.length; j++) {
const ch = rot[j];
if (ch === "=") break;
const v = map.indexOf(ch);
if (v === -1) continue;
b = (b << 6) | v;
c += 6;
if (c >= 8) {
c -= 8;
out += String.fromCharCode((b >> c) & 0xff);
}
}
return out;
}
// ------------------------- export hook -------------------------
function _defaultExport() {
return {
searchResults,
extractDetails,
extractEpisodes,
extractStreamUrl
};
}
try { globalThis.default = _defaultExport; } catch (_) {}
try { this.default = _defaultExport; } catch (_) {}
try { globalThis.module = globalThis.module || {}; globalThis.module.exports = { default: _defaultExport }; } catch (_) {}
+23
View File
@@ -0,0 +1,23 @@
{
"sourceName": "YummyAnime",
"iconUrl": "https://site.yummyani.me/img/icon/yummy-192.png",
"author": {
"name": "emp0ry",
"icon": "https://avatars.githubusercontent.com/u/64217088"
},
"version": "1.0.2",
"language": "Russian",
"streamType": "HLS",
"quality": "1080p",
"baseUrl": "https://api.yani.tv",
"searchBaseUrl": "https://api.yani.tv/search?limit=30&q=%s",
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/yummyanime/yummyanime.js",
"asyncJS": true,
"streamAsyncJS": true,
"softsub": false,
"type": "anime",
"downloadSupport": false,
"supportsMojuru": true,
"supportsSora": true,
"supportsLuna": true
}