forked from 50n50/sources
merge upstream
This commit is contained in:
@@ -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
|
||||
@@ -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/";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(/’/g, "'")
|
||||
.replace(/–/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(/'/g, "'")
|
||||
.replace(/"/g, "\"")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/ /g, " ");
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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
|
||||
))(_);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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(/(")/g, '"')
|
||||
.replace(/(&)/g, '&')
|
||||
.replace(/(<)/g, '<')
|
||||
.replace(/(>)/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(/(")/g, '"')
|
||||
.replace(/(&)/g, '&')
|
||||
.replace(/(<)/g, '<')
|
||||
.replace(/(>)/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,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": {
|
||||
|
||||
@@ -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,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
@@ -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(`"`, `"`);
|
||||
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("'", "'") || "",
|
||||
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(`"`, `"`);
|
||||
const pageData = JSON.parse(dataPage);
|
||||
const titleData = pageData.props?.title;
|
||||
|
||||
if (!titleData) {
|
||||
return JSON.stringify([]);
|
||||
}
|
||||
|
||||
return JSON.stringify([
|
||||
{
|
||||
description:
|
||||
titleData.plot?.replaceAll("amp;", "").replaceAll("'", "'") ||
|
||||
"N/A",
|
||||
aliases:
|
||||
titleData.original_name
|
||||
?.replaceAll("amp;", "")
|
||||
.replaceAll("'", "'") || "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(`"`, `"`));
|
||||
const dataPage = match[1].replaceAll(`"`, `"`);
|
||||
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("'", "'") || "",
|
||||
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(`"`, `"`);
|
||||
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("'", "'") ||
|
||||
"N/A",
|
||||
aliases: titleData.original_name
|
||||
?.replaceAll("amp;", "")
|
||||
.replaceAll("'", "'") || "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(`"`, `"`)
|
||||
);
|
||||
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(`"`, `"`));
|
||||
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(`"`, `"`)
|
||||
);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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 (_) {}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user