should be alright
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const image = 'https://files.catbox.moe/9tbjtb.png';
|
||||
const regex = /<tr><td><a href="([^"]+)">([^<]+)<\/a><\/td><td[^>]*>[^<]*<\/td><\/tr>/g;
|
||||
|
||||
const urls = [
|
||||
'https://a.111477.xyz/tvs/',
|
||||
'https://a.111477.xyz/movies/',
|
||||
'https://a.111477.xyz/kdrama/',
|
||||
'https://a.111477.xyz/asiandrama/'
|
||||
];
|
||||
|
||||
for (const url of urls) {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
const rawTitle = match[2].trim().replace(/\/$/, '');
|
||||
const cleanedTitle = rawTitle.replace(/\.+/g, ' ').toLowerCase();
|
||||
|
||||
if (cleanedTitle.includes(keyword.toLowerCase())) {
|
||||
results.push({
|
||||
title: rawTitle.replace(/\.+/g, ' '),
|
||||
image,
|
||||
href: url + match[1].trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
const results = [];
|
||||
|
||||
results.push({
|
||||
description: 'None provided, but hell who cares anyway',
|
||||
aliases: 'N/A',
|
||||
airdate: 'N/A'
|
||||
});
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const episodeRegex = /<tr><td><a href="(https:\/\/[^"]+\.mkv)">([^<]+\.mkv)<\/a><\/td>/g;
|
||||
let match;
|
||||
let count = 1;
|
||||
|
||||
while ((match = episodeRegex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: count++
|
||||
});
|
||||
}
|
||||
|
||||
if (results.length > 0) {
|
||||
return JSON.stringify([{
|
||||
href: url,
|
||||
number: 1
|
||||
}]);
|
||||
}
|
||||
if (results.length === 0) {
|
||||
const seasonRegex = /<tr><td><a href="([^"]+\/)"[^>]*>([^<]+\/)<\/a><\/td>/g;
|
||||
const seasons = [];
|
||||
|
||||
while ((match = seasonRegex.exec(html)) !== null) {
|
||||
const seasonHref = match[1].trim();
|
||||
const seasonName = match[2].trim();
|
||||
|
||||
if (seasonHref === '../' || seasonName === '../') continue;
|
||||
|
||||
const isSeasonDir = (
|
||||
/season\s*\d+/i.test(seasonName) ||
|
||||
/s\d+/i.test(seasonName) ||
|
||||
/series\s*\d+/i.test(seasonName) ||
|
||||
/specials/i.test(seasonName) ||
|
||||
/extras/i.test(seasonName) ||
|
||||
/bonus/i.test(seasonName) ||
|
||||
(/\d+/.test(seasonName) && seasonName.endsWith('/'))
|
||||
);
|
||||
|
||||
if (isSeasonDir) {
|
||||
seasons.push({
|
||||
href: seasonHref,
|
||||
name: seasonName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
seasons.sort((a, b) => {
|
||||
const aNum = extractSeasonNumber(a.name);
|
||||
const bNum = extractSeasonNumber(b.name);
|
||||
|
||||
if (aNum === null && bNum === null) return a.name.localeCompare(b.name);
|
||||
if (aNum === null) return 1;
|
||||
if (bNum === null) return -1;
|
||||
|
||||
return aNum - bNum;
|
||||
});
|
||||
|
||||
console.log(`Found ${seasons.length} seasons:`, seasons.map(s => s.name));
|
||||
console.log(`Base URL: "${url}"`);
|
||||
console.log(`Base URL ends with /: ${url.endsWith('/')}`);
|
||||
|
||||
|
||||
for (const season of seasons) {
|
||||
console.log(`Processing season: ${season.name}`);
|
||||
console.log(`Season href: "${season.href}"`);
|
||||
|
||||
let seasonUrl;
|
||||
if (season.href.startsWith('http')) {
|
||||
seasonUrl = season.href;
|
||||
} else {
|
||||
const baseUrl = url.endsWith('/') ? url : url + '/';
|
||||
seasonUrl = baseUrl + season.href;
|
||||
}
|
||||
|
||||
console.log(`Season URL: "${seasonUrl}"`);
|
||||
console.log(`URL encoded version: "${encodeURI(seasonUrl)}"`);
|
||||
|
||||
try {
|
||||
const seasonResponse = await fetchv2(decodeURIComponent(seasonUrl));
|
||||
const seasonHtml = await seasonResponse.text();
|
||||
|
||||
console.log(`Sample HTML from ${season.name}:`, seasonHtml.substring(0, 500));
|
||||
|
||||
let episodeCount = 1;
|
||||
|
||||
const seasonEpisodeRegex = /<a href="([^"]+\.mkv)"[^>]*>([^<]+\.mkv)<\/a>/g;
|
||||
let seasonMatch;
|
||||
let episodesInSeason = 0;
|
||||
|
||||
while ((seasonMatch = seasonEpisodeRegex.exec(seasonHtml)) !== null) {
|
||||
let episodeHref = seasonMatch[1].trim();
|
||||
|
||||
if (!episodeHref.startsWith('http')) {
|
||||
episodeHref = seasonUrl.endsWith('/') ? seasonUrl + episodeHref : seasonUrl + '/' + episodeHref;
|
||||
}
|
||||
|
||||
results.push({
|
||||
href: episodeHref,
|
||||
number: episodeCount++,
|
||||
season: season.name.replace('/', '')
|
||||
});
|
||||
episodesInSeason++;
|
||||
}
|
||||
|
||||
console.log(`Found ${episodesInSeason} episodes in ${season.name}`);
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`Failed to fetch season ${season.name}:`, error);
|
||||
console.warn(`Season URL was: ${seasonUrl}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
function extractSeasonNumber(seasonName) {
|
||||
const patterns = [
|
||||
/season\s*(\d+)/i,
|
||||
/s(\d+)/i,
|
||||
/series\s*(\d+)/i,
|
||||
/(\d+)/
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = seasonName.match(pattern);
|
||||
if (match) {
|
||||
return parseInt(match[1], 10);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
if (url.toLowerCase().endsWith('.mkv')) {
|
||||
const filename = url.split('/').pop();
|
||||
|
||||
const final = {
|
||||
streams: [filename, url],
|
||||
subtitles: ""
|
||||
};
|
||||
|
||||
console.log("RETURN: " + JSON.stringify(final));
|
||||
return JSON.stringify(final);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetchv2(decodeURIComponent(url));
|
||||
const html = await response.text();
|
||||
|
||||
const mkvRegex = /<tr><td><a href="([^"]+\.mkv)"[^>]*>([^<]+\.mkv)<\/a><\/td>/g;
|
||||
const streams = [];
|
||||
let match;
|
||||
|
||||
while ((match = mkvRegex.exec(html)) !== null) {
|
||||
const mkvUrl = match[1].trim();
|
||||
const filename = match[2].trim();
|
||||
|
||||
streams.push(filename, mkvUrl);
|
||||
}
|
||||
|
||||
const final = {
|
||||
streams,
|
||||
subtitles: ""
|
||||
};
|
||||
|
||||
console.log("RETURN: " + JSON.stringify(final));
|
||||
return "JSON.stringify(final)";
|
||||
|
||||
} catch (error) {
|
||||
console.log("Error in extractStreamUrl:", error);
|
||||
return JSON.stringify({
|
||||
streams: [],
|
||||
subtitles: ""
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "111477",
|
||||
"iconUrl": "https://media.tenor.com/tIOhF5a8McEAAAAe/heart-emoji-love-nonchalant.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Multi Language",
|
||||
"streamType": "MKV",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://a.111477.xyz/",
|
||||
"searchBaseUrl": "https://a.111477.xyz/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/111477/111477.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": true,
|
||||
"note": "USE AN EXTERNAL PLAYER (E.G., VLC/MPV)",
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
|
||||
const headers = {
|
||||
"Host": 'graphql.anilist.co',
|
||||
"User-Agent": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0',
|
||||
"Accept": 'application/json',
|
||||
"Accept-Language": 'en-US,en;q=0.9',
|
||||
"Accept-Encoding": 'gzip, deflate, br, zstd',
|
||||
"Referer": 'https://anicore.tv/',
|
||||
"Content-Type": 'application/json',
|
||||
"Content-Length": '1380',
|
||||
"Origin": 'https://anicore.tv',
|
||||
"Connection": 'keep-alive',
|
||||
"Sec-Fetch-Dest": 'empty',
|
||||
"Sec-Fetch-Mode": 'cors',
|
||||
"Sec-Fetch-Site": 'cross-site',
|
||||
"Priority": 'u=4',
|
||||
"TE": 'trailers'
|
||||
};
|
||||
|
||||
const postData = {"query":"\nquery ($page: Int = 1, $perPage: Int = 50, $type: MediaType = ANIME, $search: String, $format_in: [MediaFormat], $status: MediaStatus, $countryOfOrigin: CountryCode, $season: MediaSeason, $seasonYear: Int, $genre_in: [String], $tag_in: [String], $sort:[MediaSort], $isAdult: Boolean) {\n Page(page: $page, perPage: $perPage) {\n pageInfo {\n total\n perPage\n currentPage\n lastPage\n hasNextPage\n }\n media(type: $type, sort: $sort, season: $season, seasonYear: $seasonYear, search: $search, genre_in: $genre_in, tag_in: $tag_in, format_in: $format_in, status: $status, countryOfOrigin: $countryOfOrigin, isAdult: $isAdult){\n id\n title {\n english\n romaji\n }\n coverImage {\n extraLarge\n color\n }\n startDate {\n year\n month\n day\n }\n bannerImage\n season\n seasonYear\n description\n type\n format\n status(version: 2)\n episodes\n duration\n chapters\n volumes\n genres\n isAdult\n averageScore\n popularity\n nextAiringEpisode {\n airingAt\n timeUntilAiring\n episode\n }\n mediaListEntry {\n id\n status\n }\n }\n }\n}","variables":{"search":`${keyword}`,"type":"ANIME","sort":"POPULARITY_DESC","page":1}};
|
||||
|
||||
try {
|
||||
const response = await fetchv2("https://graphql.anilist.co/", headers, "POST", postData);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.data && data.data.Page && data.data.Page.media) {
|
||||
const media = data.data.Page.media;
|
||||
for (const item of media) {
|
||||
results.push({
|
||||
title: item.title.english || item.title.romaji || "Unknown",
|
||||
image: item.coverImage.extraLarge || "",
|
||||
href: `${item.id}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(ID) {
|
||||
try {
|
||||
const headers = {
|
||||
"Host": 'graphql.anilist.co',
|
||||
"User-Agent": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0',
|
||||
"Accept": 'application/json',
|
||||
"Accept-Language": 'en-US,en;q=0.9',
|
||||
"Accept-Encoding": 'gzip, deflate, br, zstd',
|
||||
"Referer": 'https://anicore.tv/',
|
||||
"Content-Type": 'application/json',
|
||||
"Content-Length": '1380',
|
||||
"Origin": 'https://anicore.tv',
|
||||
"Connection": 'keep-alive',
|
||||
"Sec-Fetch-Dest": 'empty',
|
||||
"Sec-Fetch-Mode": 'cors',
|
||||
"Sec-Fetch-Site": 'cross-site',
|
||||
"Priority": 'u=4',
|
||||
"TE": 'trailers'
|
||||
};
|
||||
|
||||
const postData = {"query":"query media($id:Int,$type:MediaType,$isAdult:Boolean){Media(id:$id,type:$type,isAdult:$isAdult){id title{userPreferred romaji english native}coverImage{extraLarge large}bannerImage startDate{year month day}endDate{year month day}description season seasonYear type format status(version:2)episodes duration chapters volumes genres synonyms source(version:3)isAdult isLocked meanScore averageScore popularity favourites isFavouriteBlocked hashtag countryOfOrigin isLicensed isFavourite isRecommendationBlocked isFavouriteBlocked isReviewBlocked nextAiringEpisode{airingAt timeUntilAiring episode}relations{edges{id relationType(version:2)node{id title{userPreferred}format type status(version:2)bannerImage coverImage{large}}}}characterPreview:characters(perPage:6,sort:[ROLE,RELEVANCE,ID]){edges{id role name voiceActors(language:JAPANESE,sort:[RELEVANCE,ID]){id name{userPreferred}language:languageV2 image{large}}node{id name{userPreferred}image{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{id role node{id name{userPreferred}language:languageV2 image{large}}}}studios{edges{isMain node{id name}}}reviewPreview:reviews(perPage:2,sort:[RATING_DESC,ID]){pageInfo{total}nodes{id summary rating ratingAmount user{id name avatar{large}}}}recommendations(perPage:7,sort:[RATING_DESC,ID]){pageInfo{total}nodes{id rating userRating mediaRecommendation{id title{userPreferred}format type status(version:2)bannerImage coverImage{large}}user{id name avatar{large}}}}externalLinks{id site url type language color icon notes isDisabled}streamingEpisodes{site title thumbnail url}trailer{id site}rankings{id rank type format year season allTime context}tags{id name description rank isMediaSpoiler isGeneralSpoiler userId}mediaListEntry{id status score}stats{statusDistribution{status amount}scoreDistribution{score amount}}}}","variables":{"id":ID,"type":"ANIME"}};
|
||||
const response = await fetchv2("https://graphql.anilist.co/", headers, "POST", postData);
|
||||
const data = await response.json();
|
||||
|
||||
const media = data.data.Media;
|
||||
return JSON.stringify([{
|
||||
description: clean(media.description),
|
||||
aliases: media.title.romaji + " / " + media.title.english + " / " + media.title.native,
|
||||
airdate: media.startDate.year
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(ID) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://anicore-api.vercel.app/api/episodes?id=" + ID);
|
||||
const data = await response.json();
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
for (const episode of data) {
|
||||
results.push({
|
||||
href: `${ID}-${episode.number}`,
|
||||
number: episode.number
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const parts = url.split('-');
|
||||
const animeID = parts[0];
|
||||
const episodeNumber = parseInt(parts[1], 10);
|
||||
|
||||
const episodesResponse = await fetchv2("https://anicore-api.vercel.app/api/episodes?id=" + animeID);
|
||||
const episodesData = await episodesResponse.json();
|
||||
|
||||
const episode = episodesData.find(ep => ep.number === episodeNumber);
|
||||
if (!episode) {
|
||||
return JSON.stringify({
|
||||
streams: [],
|
||||
subtitle: ""
|
||||
});
|
||||
}
|
||||
|
||||
const excludedProviders = ['oni', 'yuki', 'kami'];
|
||||
const subProviders = (episode.subProviders || []).filter(p => !excludedProviders.includes(p));
|
||||
const dubProviders = (episode.dubProviders || []).filter(p => !excludedProviders.includes(p));
|
||||
|
||||
if (subProviders.length === 0 && dubProviders.length === 0) {
|
||||
return JSON.stringify({
|
||||
streams: [],
|
||||
subtitle: ""
|
||||
});
|
||||
}
|
||||
|
||||
const streamPromises = [];
|
||||
|
||||
for (const provider of subProviders) {
|
||||
streamPromises.push(
|
||||
fetchv2(`https://anicore-api.vercel.app/api/stream?id=${animeID}&host=${provider}&ep=${episodeNumber}&type=sub`)
|
||||
.then(res => res.json())
|
||||
.then(data => ({
|
||||
provider,
|
||||
type: 'sub',
|
||||
data
|
||||
}))
|
||||
.catch(() => ({
|
||||
provider,
|
||||
type: 'sub',
|
||||
data: null
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
for (const provider of dubProviders) {
|
||||
streamPromises.push(
|
||||
fetchv2(`https://anicore-api.vercel.app/api/stream?id=${animeID}&host=${provider}&ep=${episodeNumber}&type=dub`)
|
||||
.then(res => res.json())
|
||||
.then(data => ({
|
||||
provider,
|
||||
type: 'dub',
|
||||
data
|
||||
}))
|
||||
.catch(() => ({
|
||||
provider,
|
||||
type: 'dub',
|
||||
data: null
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
const streamResults = await Promise.all(streamPromises);
|
||||
|
||||
const streams = [];
|
||||
let englishSubtitle = "";
|
||||
let zenResult = null;
|
||||
|
||||
for (const result of streamResults) {
|
||||
if (result.data && result.data.sources && result.data.sources.length > 0) {
|
||||
const source = result.data.sources[0];
|
||||
const typeLabel = result.type === 'dub' ? 'DUB' : 'SUB';
|
||||
streams.push({
|
||||
title: `${result.provider.toUpperCase()} - ${typeLabel}`,
|
||||
streamUrl: source.url,
|
||||
headers: result.data.headers || {}
|
||||
});
|
||||
|
||||
if (result.provider === 'zen' && result.type === 'sub') {
|
||||
zenResult = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (zenResult && zenResult.data && Array.isArray(zenResult.data.subtitles)) {
|
||||
const engSub = zenResult.data.subtitles.find(sub =>
|
||||
(sub.lang.includes('eng') || sub.lang.includes('en')) &&
|
||||
!sub.label.toLowerCase().includes('signs') &&
|
||||
!sub.label.toLowerCase().includes('songs')
|
||||
);
|
||||
if (engSub) {
|
||||
englishSubtitle = engSub.url || "";
|
||||
}
|
||||
}
|
||||
|
||||
if (!englishSubtitle) {
|
||||
for (const result of streamResults) {
|
||||
if (result.type === 'sub' && result.data) {
|
||||
if (Array.isArray(result.data.subtitles)) {
|
||||
const engSub = result.data.subtitles.find(sub =>
|
||||
(sub.lang.includes('eng') || sub.lang.includes('en') ||
|
||||
sub.language === 'eng' || sub.language === 'en' || sub.name === 'eng' || sub.name === 'en') &&
|
||||
!(sub.label && (sub.label.toLowerCase().includes('signs') || sub.label.toLowerCase().includes('songs')))
|
||||
);
|
||||
if (engSub) {
|
||||
englishSubtitle = engSub.url || "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!englishSubtitle && Array.isArray(result.data.audio)) {
|
||||
const engAudio = result.data.audio.find(audio =>
|
||||
audio.language === 'eng' || audio.language === 'en' || audio.name === 'eng' || audio.name === 'en'
|
||||
);
|
||||
if (engAudio) {
|
||||
englishSubtitle = engAudio.url || "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify({
|
||||
streams: streams.length > 0 ? streams : [],
|
||||
subtitles: englishSubtitle
|
||||
}));
|
||||
return JSON.stringify({
|
||||
streams: streams.length > 0 ? streams : [],
|
||||
subtitles: englishSubtitle
|
||||
});
|
||||
} catch (err) {
|
||||
return JSON.stringify({
|
||||
streams: [],
|
||||
subtitles: ""
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const clean = html =>
|
||||
html
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/<\/?i>/gi, '')
|
||||
.replace(/<\/?b>/gi, '')
|
||||
.replace(/<\/?[^>]+>/g, '');
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "Anicore",
|
||||
"iconUrl": "https://anicore.tv/_app/immutable/assets/favicon.Bq_xX7js.png",
|
||||
"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://anicore.tv/",
|
||||
"searchBaseUrl": "https://anicore.tv/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anicore/anicore.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": false,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
async function searchResults(keyword) {
|
||||
try {
|
||||
const encodedKeyword = encodeURIComponent(keyword);
|
||||
const responseText = await fetch(`https://api.anilibria.tv/v3/title/search?search=${encodedKeyword}&filter=id,names,posters`);
|
||||
const data = JSON.parse(responseText);
|
||||
|
||||
const transformedResults = data.list.map(anime => ({
|
||||
title: anime.names.en || anime.names.ru || 'Unknown Title',
|
||||
image: `https://anilibria.tv${anime.posters.original.url}`,
|
||||
href: `${anime.id}`
|
||||
}));
|
||||
|
||||
return JSON.stringify(transformedResults);
|
||||
|
||||
} catch (error) {
|
||||
console.log('Fetch error:', error);
|
||||
return JSON.stringify([{ title: 'Error', image: '', href: '' }]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function extractDetails(id) {
|
||||
try {
|
||||
const response = await fetch(`https://api.anilibria.tv/v3/title?id=${id}&filter=description`);
|
||||
const data = JSON.parse(response);
|
||||
|
||||
const animeInfo = data;
|
||||
|
||||
const transformedResults = [{
|
||||
description: animeInfo.description || 'No description available',
|
||||
aliases: `Alias: Unknown`,
|
||||
airdate: `Aired: Unknown`
|
||||
}];
|
||||
|
||||
return JSON.stringify(transformedResults);
|
||||
} catch (error) {
|
||||
console.log('Details error:', error);
|
||||
return JSON.stringify([{
|
||||
description: 'Error loading description',
|
||||
aliases: 'Duration: Unknown',
|
||||
airdate: 'Aired: Unknown'
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(id) {
|
||||
try {
|
||||
const response = await fetch(`https://api.anilibria.tv/v3/title?id=${id}`);
|
||||
const data = JSON.parse(response);
|
||||
|
||||
const transformedResults = Object.values(data.player.list).map(episode => ({
|
||||
href: `https://cache.libria.fun${episode.hls.hd}`,
|
||||
number: episode.episode
|
||||
}));
|
||||
|
||||
return JSON.stringify(transformedResults);
|
||||
|
||||
} catch (error) {
|
||||
console.log('Fetch error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
return url;
|
||||
} catch (error) {
|
||||
console.log('Fetch error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sourceName": "AniLibria",
|
||||
"iconUrl": "https://github.com/50n50/sources/blob/main/anilibria/icon.png?raw=true",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Russian",
|
||||
"streamType": "HLS",
|
||||
"quality": "720p",
|
||||
"baseUrl": "https://api.anilibria.tv/",
|
||||
"searchBaseUrl": "https://api.anilibria.tv/v3/title/search?search=%s",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anilibria/anilibria.js",
|
||||
"asyncJS": true,
|
||||
"type": "anime",
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,228 @@
|
||||
function searchResults(html) {
|
||||
if (typeof html !== 'string') {
|
||||
console.error('Invalid HTML input: expected a string.');
|
||||
return [];
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
const titleRegex = /<h4[^>]*>(.*?)<\/h4>/;
|
||||
const hrefRegex = /<a\s+href="([^"]+)"\s*[^>]*>/;
|
||||
const imgRegex = /<img[^>]*src="([^"]+)"[^>]*>/;
|
||||
|
||||
const itemRegex = /<a\s+href="[^"]+"\s+class="btn btn-md btn-light simple-title-card[^"]*"[^>]*>[\s\S]*?<\/a>/g;
|
||||
const items = html.match(itemRegex) || [];
|
||||
|
||||
items.forEach((itemHtml, index) => {
|
||||
try {
|
||||
if (typeof itemHtml !== 'string') {
|
||||
console.error(`Item ${index} is not a string.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const titleMatch = itemHtml.match(titleRegex);
|
||||
const title = titleMatch?.[1]?.trim() ?? '';
|
||||
|
||||
const hrefMatch = itemHtml.match(hrefRegex);
|
||||
const href = hrefMatch?.[1]?.trim() ?? '';
|
||||
|
||||
const imgMatch = itemHtml.match(imgRegex);
|
||||
const imageUrl = imgMatch?.[1]?.trim() ?? '';
|
||||
|
||||
if (title && href) {
|
||||
results.push({
|
||||
title: decodeHTMLEntities(title),
|
||||
image: imageUrl,
|
||||
href: href
|
||||
});
|
||||
} else {
|
||||
console.error(`Missing title or href in item ${index}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error processing item ${index}:`, err);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
function extractDetails(html) {
|
||||
const details = [];
|
||||
|
||||
const containerMatch = html.match(/<div class="py-4 flex flex-col gap-2">\s*((?:<p class="sm:text-\[1\.04rem\] leading-loose text-justify">[\s\S]*?<\/p>\s*)+)<\/div>/);
|
||||
|
||||
let description = "";
|
||||
if (containerMatch) {
|
||||
const pBlock = containerMatch[1];
|
||||
|
||||
const pRegex = /<p class="sm:text-\[1\.04rem\] leading-loose text-justify">([\s\S]*?)<\/p>/g;
|
||||
const matches = [...pBlock.matchAll(pRegex)]
|
||||
.map(m => m[1].trim())
|
||||
.filter(text => text.length > 0);
|
||||
|
||||
description = decodeHTMLEntities(matches.join("\n\n"));
|
||||
}
|
||||
|
||||
const airdateMatch = html.match(/<td[^>]*title="([^"]+)">[^<]+<\/td>/);
|
||||
let airdate = airdateMatch ? airdateMatch[1].trim() : "";
|
||||
|
||||
const genres = [];
|
||||
const aliasesMatch = html.match(
|
||||
/<div\s+class="flex flex-wrap gap-2 lg:gap-4 text-sm sm:text-\[\.94rem\] -mt-2 mb-4">([\s\S]*?)<\/div>/
|
||||
);
|
||||
const inner = aliasesMatch ? aliasesMatch[1] : "";
|
||||
|
||||
const anchorRe = /<a[^>]*class="btn btn-md btn-plain !p-0"[^>]*>([^<]+)<\/a>/g;
|
||||
let m;
|
||||
while ((m = anchorRe.exec(inner)) !== null) {
|
||||
genres.push(m[1].trim());
|
||||
}
|
||||
|
||||
if (description && airdate) {
|
||||
details.push({
|
||||
description: description,
|
||||
aliases: genres.join(", "),
|
||||
airdate: airdate,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(details);
|
||||
return details;
|
||||
}
|
||||
|
||||
|
||||
function extractEpisodes(html) {
|
||||
const episodes = [];
|
||||
const htmlRegex = /<a\s+[^>]*href="([^"]*?\/episode\/[^"]*?)"[^>]*>[\s\S]*?الحلقة\s+(\d+)[\s\S]*?<\/a>/gi;
|
||||
const plainTextRegex = /الحلقة\s+(\d+)/g;
|
||||
|
||||
let matches;
|
||||
|
||||
if ((matches = html.match(htmlRegex))) {
|
||||
matches.forEach(link => {
|
||||
const hrefMatch = link.match(/href="([^"]+)"/);
|
||||
const numberMatch = link.match(/الحلقة\s+(\d+)/);
|
||||
if (hrefMatch && numberMatch) {
|
||||
const href = hrefMatch[1];
|
||||
const number = numberMatch[1];
|
||||
episodes.push({
|
||||
href: href,
|
||||
number: number
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else if ((matches = html.match(plainTextRegex))) {
|
||||
matches.forEach(match => {
|
||||
const numberMatch = match.match(/\d+/);
|
||||
if (numberMatch) {
|
||||
episodes.push({
|
||||
href: null,
|
||||
number: numberMatch[0]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(episodes);
|
||||
return episodes;
|
||||
}
|
||||
|
||||
async function extractStreamUrl(html) {
|
||||
try {
|
||||
const sourceMatch = html.match(/data-video-source="([^"]+)"/);
|
||||
let embedUrl = sourceMatch?.[1]?.replace(/&/g, '&');
|
||||
if (!embedUrl) return null;
|
||||
|
||||
const cinemaMatch = html.match(/url\.searchParams\.append\(\s*['"]cinema['"]\s*,\s*(\d+)\s*\)/);
|
||||
const lastMatch = html.match(/url\.searchParams\.append\(\s*['"]last['"]\s*,\s*(\d+)\s*\)/);
|
||||
const cinemaNum = cinemaMatch ? cinemaMatch[1] : undefined;
|
||||
const lastNum = lastMatch ? lastMatch[1] : undefined;
|
||||
|
||||
if (cinemaNum) embedUrl += `&cinema=${cinemaNum}`;
|
||||
if (lastNum) embedUrl += `&last=${lastNum}`;
|
||||
embedUrl += `&next-image=undefined`;
|
||||
|
||||
console.log('Full embed URL:', embedUrl);
|
||||
|
||||
const response = await fetchv2(embedUrl);
|
||||
const data = await response.text();
|
||||
console.log('Embed page HTML:', data);
|
||||
|
||||
const qualities = extractQualities(data);
|
||||
|
||||
const epMatch = html.match(/<title>[^<]*الحلقة\s*(\d+)[^<]*<\/title>/);
|
||||
const currentEp = epMatch ? Number(epMatch[1]) : null;
|
||||
|
||||
let nextEpNum, nextDuration, nextSubtitle;
|
||||
if (currentEp !== null) {
|
||||
const episodeRegex = new RegExp(
|
||||
`<a[^>]+href="[^"]+/episode/[^/]+/(\\d+)"[\\s\\S]*?` +
|
||||
`<span[^>]*>([^<]+)<\\/span>[\\s\\S]*?` +
|
||||
`<p[^>]*>([^<]+)<\\/p>`,
|
||||
'g'
|
||||
);
|
||||
let m;
|
||||
while ((m = episodeRegex.exec(html)) !== null) {
|
||||
const num = Number(m[1]);
|
||||
if (num > currentEp) {
|
||||
nextEpNum = num;
|
||||
nextDuration = m[2].trim();
|
||||
nextSubtitle = m[3].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextEpNum != null) {
|
||||
embedUrl += `&next-title=${encodeURIComponent(nextDuration)}`;
|
||||
embedUrl += `&next-sub-title=${encodeURIComponent(nextSubtitle)}`;
|
||||
}
|
||||
|
||||
const result = {
|
||||
streams: qualities,
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result));
|
||||
return JSON.stringify(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function extractQualities(html) {
|
||||
const match = html.match(/var\s+videos\s*=\s*(\[[\s\S]*?\]);/);
|
||||
if (!match) return [];
|
||||
|
||||
const raw = match[1];
|
||||
const regex = /\{\s*src:\s*'([^']+)'\s*[^}]*label:\s*'([^']*)'/g;
|
||||
const list = [];
|
||||
let m;
|
||||
|
||||
while ((m = regex.exec(raw)) !== null) {
|
||||
list.push(m[2], m[1]);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
function decodeHTMLEntities(text) {
|
||||
text = text.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec));
|
||||
|
||||
const entities = {
|
||||
'"': '"',
|
||||
'&': '&',
|
||||
''': "'",
|
||||
'<': '<',
|
||||
'>': '>'
|
||||
};
|
||||
|
||||
for (const entity in entities) {
|
||||
text = text.replace(new RegExp(entity, 'g'), entities[entity]);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sourceName": "Anime3rb",
|
||||
"iconUrl": "https://anime3rb.com/favicon/apple-touch-icon.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.2.0",
|
||||
"language": "Arabic (SUB)",
|
||||
"streamType": "MP4",
|
||||
"quality": "1080p - 720p - 360p",
|
||||
"baseUrl": "https://anime3rb.com/",
|
||||
"searchBaseUrl": "https://anime3rb.com/search?q=%s",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anime3rb/anime3rb.js",
|
||||
"streamAsyncJS": true,
|
||||
"type": "anime",
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,165 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(
|
||||
"https://ww.anime4up.rest/?search_param=animes&s=" + encodeURIComponent(keyword)
|
||||
);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<div class="anime-card-container">[\s\S]*?<img[^>]+src="([^"]+)"[^>]*alt="([^"]*)"[\s\S]*?<h3><a[^>]*href="([^"]+)"[^>]*>([^<]+)<\/a><\/h3>/gi;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: match[4].trim(),
|
||||
image: match[1].trim().replace('ww.anime4up.rest', 'www.anime4up.rest'),
|
||||
href: match[3].trim().replace('ww.anime4up.rest', 'www.anime4up.rest')
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const descMatch = /<p class="anime-story">([\s\S]*?)<\/p>/i.exec(html);
|
||||
const description = descMatch ? descMatch[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const episodeRegex = /<div class="col-lg-3[^"]*DivEpisodeContainer">([\s\S]*?)<\/div>\s*<\/div>/gi;
|
||||
let match;
|
||||
let epNumber = 1;
|
||||
|
||||
while ((match = episodeRegex.exec(html)) !== null) {
|
||||
const hrefMatch = /<h3><a href="([^"]+)"/i.exec(match[1]);
|
||||
const href = hrefMatch ? hrefMatch[1].trim() : "";
|
||||
results.push({
|
||||
href: href,
|
||||
number: epNumber
|
||||
});
|
||||
epNumber++;
|
||||
}
|
||||
|
||||
const paginationRegex = /<li\s*><a\s+class="page-numbers"\s+href="[^"]*\/page\/(\d+)\/">(\d+)<\/a><\/li>/gi;
|
||||
let maxPage = 1;
|
||||
let paginationMatch;
|
||||
|
||||
while ((paginationMatch = paginationRegex.exec(html)) !== null) {
|
||||
const pageNum = parseInt(paginationMatch[2]);
|
||||
if (pageNum > maxPage) {
|
||||
maxPage = pageNum;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxPage > 1) {
|
||||
const pagePromises = [];
|
||||
|
||||
for (let page = 2; page <= maxPage; page++) {
|
||||
const pageUrl = `${url}/page/${page}/`;
|
||||
pagePromises.push(fetchPageEpisodes(pageUrl, epNumber + (page - 2) * getEpisodesPerPage(results.length)));
|
||||
}
|
||||
|
||||
const pageResults = await Promise.all(pagePromises);
|
||||
|
||||
pageResults.forEach(pageEpisodes => {
|
||||
results.push(...pageEpisodes);
|
||||
});
|
||||
|
||||
results.forEach((episode, index) => {
|
||||
episode.number = index + 1;
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPageEpisodes(pageUrl, startingEpNumber) {
|
||||
try {
|
||||
const response = await fetchv2(pageUrl);
|
||||
const html = await response.text();
|
||||
const episodeRegex = /<div class="col-lg-3[^"]*DivEpisodeContainer">([\s\S]*?)<\/div>\s*<\/div>/gi;
|
||||
const pageResults = [];
|
||||
let match;
|
||||
let epNumber = startingEpNumber;
|
||||
|
||||
while ((match = episodeRegex.exec(html)) !== null) {
|
||||
const hrefMatch = /<h3><a href="([^"]+)"/i.exec(match[1]);
|
||||
const href = hrefMatch ? hrefMatch[1].trim() : "";
|
||||
pageResults.push({
|
||||
href: href,
|
||||
number: epNumber
|
||||
});
|
||||
epNumber++;
|
||||
}
|
||||
|
||||
return pageResults;
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getEpisodesPerPage(firstPageCount) {
|
||||
return firstPageCount || 12;
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = /<a[^>]+data-ep-url="([^"]*uqload\.cx[^"]*)"/i.exec(html);
|
||||
if (match) {
|
||||
console.log(match[1].trim());
|
||||
const response2 = await fetchv2(match[1].trim());
|
||||
const html2 = await response2.text();
|
||||
|
||||
const match2 = /sources:\s*\[\s*"([^"]+)"\s*\]/i.exec(html2);
|
||||
const url = match2 ? match2[1] : null;
|
||||
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
return "dwd";
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"sourceName": "Anime4Up",
|
||||
"iconUrl": "https://ww.anime4up.rest/wp-content/uploads/2019/03/Anime4up-Icon-1.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"language": "Arabic",
|
||||
"streamType": "HLS",
|
||||
"encrypted": true,
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://uqload.cx/",
|
||||
"searchBaseUrl": "https://uqload.cx/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anime4up/anime4up.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": true,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://www.animebum.net/search?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<div class="search-results__item[^>]*>[\s\S]*?<a href="([^"]+)"[^>]*><img src="([^"]+)"[^>]*><\/a>[\s\S]*?<h2>([^<]+)<\/h2>/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: match[3].trim(),
|
||||
image: match[2].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<div class="description scrollV">\s*<p>([\s\S]*?)<\/p>/i;
|
||||
const match = regex.exec(html);
|
||||
let description = match ? match[1].trim() : "N/A";
|
||||
|
||||
description = description.replace(/<[^>]+>/g, "");
|
||||
description = description.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/á/g, "á")
|
||||
.replace(/é/g, "é")
|
||||
.replace(/í/g, "í")
|
||||
.replace(/ó/g, "ó")
|
||||
.replace(/ú/g, "ú")
|
||||
.replace(/ñ/g, "ñ")
|
||||
.replace(/ü/g, "ü")
|
||||
.replace(/“/g, "“")
|
||||
.replace(/”/g, "”")
|
||||
.replace(/‘/g, "‘")
|
||||
.replace(/’/g, "’")
|
||||
.replace(/¡/g, "¡")
|
||||
.replace(/¿/g, "¿");
|
||||
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const containerMatch = html.match(
|
||||
/<div class="list-episodies-content">([\s\S]*?)<\/div>/
|
||||
);
|
||||
|
||||
if (!containerMatch) {
|
||||
return JSON.stringify([{ href: "Not found", number: "N/A" }]);
|
||||
}
|
||||
|
||||
const containerHtml = containerMatch[1];
|
||||
|
||||
const regex = /<a[^>]+href="([^"]+)"[^>]*>[\s\S]*?Episodio\s+(\d+)\s*<\/a>/gi;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(containerHtml)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{ href: "Error", number: "Error" }]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
// Try Filemoon first
|
||||
const filemoonRegex = /(https:\/\/filemoon\.(?:to|sx)\/e\/[A-Za-z0-9]+\/[^\s"']+)/;
|
||||
const filemoonMatch = filemoonRegex.exec(html);
|
||||
|
||||
if (filemoonMatch) {
|
||||
const filemoon = filemoonMatch[1];
|
||||
console.log("Filemoon URL found: " + filemoon);
|
||||
|
||||
const headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Referer": "https://animebum.net/",
|
||||
};
|
||||
|
||||
const filemoonResponse = await fetchv2(filemoon, headers);
|
||||
const filemoonHtml = await filemoonResponse.text();
|
||||
|
||||
try {
|
||||
const streamUrl = await filemoonExtractor(filemoonHtml, "https://animebum.net/");
|
||||
if (streamUrl) return streamUrl;
|
||||
} catch (err) {
|
||||
console.log("Filemoon extraction error:" + err);
|
||||
}
|
||||
}
|
||||
|
||||
const optionRegex = /<ul class="play-list-tabs">([\s\S]*?)<\/ul>/i;
|
||||
const optionMatch = optionRegex.exec(html);
|
||||
if (!optionMatch) return null;
|
||||
|
||||
const optionsHtml = optionMatch[1];
|
||||
const faireMatch = /<li[^>]*data-id="(\d+)"[^>]*>\s*<a[^>]*title="Faire"/i.exec(optionsHtml);
|
||||
if (!faireMatch) return null;
|
||||
|
||||
const faireId = parseInt(faireMatch[1], 10);
|
||||
const videoRegex = new RegExp(`video\\[${faireId}\\]\\s*=\\s*'([^']+)'`);
|
||||
const videoMatch = videoRegex.exec(html);
|
||||
if (!videoMatch) return null;
|
||||
|
||||
const iframeHtml = videoMatch[1];
|
||||
const srcMatch = /src=["']([^"']+)["']/.exec(iframeHtml);
|
||||
const streamUrl = srcMatch ? srcMatch[1] : null;
|
||||
|
||||
const responeSome = await fetchv2(streamUrl);
|
||||
const htmlSome = await responeSome.text();
|
||||
|
||||
const shareIdMatch = htmlSome.match(/var\s+shareId\s*=\s*"([^"]+)"/);
|
||||
if (!shareIdMatch) return null;
|
||||
const shareId = shareIdMatch[1];
|
||||
const someUrl = `https://www.amazon.com/drive/v1/shares/${shareId}?resourceVersion=V2&ContentType=JSON&asset=ALL`;
|
||||
|
||||
const shareJsonResp = await fetchv2(someUrl);
|
||||
const shareJson = await shareJsonResp.json();
|
||||
console.log(JSON.stringify(shareJson));
|
||||
const nodeId = shareJson.nodeInfo?.id;
|
||||
if (!nodeId) return null;
|
||||
|
||||
const childrenUrl = `https://www.amazon.com/drive/v1/nodes/${nodeId}/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=${shareId}`;
|
||||
const childrenResp = await fetchv2(childrenUrl);
|
||||
const childrenData = await childrenResp.json();
|
||||
console.log(JSON.stringify(childrenData));
|
||||
|
||||
const file = childrenData.data.find(item => item.kind === "FILE");
|
||||
if (!file) return null;
|
||||
|
||||
return file.tempLink;
|
||||
|
||||
} catch (err) {
|
||||
console.log("Fetch error:" + err);
|
||||
return "https://error.org/";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* SCHEME START */
|
||||
/* {REQUIRED PLUGINS: unbaser} */
|
||||
|
||||
/**
|
||||
* @name filemoonExtractor
|
||||
* @author Cufiy - Inspired by Churly
|
||||
*/
|
||||
|
||||
async function filemoonExtractor(html, url = null) {
|
||||
// check if contains iframe, if does, extract the src and get the url
|
||||
const regex = /<iframe[^>]+src="([^"]+)"[^>]*><\/iframe>/;
|
||||
const match = html.match(regex);
|
||||
if (match) {
|
||||
console.log("Iframe URL: " + match[1]);
|
||||
const iframeUrl = match[1];
|
||||
const iframeResponse = await soraFetch(iframeUrl, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Referer": url,
|
||||
}
|
||||
});
|
||||
console.log("Iframe Response: " + iframeResponse.status);
|
||||
html = await iframeResponse.text();
|
||||
}
|
||||
// console.log("HTML: " + html);
|
||||
// get /<script[^>]*>([\s\S]*?)<\/script>/gi
|
||||
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
||||
const scripts = [];
|
||||
let scriptMatch;
|
||||
while ((scriptMatch = scriptRegex.exec(html)) !== null) {
|
||||
scripts.push(scriptMatch[1]);
|
||||
}
|
||||
// get the script with eval and m3u8
|
||||
const evalRegex = /eval\((.*?)\)/;
|
||||
const m3u8Regex = /m3u8/;
|
||||
// console.log("Scripts: " + scripts);
|
||||
const evalScript = scripts.find(script => evalRegex.test(script) && m3u8Regex.test(script));
|
||||
if (!evalScript) {
|
||||
console.log("No eval script found");
|
||||
return null;
|
||||
}
|
||||
const unpackedScript = unpack(evalScript);
|
||||
// get the m3u8 url
|
||||
const m3u8Regex2 = /https?:\/\/[^\s]+master\.m3u8[^\s]*?(\?[^"]*)?/;
|
||||
const m3u8Match = unpackedScript.match(m3u8Regex2);
|
||||
if (m3u8Match) {
|
||||
return m3u8Match[0];
|
||||
} else {
|
||||
console.log("No M3U8 URL found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* REMOVE_START */
|
||||
|
||||
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function unpack(source) {
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses Sora's fetchv2 on ipad, fallbacks to regular fetch on Windows
|
||||
* @author ShadeOfChaos
|
||||
*
|
||||
* @param {string} url The URL to make the request to.
|
||||
* @param {object} [options] The options to use for the request.
|
||||
* @param {object} [options.headers] The headers to send with the request.
|
||||
* @param {string} [options.method='GET'] The method to use for the request.
|
||||
* @param {string} [options.body=null] The body of the request.
|
||||
*
|
||||
* @returns {Promise<Response|null>} The response from the server, or null if the
|
||||
* request failed.
|
||||
*/
|
||||
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
|
||||
try {
|
||||
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
|
||||
} catch(e) {
|
||||
try {
|
||||
return await fetch(url, options);
|
||||
} catch(error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* REMOVE_END */
|
||||
|
||||
/* SCHEME END */
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "AnimeBum",
|
||||
"iconUrl": "https://files.catbox.moe/i96cs1.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://www.animebum.net/",
|
||||
"searchBaseUrl": "https://www.animebum.net/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animebum/animebum.js",
|
||||
"type": "Anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://animeepisodeseries.com/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)"[^>]*>\s*<img[^>]+src="([^"]+)"[^>]*>[\s\S]*?<h2 class="entry-title"><a [^>]+>([^<]+)<\/a><\/h2>/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: cleanTitle(match[3].trim()),
|
||||
image: match[2].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<p><strong[\s\S]*?>[\s\S]*?Summary:[\s\S]*?<\/strong><br\s*\/?>(.*?)<\/p>/i);
|
||||
const description = match ? match[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: cleanTitle(description),
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const gridMatch = html.match(/<div[^>]+class="[^"]*eael-post-grid[^"]*"[^>]*>([\s\S]*?)<\/div>\s*<div[^>]+class="clearfix">/i);
|
||||
|
||||
|
||||
const gridHtml = gridMatch[1];
|
||||
|
||||
const patterns = [
|
||||
/<a[^>]+href="([^"]*?-episode-(\d+)-[^"]*)"/gi,
|
||||
/<a[^>]+href="([^"]*?-episode-(\d+)(?:-[^"]*)?\/?)"/gi,
|
||||
/<a[^>]+href="([^"]*?episode(\d+)[^"]*)"/gi,
|
||||
/<a[^>]+href="([^"]*?ep(\d+)[^"]*)"/gi
|
||||
];
|
||||
|
||||
for (const regex of patterns) {
|
||||
let match;
|
||||
while ((match = regex.exec(gridHtml)) !== null) {
|
||||
const episodeNumber = parseInt(match[2], 10);
|
||||
|
||||
if (!results.find(r => r.number === episodeNumber && r.href === match[1].trim())) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: episodeNumber
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
const linkRegex = /<a[^>]+href="([^"]+)"[^>]*title="[^"]*episode[^"]*(\d+)[^"]*"/gi;
|
||||
let match;
|
||||
while ((match = linkRegex.exec(gridHtml)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueResults = results.reduce((acc, current) => {
|
||||
const existing = acc.find(item => item.number === current.number);
|
||||
if (!existing) {
|
||||
acc.push(current);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
uniqueResults.sort((a, b) => a.number - b.number);
|
||||
|
||||
return JSON.stringify(uniqueResults);
|
||||
|
||||
} catch (err) {
|
||||
console.error("Error in extractEpisodes:", err);
|
||||
return JSON.stringify([{
|
||||
href: "Error: " + err.message,
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<iframe[^>]+src="(https:\/\/www\.4shared\.com\/web\/embed\/file\/[^"]+)"/i);
|
||||
const video = match ? match[1].trim() : null;
|
||||
|
||||
const response2 = await fetchv2(video);
|
||||
const html2 = await response2.text();
|
||||
|
||||
const match2 = html2.match(/<source[^>]+src="([^"]+)"[^>]*type="video\/mp4"/i);
|
||||
|
||||
return match2 ? match2[1].trim() : "dwa";
|
||||
} catch (err) {
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
}
|
||||
|
||||
function cleanTitle(title) {
|
||||
return title
|
||||
.replace(/’/g, "'")
|
||||
.replace(/–/g, "-")
|
||||
.replace(/&#[0-9]+;/g, "");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"sourceName": "AnimeEpisodeSeries",
|
||||
"iconUrl": "https://animeepisodeseries.com/wp-content/uploads/2019/10/cropped-anime-episode-1-192x192.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "English (DUB/SUB)",
|
||||
"streamType": "mp4",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://www.4shared.com/",
|
||||
"searchBaseUrl": "https://www.4shared.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animeepisodeseries/animeepisodeseries.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": true,
|
||||
"note": "Use external player.",
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const response = await fetchv2("https://animefhd.com/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
const filmListRegex = /<div class="ultAnisContainerItem">[\s\S]*?<\/div>\s*<\/div>/g;
|
||||
const items = html.match(filmListRegex) || [];
|
||||
|
||||
items.forEach((itemHtml) => {
|
||||
const titleMatch = itemHtml.match(/<a href="([^"]+)"[^>]*title="([^"]+)"/);
|
||||
const href = titleMatch ? titleMatch[1] : '';
|
||||
const title = titleMatch ? titleMatch[2] : '';
|
||||
const imgMatch = itemHtml.match(/<img[^>]*src="([^"]+)"[^>]*>/);
|
||||
const imageUrl = imgMatch ? imgMatch[1] : '';
|
||||
|
||||
if (title && href) {
|
||||
results.push({
|
||||
title: title.trim(),
|
||||
image: imageUrl.trim(),
|
||||
href: href.trim(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log(results);
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
const details = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
const descriptionMatch = html.match(/<b>Sinopse:<\/b>\s*<p>\s*([\s\S]*?)\s*<\/p>/);
|
||||
let description = descriptionMatch ? descriptionMatch[1].trim() : 'N/A';
|
||||
|
||||
const airdateMatch = html.match(/<b>Ano:<\/b>\s*(\d{4})/);
|
||||
let airdate = airdateMatch ? airdateMatch[1].trim() : 'N/A';
|
||||
|
||||
const episodesMatch = html.match(/<b>Episódios:<\/b>\s*(\d+)/);
|
||||
let aliases = episodesMatch ? episodesMatch[1].trim() : 'N/A';
|
||||
|
||||
details.push({
|
||||
description: description,
|
||||
alias: "Episódios: " + aliases,
|
||||
airdate: airdate
|
||||
});
|
||||
|
||||
console.log(details);
|
||||
return JSON.stringify(details);
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const episodes = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
const episodeMatches = html.match(/<a href="([^"]+)"[^>]*class="list-epi"[^>]*>Episódio (\d+)<\/a>/g);
|
||||
|
||||
if (episodeMatches) {
|
||||
episodeMatches.forEach(match => {
|
||||
const hrefMatch = match.match(/href="([^"]+)"/);
|
||||
const numberMatch = match.match(/Episódio (\d+)/);
|
||||
|
||||
if (hrefMatch && numberMatch) {
|
||||
episodes.push({
|
||||
href: hrefMatch[1],
|
||||
number: parseInt(numberMatch[1])
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(episodes));
|
||||
return JSON.stringify(episodes);
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
const iframeMatch = html.match(/<iframe[^>]*src="([^"]+)"/);
|
||||
|
||||
if (iframeMatch) {
|
||||
const streamUrl = iframeMatch[1];
|
||||
console.log(streamUrl);
|
||||
const response = await fetch(streamUrl);
|
||||
const newHtml = await response;
|
||||
|
||||
const m3u8Match = newHtml.match(/file:\s*'([^']+\.m3u8)'/);
|
||||
|
||||
if (m3u8Match) {
|
||||
const videoUrl = m3u8Match[1];
|
||||
console.log(videoUrl);
|
||||
return videoUrl;
|
||||
} else {
|
||||
console.log("No m3u8 URL found.");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
console.log("No iframe found.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sourceName": "AnimeFHD",
|
||||
"iconUrl": "https://animefhd.net/wp-content/uploads/2024/12/270.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.2",
|
||||
"language": "Portuguese (SUB)",
|
||||
"streamType": "MP4",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://animefhd.net/",
|
||||
"searchBaseUrl": "https://animefhd.net/?s=%s",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animefhd/animefhd.js",
|
||||
"asyncJS": true,
|
||||
"type": "anime",
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://animemeow.xyz/directorio/?q=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const containerMatch = html.match(/<ul class="grid-animes directorio">([\s\S]*?)<\/ul>/);
|
||||
const containerHtml = containerMatch ? containerMatch[1] : "";
|
||||
|
||||
const regex = /<a href="([^"]+)">[\s\S]*?<div class="main-img">[\s\S]*?<img[^>]+src="([^"]+)"[^>]*>[\s\S]*?<p>([^<]+)<\/p>/g;
|
||||
let match;
|
||||
while ((match = regex.exec(containerHtml)) !== null) {
|
||||
results.push({
|
||||
href: "https://animemeow.xyz" + match[1].trim(),
|
||||
image: "https://animemeow.xyz" + match[2].trim(),
|
||||
title: match[3].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<p class="sinopsis" id="sinopsis">([\s\S]*?)<\/p>/);
|
||||
const description = match ? match[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const containerMatch = html.match(/<ul id="eps">([\s\S]*?)<\/ul>/);
|
||||
const containerHtml = containerMatch ? containerMatch[1] : "";
|
||||
|
||||
const regex = /<a href="([^"]+)">[\s\S]*?<p>[\s\S]*?<\/i>\s*([^<]+)<\/p>/g;
|
||||
let match;
|
||||
while ((match = regex.exec(containerHtml)) !== null) {
|
||||
const href = "https://animemeow.xyz" + match[1].trim();
|
||||
let number;
|
||||
|
||||
if (/Episodio\s*(\d+)/i.test(match[2])) {
|
||||
number = parseInt(match[2].match(/Episodio\s*(\d+)/i)[1], 10);
|
||||
} else if (/Ver Pel[ií]cula/i.test(match[2])) {
|
||||
number = 1;
|
||||
} else {
|
||||
number = "N/A";
|
||||
}
|
||||
|
||||
results.push({ href, number });
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const buttonMatch = html.match(/<button[^>]+data-url="([^"]*voe[^"]+)"[^>]*>/i);
|
||||
if (!buttonMatch) return "https://error.org/";
|
||||
|
||||
const voeLink = buttonMatch[1].split('id=')[1];
|
||||
console.log("VOE Link: " + voeLink);
|
||||
const voeResponse = await fetchv2(voeLink);
|
||||
const voeHtml = await voeResponse.text();
|
||||
|
||||
const redirectMatch = voeHtml.match(/window\.location\.href\s*=\s*['"]([^'"]+)['"]/);
|
||||
if (!redirectMatch) return null;
|
||||
|
||||
const finalLink = redirectMatch[1];
|
||||
|
||||
const streamUrlOne = finalLink;
|
||||
|
||||
const responseTwo = await fetchv2(streamUrlOne);
|
||||
const finalHtml = await responseTwo.text();
|
||||
|
||||
let streamUrl = null;
|
||||
try {
|
||||
streamUrl = voeExtractor(finalHtml);
|
||||
} catch (error) {
|
||||
console.log("VOE extraction error:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log("Voe Stream URL: " + streamUrl);
|
||||
return streamUrl;
|
||||
|
||||
} catch (err) {
|
||||
return "https://error.org/";
|
||||
}
|
||||
}
|
||||
|
||||
/* SCHEME START */
|
||||
|
||||
/**
|
||||
* @name voeExtractor
|
||||
* @author Cufiy
|
||||
*/
|
||||
|
||||
function voeExtractor(html, url = null) {
|
||||
// Extract the first <script type="application/json">...</script>
|
||||
const jsonScriptMatch = html.match(
|
||||
/<script[^>]+type=["']application\/json["'][^>]*>([\s\S]*?)<\/script>/i
|
||||
);
|
||||
if (!jsonScriptMatch) {
|
||||
console.log("No application/json script tag found");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
const obfuscatedJson = jsonScriptMatch[1].trim();
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(obfuscatedJson);
|
||||
} catch (e) {
|
||||
throw new Error("Invalid JSON input.");
|
||||
}
|
||||
if (!Array.isArray(data) || typeof data[0] !== "string") {
|
||||
throw new Error("Input doesn't match expected format.");
|
||||
}
|
||||
let obfuscatedString = data[0];
|
||||
|
||||
// Step 1: ROT13
|
||||
let step1 = voeRot13(obfuscatedString);
|
||||
|
||||
// Step 2: Remove patterns
|
||||
let step2 = voeRemovePatterns(step1);
|
||||
|
||||
// Step 3: Base64 decode
|
||||
let step3 = voeBase64Decode(step2);
|
||||
|
||||
// Step 4: Subtract 3 from each char code
|
||||
let step4 = voeShiftChars(step3, 3);
|
||||
|
||||
// Step 5: Reverse string
|
||||
let step5 = step4.split("").reverse().join("");
|
||||
|
||||
// Step 6: Base64 decode again
|
||||
let step6 = voeBase64Decode(step5);
|
||||
|
||||
// Step 7: Parse as JSON
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(step6);
|
||||
} catch (e) {
|
||||
throw new Error("Final JSON parse error: " + e.message);
|
||||
}
|
||||
// console.log("Decoded JSON:", result);
|
||||
|
||||
// check if direct_access_url is set, not null and starts with http
|
||||
if (result && typeof result === "object") {
|
||||
const streamUrl =
|
||||
result.direct_access_url ||
|
||||
result.source
|
||||
.map((source) => source.direct_access_url)
|
||||
.find((url) => url && url.startsWith("http"));
|
||||
if (streamUrl) {
|
||||
console.log("Voe Stream URL: " + streamUrl);
|
||||
return streamUrl;
|
||||
} else {
|
||||
console.log("No stream URL found in the decoded JSON");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function voeRot13(str) {
|
||||
return str.replace(/[a-zA-Z]/g, function (c) {
|
||||
return String.fromCharCode(
|
||||
(c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13)
|
||||
? c
|
||||
: c - 26
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function voeRemovePatterns(str) {
|
||||
const patterns = ["@$", "^^", "~@", "%?", "*~", "!!", "#&"];
|
||||
let result = str;
|
||||
for (const pat of patterns) {
|
||||
result = result.split(pat).join("");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function voeBase64Decode(str) {
|
||||
// atob is available in browsers and Node >= 16
|
||||
if (typeof atob === "function") {
|
||||
return atob(str);
|
||||
}
|
||||
// Node.js fallback
|
||||
return Buffer.from(str, "base64").toString("utf-8");
|
||||
}
|
||||
|
||||
function voeShiftChars(str, shift) {
|
||||
return str
|
||||
.split("")
|
||||
.map((c) => String.fromCharCode(c.charCodeAt(0) - shift))
|
||||
.join("");
|
||||
}
|
||||
/* SCHEME END */
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "AnimeMeow",
|
||||
"iconUrl": "https://files.catbox.moe/5phbht.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://animemeow.xyz/",
|
||||
"searchBaseUrl": "https://animemeow.xyz/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animemeow/animemeow.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://animeq.blog/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<div class="result-item">[\s\S]*?<a href="([^"]+)"[^>]*>\s*<img src="([^"]+)"[^>]*alt="([^"]+)"/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
image: match[2].trim(),
|
||||
title: cleanHtmlSymbols(match[3].trim())
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<p>\s*Sinopse\s*:\s*([\s\S]*?)<\/p>/i;
|
||||
const match = regex.exec(html);
|
||||
|
||||
const description = match ? match[1].trim() : "fuck off you don't need a description";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
|
||||
const regex = /<div class=['"]?numerando['"]?[^>]*>\d+\s*-\s*(\d+)<\/div>[\s\S]*?<a\s+href=['"]([^'"]+)['"][^>]*>/g;
|
||||
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
const episodeNumber = parseInt(match[1], 10);
|
||||
const href = match[2].trim();
|
||||
|
||||
results.push({
|
||||
href: "episode: " + href,
|
||||
number: episodeNumber
|
||||
});
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
results.push({
|
||||
href: "movie: " + url,
|
||||
number: 1
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
let endpointType;
|
||||
|
||||
if (url.startsWith("movie: ")) {
|
||||
url = url.replace("movie: ", "");
|
||||
endpointType = "movie";
|
||||
} else if (url.startsWith("episode: ")) {
|
||||
url = url.replace("episode: ", "");
|
||||
endpointType = "tv";
|
||||
} else {
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const idMatch = html.match(/<link rel=['"]shortlink['"] href=['"][^?]+\?p=(\d+)['"]/);
|
||||
if (!idMatch) return "ID NOT FOUND";
|
||||
const id = idMatch[1];
|
||||
|
||||
const apiUrl = `https://animeq.blog/wp-json/dooplayer/v2/${id}/${endpointType}/1`;
|
||||
const apiResponse = await fetchv2(apiUrl);
|
||||
const apiData = await apiResponse.json();
|
||||
|
||||
if (!apiData.embed_url) return { error: "embed_url not found" };
|
||||
const embedResponse = await fetchv2(apiData.embed_url);
|
||||
const embedHtml = await embedResponse.text();
|
||||
|
||||
const fileMatch = embedHtml.match(/"file":"(https?:\\\/\\\/[^"]+)"/);
|
||||
if (!fileMatch) return { error: "file not found" };
|
||||
|
||||
const fileUrl = fileMatch[1].replace(/\\\//g, "/");
|
||||
|
||||
return fileUrl;
|
||||
} catch (err) {
|
||||
console.error("Error extracting stream URL:"+ err);
|
||||
return "{ error: err.message }";
|
||||
}
|
||||
}
|
||||
|
||||
function cleanHtmlSymbols(string) {
|
||||
if (!string) return "";
|
||||
return string
|
||||
.replace(/’/g, "'")
|
||||
.replace(/–/g, "-")
|
||||
.replace(/&#[0-9]+;/g, "")
|
||||
.replace(/\r?\n|\r/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "Animeq",
|
||||
"iconUrl": "https://animeq.blog/wp-content/uploads/2025/06/Favicon-AnimeQ-1.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Portuguese",
|
||||
"streamType": "mp4",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://animeq.blog/",
|
||||
"searchBaseUrl": "https://animeq.blog/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animeq/animeq.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": true,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://animesdrive.blog/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<div class="result-item">[\s\S]*?<a href="([^"]+)"[^>]*>\s*<img src="([^"]+)"[^>]*alt="([^"]+)"/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
image: match[2].trim(),
|
||||
title: cleanHtmlSymbols(match[3].trim())
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<p[^>]*>\s*Sinopse\s*:\s*([\s\S]*?)<\/p>/i;
|
||||
const match = regex.exec(html);
|
||||
|
||||
const description = match ? match[1].trim() : "fuck off you don't need a description";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
|
||||
const regex = /<div class=['"]?numerando['"]?[^>]*>\d+\s*-\s*(\d+)<\/div>[\s\S]*?<a\s+href=['"]([^'"]+)['"][^>]*>/g;
|
||||
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
const episodeNumber = parseInt(match[1], 10);
|
||||
const href = match[2].trim();
|
||||
|
||||
results.push({
|
||||
href: "episode: " + href,
|
||||
number: episodeNumber
|
||||
});
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
results.push({
|
||||
href: "movie: " + url,
|
||||
number: 1
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
let endpointType;
|
||||
|
||||
if (url.startsWith("movie: ")) {
|
||||
url = url.replace("movie: ", "");
|
||||
endpointType = "movie";
|
||||
} else if (url.startsWith("episode: ")) {
|
||||
url = url.replace("episode: ", "");
|
||||
endpointType = "tv";
|
||||
} else {
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const idMatch = html.match(/<link rel=['"]shortlink['"] href=['"][^?]+\?p=(\d+)['"]/);
|
||||
if (!idMatch) return "ID NOT FOUND";
|
||||
const id = idMatch[1];
|
||||
|
||||
const apiUrl = `https://animesdrive.blog/wp-json/dooplayer/v2/${id}/${endpointType}/1`;
|
||||
const apiResponse = await fetchv2(apiUrl);
|
||||
const apiData = await apiResponse.json();
|
||||
console.log(JSON.stringify(apiData));
|
||||
|
||||
const embedResponse = await fetchv2(apiData.embed_url);
|
||||
const embedHtml = await embedResponse.text();
|
||||
|
||||
const match = embedHtml.match(/<source\s+src="([^"]+)"\s+type="video\/mp4"/i);
|
||||
const finalUrl = match ? match[1] : null;
|
||||
console.log("Final URL: " + finalUrl);
|
||||
|
||||
return finalUrl
|
||||
} catch (err) {
|
||||
console.error("Error extracting stream URL:"+ err);
|
||||
return "{ error: err.message }";
|
||||
}
|
||||
}
|
||||
|
||||
function cleanHtmlSymbols(string) {
|
||||
if (!string) return "";
|
||||
return string
|
||||
.replace(/’/g, "'")
|
||||
.replace(/–/g, "-")
|
||||
.replace(/&#[0-9]+;/g, "")
|
||||
.replace(/\r?\n|\r/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "AnimesDrive",
|
||||
"iconUrl": "https://animesdrive.blog/wp-content/uploads/2025/08/cropped-ico-1-192x192.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Portuguese",
|
||||
"streamType": "mp4",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://animesdrive.blog/",
|
||||
"searchBaseUrl": "https://animesdrive.blog/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animesdrive/animesdrive.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": true,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://animesonline.cloud/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<div class="result-item">[\s\S]*?<a href="([^"]+)"[^>]*>\s*<img src="([^"]+)"[^>]*alt="([^"]+)"/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
image: match[2].trim(),
|
||||
title: cleanHtmlSymbols(match[3].trim())
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<p>\s*Sinopse\s*:\s*([\s\S]*?)<\/p>/i;
|
||||
const match = regex.exec(html);
|
||||
|
||||
const description = match ? match[1].trim() : "fuck off you don't need a description";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
|
||||
const regex = /<div class=['"]?numerando['"]?[^>]*>\d+\s*-\s*(\d+)<\/div>[\s\S]*?<a\s+href=['"]([^'"]+)['"][^>]*>/g;
|
||||
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
const episodeNumber = parseInt(match[1], 10);
|
||||
const href = match[2].trim();
|
||||
|
||||
results.push({
|
||||
href: "episode: " + href,
|
||||
number: episodeNumber
|
||||
});
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
results.push({
|
||||
href: "movie: " + url,
|
||||
number: 1
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
let endpointType;
|
||||
|
||||
if (url.startsWith("movie: ")) {
|
||||
url = url.replace("movie: ", "");
|
||||
endpointType = "movie";
|
||||
} else if (url.startsWith("episode: ")) {
|
||||
url = url.replace("episode: ", "");
|
||||
endpointType = "tv";
|
||||
} else {
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const idMatch = html.match(/<link rel=['"]shortlink['"] href=['"][^?]+\?p=(\d+)['"]/);
|
||||
if (!idMatch) return "ID NOT FOUND";
|
||||
const id = idMatch[1];
|
||||
|
||||
const apiUrl = `https://animesonline.cloud/wp-json/dooplayer/v2/${id}/${endpointType}/1`;
|
||||
const apiResponse = await fetchv2(apiUrl);
|
||||
const apiData = await apiResponse.json();
|
||||
console.log(JSON.stringify(apiData));
|
||||
|
||||
if (!apiData.embed_url) return { error: "embed_url not found" };
|
||||
const embedResponse = await fetchv2(apiData.embed_url);
|
||||
const embedHtml = await embedResponse.text();
|
||||
console.log(embedHtml);
|
||||
|
||||
const fileMatch = embedHtml.match(/"file":"(https?:\\\/\\\/[^"]+)"/);
|
||||
if (!fileMatch) return { error: "file not found" };
|
||||
|
||||
const fileUrl = fileMatch[1].replace(/\\\//g, "/");
|
||||
|
||||
let finalUrl = encodeURI(fileUrl);
|
||||
|
||||
console.log(finalUrl);
|
||||
|
||||
return finalUrl;
|
||||
} catch (err) {
|
||||
console.error("Error extracting stream URL:"+ err);
|
||||
return "{ error: err.message }";
|
||||
}
|
||||
}
|
||||
|
||||
function cleanHtmlSymbols(string) {
|
||||
if (!string) return "";
|
||||
return string
|
||||
.replace(/’/g, "'")
|
||||
.replace(/–/g, "-")
|
||||
.replace(/&#[0-9]+;/g, "")
|
||||
.replace(/\r?\n|\r/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "AnimesOnline",
|
||||
"iconUrl": "https://animesonline.cloud/wp-content/uploads/2025/06/Icone.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Portuguese",
|
||||
"streamType": "mp4",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://animesonline.cloud",
|
||||
"searchBaseUrl": "https://animesonline.cloud",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animesonline/animesonline.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": true,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(
|
||||
"https://api-search.anroll.net/data?q=" + encodeURIComponent(keyword)
|
||||
);
|
||||
const json = await response.json();
|
||||
|
||||
if (json.code === 200 && json.data && Array.isArray(json.data)) {
|
||||
for (const item of json.data) {
|
||||
if (item.generic_path && item.generic_path.startsWith("/f/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results.push({
|
||||
title: item.title,
|
||||
image: `https://www.anroll.net/_next/image?url=${encodeURIComponent(
|
||||
"https://static.anroll.net/images/animes/capas/" + item.slug + ".jpg"
|
||||
)}&w=384&q=75`,
|
||||
href: "https://www.anroll.net" + item.generic_path,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([
|
||||
{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error",
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
console.log(url);
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<div class="sinopse">(.*?)<\/div>/s);
|
||||
const description = match ? match[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([
|
||||
{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A",
|
||||
},
|
||||
]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([
|
||||
{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error",
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
try {
|
||||
const results = [];
|
||||
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const episodesMatch = html.match(/"episodes"\s*:\s*(\d+)/);
|
||||
const episodes = episodesMatch ? parseInt(episodesMatch[1], 10) : null;
|
||||
const idMatch = html.match(/"id_serie"\s*:\s*(\d+)/);
|
||||
const serieId = idMatch ? parseInt(idMatch[1], 10) : null;
|
||||
|
||||
if (!episodes || !serieId) {
|
||||
console.log("Failed to extract episode count or serie ID.");
|
||||
return JSON.stringify([{ href: "Error", number: "Error" }]);
|
||||
}
|
||||
|
||||
const totalPages = Math.ceil(episodes / 25);
|
||||
|
||||
const pagePromises = [];
|
||||
for (let page = 1; page <= totalPages; page++) {
|
||||
const apiUrl = `https://apiv3-prd.anroll.net/animes/${serieId}/episodes?page=${page}&order=desc`;
|
||||
|
||||
pagePromises.push(
|
||||
fetchv2(apiUrl)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
console.log(`Fetched page ${page} of ${totalPages}...`);
|
||||
if (json && json.data && Array.isArray(json.data)) {
|
||||
return json.data.map(ep => ({
|
||||
href: "https://www.anroll.net/watch/e/" + ep.generate_id,
|
||||
number: parseInt(ep.n_episodio, 10),
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(`Error fetching page ${page}:`, error);
|
||||
return [];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const pageResults = await Promise.all(pagePromises);
|
||||
|
||||
pageResults.forEach(pageEpisodes => {
|
||||
results.push(...pageEpisodes);
|
||||
});
|
||||
|
||||
return JSON.stringify(results.reverse());
|
||||
} catch (err) {
|
||||
console.log("Error during fetch:", err);
|
||||
return JSON.stringify([{ href: "Error", number: "Error" }]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const patterns = [
|
||||
/"streamUrl"\s*:\s*"([^"]+\.m3u8)"/,
|
||||
/"streamUrl\\?"\s*:\s*\\?"([^"\\]+\.m3u8)\\?"/,
|
||||
/streamUrl['"]\s*:\s*['"](https?:\/\/[^'"]+\.m3u8)['"]/,
|
||||
/streamUrl\s*:\s*"([^"]+\.m3u8)"/,
|
||||
/\\"streamUrl\\":\\"([^"\\]+\.m3u8)\\"/
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = html.match(pattern);
|
||||
if (match && match[1]) {
|
||||
const streamUrl = match[1]
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\\//g, '/')
|
||||
.replace(/\\\\/g, '\\');
|
||||
return streamUrl;
|
||||
}
|
||||
}
|
||||
|
||||
const nextDataMatch = html.match(/self\.__next_f\.push\(\[1,"([^"]+)"\]\)/g);
|
||||
if (nextDataMatch) {
|
||||
for (const match of nextDataMatch) {
|
||||
const dataMatch = match.match(/self\.__next_f\.push\(\[1,"([^"]+)"\]\)/);
|
||||
if (dataMatch && dataMatch[1]) {
|
||||
const decodedData = dataMatch[1]
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\\//g, '/')
|
||||
.replace(/\\n/g, '\n');
|
||||
|
||||
const streamMatch = decodedData.match(/streamUrl['"]\s*:\s*['"](https?:\/\/[^'"]+\.m3u8)['"]/);
|
||||
if (streamMatch && streamMatch[1]) {
|
||||
return streamMatch[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error extracting stream URL:', err);
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "AnimesRoll",
|
||||
"iconUrl": "https://cdn.countryflags.com/thumbs/portugal/flag-button-round-250.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Portuguese",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://www.anroll.net/",
|
||||
"searchBaseUrl": "https://www.anroll.net/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animesroll/animesroll.js",
|
||||
"type": "animes",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
function cleanTitle(title) {
|
||||
return title
|
||||
.replace(/’/g, "'")
|
||||
.replace(/–/g, "-")
|
||||
.replace(/&#[0-9]+;/g, "");
|
||||
}
|
||||
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const response = await fetchv2(`https://animeytx.net/?s=${keyword}`);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<article class="bs"[^>]*>[\s\S]*?<a href="([^"]+)"[^>]*>[\s\S]*?(?:data-src|src)="([^"]+)"[^>]*>[\s\S]*?<h2[^>]*>(.*?)<\/h2>/gs;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: cleanTitle(match[3].trim()),
|
||||
image: match[2].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<div class="entry-content"[^>]*>([\s\S]*?)<\/div>/);
|
||||
|
||||
let description = "N/A";
|
||||
if (match) {
|
||||
description = match[1]
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&/g, "&")
|
||||
.trim();
|
||||
}
|
||||
|
||||
results.push({
|
||||
description: description,
|
||||
aliases: 'N/A',
|
||||
airdate: 'N/A'
|
||||
});
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<li data-index="\d+">[\s\S]*?<a href="([^"]+)">/g;
|
||||
|
||||
let match;
|
||||
let count = 1;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: encodeURI(match[1].trim()),
|
||||
number: count
|
||||
});
|
||||
count++;
|
||||
}
|
||||
|
||||
const total = results.length;
|
||||
results.forEach((ep, i) => {
|
||||
ep.number = total - i;
|
||||
});
|
||||
|
||||
return JSON.stringify(results.reverse());
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(`https://passthrough-worker.simplepostrequest.workers.dev/?simple=${encodeURIComponent(url)}`);
|
||||
const html = await response.text();
|
||||
|
||||
// Try primary method first
|
||||
let iframeMatch = html.match(/<iframe[^>]+data-src="([^"]+)"/);
|
||||
let filemoonUrl;
|
||||
|
||||
if (iframeMatch) {
|
||||
const iframeUrl = iframeMatch[1];
|
||||
const valueMatch = iframeUrl.match(/value=([^&]+)/);
|
||||
if (valueMatch) {
|
||||
const valueId = valueMatch[1];
|
||||
const containerResponse = await fetchv2(`https://mytsumi.com/multiplayer/contenedor.php?id=${valueId}`);
|
||||
const containerHtml = await containerResponse.text();
|
||||
const filemoonMatch = containerHtml.match(/"tab_name":"Moon","url":"([^"]+)"/);
|
||||
if (filemoonMatch) filemoonUrl = filemoonMatch[1].replace(/\\\//g, '/');
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback if no Filemoon found
|
||||
if (!filemoonUrl) {
|
||||
const optionMatch = html.match(/<option[^>]+value="([^"]+)"[^>]*>\s*Moon\s*<\/option>/);
|
||||
if (!optionMatch) return "https://error.org/";
|
||||
|
||||
const decoded = atob(optionMatch[1]);
|
||||
const srcMatch = decoded.match(/src="([^"]+)"/);
|
||||
if (!srcMatch) return "https://error.org/";
|
||||
|
||||
const fallbackUrl = srcMatch[1];
|
||||
const fallbackResponse = await fetchv2(fallbackUrl);
|
||||
const fallbackHtml = await fallbackResponse.text();
|
||||
const filemoonMatch2 = fallbackHtml.match(/<a href="(https:\/\/filemoon\.to\/e\/[^"]+)">/);
|
||||
if (!filemoonMatch2) return "https://error.org/";
|
||||
|
||||
filemoonUrl = filemoonMatch2[1];
|
||||
}
|
||||
console.log(filemoonUrl);
|
||||
const headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Referer": url
|
||||
};
|
||||
const finalResponse = await fetchv2(filemoonUrl, headers);
|
||||
const finalHtml = await finalResponse.text();
|
||||
const streamUrl = await filemoonExtractor(finalHtml, filemoonUrl);
|
||||
if (streamUrl) return streamUrl;
|
||||
return "filemoonUrl";
|
||||
} catch (err) {
|
||||
return "https://error.org/";
|
||||
}
|
||||
}
|
||||
|
||||
/* SCHEME START */
|
||||
/* {REQUIRED PLUGINS: unbaser} */
|
||||
|
||||
/**
|
||||
* @name filemoonExtractor
|
||||
* @author Cufiy - Inspired by Churly
|
||||
*/
|
||||
|
||||
async function filemoonExtractor(html, url = null) {
|
||||
// check if contains iframe, if does, extract the src and get the url
|
||||
const regex = /<iframe[^>]+src="([^"]+)"[^>]*><\/iframe>/;
|
||||
const match = html.match(regex);
|
||||
if (match) {
|
||||
console.log("Iframe URL: " + match[1]);
|
||||
const iframeUrl = match[1];
|
||||
const iframeResponse = await soraFetch(iframeUrl, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Referer": url,
|
||||
}
|
||||
});
|
||||
console.log("Iframe Response: " + iframeResponse.status);
|
||||
html = await iframeResponse.text();
|
||||
}
|
||||
// console.log("HTML: " + html);
|
||||
// get /<script[^>]*>([\s\S]*?)<\/script>/gi
|
||||
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
||||
const scripts = [];
|
||||
let scriptMatch;
|
||||
while ((scriptMatch = scriptRegex.exec(html)) !== null) {
|
||||
scripts.push(scriptMatch[1]);
|
||||
}
|
||||
// get the script with eval and m3u8
|
||||
const evalRegex = /eval\((.*?)\)/;
|
||||
const m3u8Regex = /m3u8/;
|
||||
// console.log("Scripts: " + scripts);
|
||||
const evalScript = scripts.find(script => evalRegex.test(script) && m3u8Regex.test(script));
|
||||
if (!evalScript) {
|
||||
console.log("No eval script found");
|
||||
return null;
|
||||
}
|
||||
const unpackedScript = unpack(evalScript);
|
||||
// get the m3u8 url
|
||||
const m3u8Regex2 = /https?:\/\/[^\s]+master\.m3u8[^\s]*?(\?[^"]*)?/;
|
||||
const m3u8Match = unpackedScript.match(m3u8Regex2);
|
||||
if (m3u8Match) {
|
||||
return m3u8Match[0];
|
||||
} else {
|
||||
console.log("No M3U8 URL found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* REMOVE_START */
|
||||
|
||||
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function unpack(source) {
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses Sora's fetchv2 on ipad, fallbacks to regular fetch on Windows
|
||||
* @author ShadeOfChaos
|
||||
*
|
||||
* @param {string} url The URL to make the request to.
|
||||
* @param {object} [options] The options to use for the request.
|
||||
* @param {object} [options.headers] The headers to send with the request.
|
||||
* @param {string} [options.method='GET'] The method to use for the request.
|
||||
* @param {string} [options.body=null] The body of the request.
|
||||
*
|
||||
* @returns {Promise<Response|null>} The response from the server, or null if the
|
||||
* request failed.
|
||||
*/
|
||||
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
|
||||
try {
|
||||
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
|
||||
} catch(e) {
|
||||
try {
|
||||
return await fetch(url, options);
|
||||
} catch(error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* REMOVE_END */
|
||||
|
||||
/* SCHEME END */
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "AnimeYTX",
|
||||
"iconUrl": "https://i1.wp.com/animeytx.net/wp-content/uploads/2024/09/cropped-hgn584ghj45-1-192x192.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://animeytx.net/",
|
||||
"searchBaseUrl": "https://animeytx.net/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animeytx/animeytx.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://api-search.anroll.net/data?q=" + encodeURIComponent(keyword));
|
||||
const data = await response.json();
|
||||
|
||||
data.data.forEach(item => {
|
||||
const baseUrl = item.generic_path.trim().startsWith('/f/')
|
||||
? "https://www.anroll.net/_next/image?url=https://static.anroll.net/images/filmes/capas/"
|
||||
: "https://www.anroll.net/_next/image?url=https://static.anroll.net/images/animes/capas/";
|
||||
|
||||
results.push({
|
||||
title: item.title.trim(),
|
||||
image: baseUrl + item.slug.trim() + ".jpg&w=384&q=75",
|
||||
href: item.generic_path.trim()
|
||||
});
|
||||
});
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(slug) {
|
||||
try {
|
||||
const response = await fetchv2("https://www.anroll.net/" + slug);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<div class="sinopse">(.*?)<\/div>/s);
|
||||
const description = match ? match[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function extractEpisodes(slug) {
|
||||
const results = [];
|
||||
try {
|
||||
// If slug starts with /f/ (movie), extract ID and return as single episode
|
||||
if (slug.startsWith('/f/')) {
|
||||
results.push({
|
||||
href: slug,
|
||||
number: 1
|
||||
});
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
// Only process if slug starts with /a/ (anime)
|
||||
if (!slug.startsWith('/a/')) {
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
const response = await fetchv2("https://www.anroll.net" + slug);
|
||||
const html = await response.text();
|
||||
|
||||
// Extract series ID from __NEXT_DATA__
|
||||
const scriptMatch = html.match(/<script id="__NEXT_DATA__" type="application\/json">({.*?})<\/script>/);
|
||||
if (!scriptMatch) return JSON.stringify(results);
|
||||
|
||||
const jsonData = JSON.parse(scriptMatch[1]);
|
||||
const seriesId = jsonData?.props?.pageProps?.data?.id_serie;
|
||||
if (!seriesId) return JSON.stringify(results);
|
||||
|
||||
// Fetch all episodes from all pages
|
||||
let page = 1;
|
||||
let hasNextPage = true;
|
||||
|
||||
while (hasNextPage) {
|
||||
const episodesResponse = await fetchv2(`https://apiv3-prd.anroll.net/animes/${seriesId}/episodes?page=${page}&order=desc`);
|
||||
const episodesData = await episodesResponse.json();
|
||||
|
||||
if (episodesData.data && episodesData.data.length > 0) {
|
||||
episodesData.data.forEach(episode => {
|
||||
results.push({
|
||||
href: episode.generate_id,
|
||||
number: parseInt(episode.n_episodio, 10)
|
||||
});
|
||||
});
|
||||
|
||||
hasNextPage = episodesData.meta?.hasNextPage || false;
|
||||
page++;
|
||||
} else {
|
||||
hasNextPage = false;
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results.reverse());
|
||||
} catch (err) {
|
||||
return JSON.stringify([]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(ID) {
|
||||
try {
|
||||
if (!ID.startsWith('/f/')) {
|
||||
const response = await fetchv2("https://www.anroll.net/watch/e/" + ID);
|
||||
const html = await response.text();
|
||||
|
||||
const streamMatch = html.match(/streamUrl\\?":\\?"(https:\/\/[^"\\]+\.m3u8)/);
|
||||
|
||||
if (!streamMatch) return JSON.stringify({
|
||||
streams: [{
|
||||
title: "Server 1",
|
||||
streamUrl: "https://error.org/no-stream-found",
|
||||
headers: {}
|
||||
}],
|
||||
subtitle: ""
|
||||
});
|
||||
|
||||
const streamUrl = streamMatch[1];
|
||||
|
||||
return JSON.stringify({
|
||||
streams: [
|
||||
{
|
||||
title: "Server 1",
|
||||
streamUrl: streamUrl,
|
||||
headers: {
|
||||
"Referer": "https://www.anroll.net/",
|
||||
"Origin": "https://www.anroll.net"
|
||||
}
|
||||
}
|
||||
],
|
||||
subtitle: ""
|
||||
});
|
||||
}
|
||||
|
||||
const response = await fetchv2("https://www.anroll.net" + ID);
|
||||
const html = await response.text();
|
||||
|
||||
const scriptMatch = html.match(/<script id="__NEXT_DATA__" type="application\/json">({.*?})<\/script>/);
|
||||
if (!scriptMatch) return "https://error.org/no-next-data";
|
||||
|
||||
const jsonData = JSON.parse(scriptMatch[1]);
|
||||
const movieData = jsonData?.props?.pageProps?.data?.data_movie;
|
||||
|
||||
if (!movieData || !movieData.slug_filme) {
|
||||
return "https://error.org/no-movie-data";
|
||||
}
|
||||
|
||||
const slug = movieData.slug_filme;
|
||||
const streamUrl = `https://cdn-zenitsu-2-gamabunta.b-cdn.net/cf/hls/movies/${slug}/movie.mp4/media-1/stream.m3u8`;
|
||||
|
||||
return JSON.stringify({
|
||||
streams: [
|
||||
{
|
||||
title: "Server 1",
|
||||
streamUrl: streamUrl,
|
||||
headers: {
|
||||
"Referer": "https://www.anroll.net/",
|
||||
"Origin": "https://www.anroll.net"
|
||||
}
|
||||
}
|
||||
],
|
||||
subtitle: ""
|
||||
});
|
||||
} catch (err) {
|
||||
return "https://error.org/";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "AnimesRoll",
|
||||
"iconUrl": "https://files.catbox.moe/9lx2bp.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://www.anroll.net/",
|
||||
"searchBaseUrl": "https://www.anroll.net/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anroll/anroll.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": false,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
|
||||
"Referer": "https://arablionztv.cam/"
|
||||
};
|
||||
const postData = "";
|
||||
try {
|
||||
const response = await fetchv2("https://arablionztv.cam/SearchEngine/"+encodeURIComponent(keyword), headers, "POST", postData);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)"[^>]*>\s*<div class="Box--Poster">\s*<img[^>]+data-image="([^"]+)"[^>]*>\s*.*?<h2>(.*?)<\/h2>/gs;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: match[3].trim(),
|
||||
image: match[2].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<p class="Singular--Story-P">(.*?)<\/p>/s;
|
||||
const match = html.match(regex);
|
||||
const description = match ? match[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
try {
|
||||
return JSON.stringify([{
|
||||
href: url,
|
||||
number: 1
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const idMatch = html.match(/data-id="(\d+)"/);
|
||||
if (!idMatch) return "https://error.org/";
|
||||
const id = idMatch[1];
|
||||
|
||||
const apiUrl = "https://passthrough-worker.simplepostrequest.workers.dev/?arablionz=https://arablionztv.cam/PostServersWatch/" + id;
|
||||
const apiResponse = await fetchv2(apiUrl);
|
||||
const apiHtml = await apiResponse.text();
|
||||
|
||||
const iframeMatch = apiHtml.match(/<iframe[^>]+src="([^"]+)"/);
|
||||
if (!iframeMatch) return "https://error.org/";
|
||||
const iframeUrl = iframeMatch[1];
|
||||
const headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
|
||||
"Referer": "https://arablionztv.cam/"
|
||||
};
|
||||
const streamResponse = await fetchv2(iframeUrl, headers);
|
||||
const streamHtml = await streamResponse.text();
|
||||
|
||||
const obfuscatedScript = streamHtml.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
|
||||
if (!obfuscatedScript) return "https://error.org/";
|
||||
const unpackedScript = unpack(obfuscatedScript[1]);
|
||||
|
||||
const fileMatch = unpackedScript.match(/file:\s*"([^"]+)"/);
|
||||
if (!fileMatch) return "https://error.org/";
|
||||
const fileUrl = fileMatch[1];
|
||||
console.log(fileUrl);
|
||||
return fileUrl;
|
||||
} catch (err) {
|
||||
return "https://error.org/";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************
|
||||
* UNPACKER MODULE
|
||||
* Credit to GitHub user "mnsrulz" for Unpacker Node library
|
||||
* https://github.com/mnsrulz/unpacker
|
||||
***********************************************************/
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
/* Functor for a given base. Will efficiently convert
|
||||
strings to natural numbers. */
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
// fill elements 37...61, if necessary
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
// If base can be handled by int() builtin, let it do it for us
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
// Build conversion dictionary cache
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
/* Decodes a value to an integer. */
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
function detect(source) {
|
||||
/* Detects whether `source` is P.A.C.K.E.R. coded. */
|
||||
return source.replace(" ", "").startsWith("eval(function(p,a,c,k,e,");
|
||||
}
|
||||
|
||||
function unpack(source) {
|
||||
/* Unpacks P.A.C.K.E.R. packed js code. */
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
/* Look up symbols in the synthetic symtab. */
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
//throw Error("symtab unknown");
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
/* Juice from a source file the four args needed by decoder. */
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
//const args = re.search(juicer, source, re.DOTALL);
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
//don't know what it is
|
||||
// a = list(a);
|
||||
// a[1] = 62;
|
||||
// a = tuple(a);
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
/* Strip string lookup table (list) and replace values in source. */
|
||||
/* Need to work on this. */
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sourceName": "ArabLionz",
|
||||
"iconUrl": "https://arablionztv.cam/wp-content/uploads/2023/04/vRA6ewKk_400x400-280x280.jpeg",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Arabic",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://arablionztv.cam/",
|
||||
"searchBaseUrl": "https://arablionztv.cam/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/arablionz/arablionz.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"downloadSupport": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://a.asd.homes/find/?word=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<li class="box__xs__2[^>]*>[\s\S]*?<a href="([^"]+)"[^>]*>[\s\S]*?<img[^>]+src="([^"]+)"[^>]*>[\s\S]*?<h3>(.*?)<\/h3>[\s\S]*?<\/a>/g;
|
||||
|
||||
let match;
|
||||
const tempResults = [];
|
||||
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
const cleanedTitle = match[3].replace(/الموسم\s+\S+\s+الحلقة\s+\S+.*$/u, '').trim();
|
||||
tempResults.push({
|
||||
href: match[1].trim(),
|
||||
image: match[2].trim(),
|
||||
title: cleanedTitle
|
||||
});
|
||||
}
|
||||
|
||||
const combined = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const item of tempResults) {
|
||||
if (!seen.has(item.title)) {
|
||||
seen.add(item.title);
|
||||
combined.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(combined);
|
||||
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<div class="post__story">\s*<p>(.*?)<\/p>\s*<\/div>/s);
|
||||
|
||||
let description = "N/A";
|
||||
|
||||
if (match) {
|
||||
const rawDescription = match[1];
|
||||
description = rawDescription.replace(/<\/?span[^>]*>/g, '').trim();
|
||||
description = description.replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const episodesListMatch = html.match(/<ul class="episodes__list[^>]*>([\s\S]*?)<\/ul>/);
|
||||
if (!episodesListMatch) {
|
||||
results.push({
|
||||
href: url,
|
||||
number: 1
|
||||
});
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
const episodesHTML = episodesListMatch[1];
|
||||
|
||||
const episodeItemRegex = /<li[^>]*>[\s\S]*?<a href="([^"]+)"[\s\S]*?الحلقة<b>(\d+)<\/b>[\s\S]*?<\/a>[\s\S]*?<\/li>/g;
|
||||
let match;
|
||||
const episodes = [];
|
||||
|
||||
while ((match = episodeItemRegex.exec(episodesHTML)) !== null) {
|
||||
const href = match[1].trim();
|
||||
const episodeNumber = parseInt(match[2]);
|
||||
episodes.push({
|
||||
href: href,
|
||||
number: episodeNumber
|
||||
});
|
||||
}
|
||||
|
||||
episodes.sort((a, b) => a.number - b.number);
|
||||
|
||||
results.push(...episodes);
|
||||
|
||||
if (results.length === 0) {
|
||||
results.push({
|
||||
href: url,
|
||||
number: 1
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: url,
|
||||
number: 1
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/href="([^"]+)"[^>]*class="btton watch__btn"/);
|
||||
console.log("Match found: " + match);
|
||||
if (match) {
|
||||
const extractedUrl = match[1].replace(/&/g, '&');
|
||||
const headers = {
|
||||
"Referer": "https://a.asd.homes/"
|
||||
};
|
||||
const extractedResponse = await fetchv2(extractedUrl, headers);
|
||||
const extractedHtml = await extractedResponse.text();
|
||||
console.log("Extracted HTML snippet:"+ extractedHtml);
|
||||
|
||||
const embedMatch = extractedHtml.match(/<iframe[^>]*src="([^"]+)"/);
|
||||
if (embedMatch) {
|
||||
const embedUrl = embedMatch[1];
|
||||
const embedResponse = await fetchv2(embedUrl, headers);
|
||||
const embedHtml = await embedResponse.text();
|
||||
|
||||
const sourceMatch = embedHtml.match(/<source src="([^"]+)"/);
|
||||
if (sourceMatch) {
|
||||
return sourceMatch[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sourceName": "ArabSeed",
|
||||
"iconUrl": "https://a.asd.homes/wp-content/themes/Elshaikh2021/UI/images/logo333.webp",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.2",
|
||||
"language": "Arabic",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://a.asd.homes/",
|
||||
"searchBaseUrl": "https://a.asd.homes/?s=%s",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/arabseed/arabseed.js",
|
||||
"type": "shows/movies/anime",
|
||||
"asyncJS": true,
|
||||
"downloadSupport": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://asialiveaction.com/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const containerMatch = html.match(/<ul class="navegacion-grid">([\s\S]*?)<\/ul>/);
|
||||
if (!containerMatch) return JSON.stringify([]);
|
||||
|
||||
const containerHtml = containerMatch[1];
|
||||
|
||||
const regex = /<a href="([^"]+)"[^>]*>[\s\S]*?<h5 class="carousel-title">([\s\S]*?)<\/h5>[\s\S]*?<img[^>]+src="([^"]+)"/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(containerHtml)) !== null) {
|
||||
results.push({
|
||||
title: match[2].trim(),
|
||||
image: match[3].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const titleMatch = html.match(/<h2 class="Title">\s*([\s\S]*?)\s*<\/h2>/);
|
||||
const descriptionMatch = html.match(/<p>([\s\S]*?)<\/p>/);
|
||||
|
||||
const description = descriptionMatch ? descriptionMatch[1].trim() : "N/A";
|
||||
const title = titleMatch ? titleMatch[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: title,
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const listMatch = html.match(/<div class="lista-episodios">([\s\S]*?)<\/div>\s*<\/div>/);
|
||||
if (!listMatch) return JSON.stringify([]);
|
||||
|
||||
const listHtml = listMatch[1];
|
||||
|
||||
const regex = /<a href="([^"]+)">[\s\S]*?<b class="numero-episodio">Episodio (\d+)<\/b>/g;
|
||||
let match;
|
||||
while ((match = regex.exec(listHtml)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{ href: "Error", number: "Error" }]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/\["FM","(https:\\?\/\\?\/filemoon\.to\\?\/e\\?\/[^"]+)"/);
|
||||
|
||||
const filemoonLink = match[1].replace(/\\\//g, "/");
|
||||
console.log(filemoonLink);
|
||||
|
||||
const response2 = await fetchv2(filemoonLink);
|
||||
const html2 = await response2.text();
|
||||
let streamUrl = null;
|
||||
try {
|
||||
streamUrl = await filemoonExtractor(html2, filemoonLink);
|
||||
} catch (error) {
|
||||
console.log("filemoon HD extraction error:" + error);
|
||||
}
|
||||
|
||||
console.log("filemoon Stream URL: " + streamUrl);
|
||||
if (streamUrl && streamUrl !== false && streamUrl !== null) {
|
||||
return streamUrl;
|
||||
}
|
||||
|
||||
console.log("No stream URL found");
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.log("Fetch error:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* SCHEME START */
|
||||
/* {REQUIRED PLUGINS: unbaser} */
|
||||
|
||||
/**
|
||||
* @name filemoonExtractor
|
||||
* @author Cufiy - Inspired by Churly
|
||||
*/
|
||||
|
||||
async function filemoonExtractor(html, url = null) {
|
||||
// check if contains iframe, if does, extract the src and get the url
|
||||
const regex = /<iframe[^>]+src="([^"]+)"[^>]*><\/iframe>/;
|
||||
const match = html.match(regex);
|
||||
if (match) {
|
||||
console.log("Iframe URL: " + match[1]);
|
||||
const iframeUrl = match[1];
|
||||
const iframeResponse = await soraFetch(iframeUrl, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Referer": url,
|
||||
}
|
||||
});
|
||||
console.log("Iframe Response: " + iframeResponse.status);
|
||||
html = await iframeResponse.text();
|
||||
}
|
||||
// console.log("HTML: " + html);
|
||||
// get /<script[^>]*>([\s\S]*?)<\/script>/gi
|
||||
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
||||
const scripts = [];
|
||||
let scriptMatch;
|
||||
while ((scriptMatch = scriptRegex.exec(html)) !== null) {
|
||||
scripts.push(scriptMatch[1]);
|
||||
}
|
||||
// get the script with eval and m3u8
|
||||
const evalRegex = /eval\((.*?)\)/;
|
||||
const m3u8Regex = /m3u8/;
|
||||
// console.log("Scripts: " + scripts);
|
||||
const evalScript = scripts.find(script => evalRegex.test(script) && m3u8Regex.test(script));
|
||||
if (!evalScript) {
|
||||
console.log("No eval script found");
|
||||
return null;
|
||||
}
|
||||
const unpackedScript = unpack(evalScript);
|
||||
// get the m3u8 url
|
||||
const m3u8Regex2 = /https?:\/\/[^\s]+master\.m3u8[^\s]*?(\?[^"]*)?/;
|
||||
const m3u8Match = unpackedScript.match(m3u8Regex2);
|
||||
if (m3u8Match) {
|
||||
return m3u8Match[0];
|
||||
} else {
|
||||
console.log("No M3U8 URL found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* REMOVE_START */
|
||||
|
||||
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function unpack(source) {
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses Sora's fetchv2 on ipad, fallbacks to regular fetch on Windows
|
||||
* @author ShadeOfChaos
|
||||
*
|
||||
* @param {string} url The URL to make the request to.
|
||||
* @param {object} [options] The options to use for the request.
|
||||
* @param {object} [options.headers] The headers to send with the request.
|
||||
* @param {string} [options.method='GET'] The method to use for the request.
|
||||
* @param {string} [options.body=null] The body of the request.
|
||||
*
|
||||
* @returns {Promise<Response|null>} The response from the server, or null if the
|
||||
* request failed.
|
||||
*/
|
||||
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
|
||||
try {
|
||||
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
|
||||
} catch(e) {
|
||||
try {
|
||||
return await fetch(url, options);
|
||||
} catch(error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* REMOVE_END */
|
||||
|
||||
/* SCHEME END */
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "AsiaLiveAction",
|
||||
"iconUrl": "https://asialiveaction.com/wp-content/uploads/cropped-favicon-200x200.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://asialiveaction.com/",
|
||||
"searchBaseUrl": "https://asialiveaction.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/asialiveaction/asialiveaction.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
async function searchContent(keyword, page=0) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetch(`https://atsu.moe/collections/manga/documents/search?q=${encodeURIComponent(keyword)}&limit=250&query_by=title%2CenglishTitle%2CotherNames&query_by_weights=3%2C2%2C1&include_fields=id%2Ctitle%2CenglishTitle%2Cposter%2CposterSmall%2CposterMedium%2Ctype%2CisAdult&num_typos=4%2C3%2C2&highlight_full_fields=title%2CenglishTitle%2CotherNames`);
|
||||
const data = await response.json();
|
||||
if (Array.isArray(data.hits)) {
|
||||
for (const hit of data.hits) {
|
||||
const doc = hit.document;
|
||||
results.push({
|
||||
id: doc.id,
|
||||
imageURL: "https://atsu.moe" + doc.poster,
|
||||
title: doc.title || doc.englishTitle || ""
|
||||
});
|
||||
}
|
||||
}
|
||||
return results;
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getContentData(ID) {
|
||||
try {
|
||||
const response = await fetch(`https://atsu.moe/manga/${ID}`);
|
||||
const html = await response.text();
|
||||
const scriptRegex = /window\.mangaPage\s*=\s*(\{[\s\S]*?\});/;
|
||||
const scriptMatch = scriptRegex.exec(html);
|
||||
let description = "";
|
||||
let tags = [];
|
||||
if (scriptMatch) {
|
||||
try {
|
||||
const mangaPageObj = JSON.parse(scriptMatch[1]);
|
||||
const page = mangaPageObj.mangaPage;
|
||||
description = page.synopsis || "";
|
||||
if (Array.isArray(page.tags)) {
|
||||
tags = page.tags.map(tag => tag.name);
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
return {
|
||||
description,
|
||||
tags
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
description: "Error",
|
||||
tags: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function getChapters(ID) {
|
||||
const results = [];
|
||||
try {
|
||||
const firstResponse = await fetch(`https://atsu.moe/api/manga/chapters?id=${ID}&filter=all&sort=desc&page=0`);
|
||||
const firstData = await firstResponse.json();
|
||||
const totalPages = firstData.pages || 1;
|
||||
let allChapters = firstData.chapters || [];
|
||||
|
||||
const fetchPromises = [];
|
||||
for (let p = 1; p < totalPages; p++) {
|
||||
fetchPromises.push(
|
||||
fetch(`https://atsu.moe/api/manga/chapters?id=${ID}&filter=all&sort=desc&page=${p}`).then(r => r.json())
|
||||
);
|
||||
}
|
||||
const pageResults = await Promise.all(fetchPromises);
|
||||
for (const pageData of pageResults) {
|
||||
if (Array.isArray(pageData.chapters)) {
|
||||
allChapters = allChapters.concat(pageData.chapters);
|
||||
}
|
||||
}
|
||||
|
||||
const total = allChapters.length;
|
||||
for (let i = 0; i < total; i++) {
|
||||
const chapter = allChapters[i];
|
||||
results.push([
|
||||
String(total - i - 1),
|
||||
[{
|
||||
id: "https://atsu.moe/api/read/chapter?mangaId=" + ID + "&chapterId=" + chapter.id,
|
||||
title: chapter.title,
|
||||
chapter: chapter.number,
|
||||
scanlation_group: chapter.title
|
||||
}]
|
||||
]);
|
||||
}
|
||||
return { en: results.reverse() };
|
||||
} catch (err) {
|
||||
return { en: [] };
|
||||
}
|
||||
}
|
||||
|
||||
async function getChapterImages(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
if (data.readChapter && Array.isArray(data.readChapter.pages)) {
|
||||
for (const page of data.readChapter.pages) {
|
||||
results.push("https://atsu.moe" + page.image);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"sourceName": "Atsu",
|
||||
"iconURL": "https://atsu.moe/favicon/apple-touch-icon-180x180.png",
|
||||
"version": "1.0",
|
||||
"language": "English",
|
||||
"scriptURL": "https://git.luna-app.eu/50n50/sources/raw/branch/main/atsu/atsu.js",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"iconURL": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
let pageNumber = 1;
|
||||
let hasMore = true;
|
||||
|
||||
try {
|
||||
while (hasMore) {
|
||||
const headers = {
|
||||
"next-action": await extractNextActionIdentifier(),
|
||||
"Content-Type": "text/plain"
|
||||
};
|
||||
console.log(JSON.stringify(headers));
|
||||
const postData = `[${pageNumber},"${keyword}",""]`;
|
||||
const response = await fetchv2("https://mapple.site/audiobooks?search=" + encodeURIComponent(keyword), headers, "POST", postData);
|
||||
const text = await response.text();
|
||||
console.log("Page " + pageNumber + " response:", text);
|
||||
|
||||
const lines = text.split('\n').filter(line => line.trim());
|
||||
let foundBooks = false;
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const data = JSON.parse(line.substring(line.indexOf('{')));
|
||||
|
||||
if (data.books && Array.isArray(data.books)) {
|
||||
foundBooks = true;
|
||||
for (const book of data.books) {
|
||||
let title = book.title || "Untitled";
|
||||
title = title.replace(/\s+free\s+online\s*$/i, '')
|
||||
.replace(/\s+audiobook\s+free\s+online\s*$/i, '')
|
||||
.replace(/\s+audiobook\s+free\s*$/i, '')
|
||||
.replace(/\s+free\s+audiobook\s*$/i, '')
|
||||
.replace(/\s+free\s*$/i, '')
|
||||
.replace(/\s+\(free\s*\)\s*$/i, '')
|
||||
.replace(/\s+free\s+read\s+/i, ' read ')
|
||||
.replace(/\s+\(.*?free.*?\)\s*$/i, '')
|
||||
.replace(/\s+audiobook\s*$/i, '')
|
||||
.trim();
|
||||
|
||||
results.push({
|
||||
title: title,
|
||||
image: book.image || "",
|
||||
href: "https://mapple.site/listen/" + (book.$id || "") + "/" + encodeURIComponent(book.title || "untitled")
|
||||
});
|
||||
}
|
||||
hasMore = data.hasMore === true;
|
||||
console.log("hasMore: " + hasMore);
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundBooks) {
|
||||
hasMore = false;
|
||||
}
|
||||
|
||||
pageNumber++;
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
console.log("Search error:", err);
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
return JSON.stringify([{
|
||||
description: "",
|
||||
aliases: "",
|
||||
airdate: ""
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const fetchResponse = await fetchv2(url, {
|
||||
"rsc": "1"
|
||||
});
|
||||
const fetchText = await fetchResponse.text();
|
||||
|
||||
const audioItemsMatch = fetchText.match(/"audio_items":\[([^\]]+)\]/);
|
||||
|
||||
if (audioItemsMatch && audioItemsMatch[1]) {
|
||||
const audioItemsStr = audioItemsMatch[1];
|
||||
const urlMatches = audioItemsStr.match(/"(https?:\/\/[^"]+)"/g);
|
||||
|
||||
if (urlMatches) {
|
||||
for (let i = 0; i < urlMatches.length; i++) {
|
||||
const audioUrl = urlMatches[i].replace(/"/g, '');
|
||||
results.push({
|
||||
href: audioUrl,
|
||||
number: i + 1,
|
||||
title: `Chapter ${i + 1}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
console.log("Episodes error:", err);
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error",
|
||||
title: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const headers = {
|
||||
"Host": "ipaudio.club",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:146.0) Gecko/20100101 Firefox/146.0",
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
"Connection": "keep-alive",
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Sec-Fetch-Mode": "no-cors",
|
||||
"Sec-Fetch-Site": "cross-site",
|
||||
"Priority": "u=4",
|
||||
"Pragma": "no-cache",
|
||||
"Cache-Control": "no-cache"
|
||||
};
|
||||
|
||||
const streams = [{
|
||||
title: `Audio Stream 1`,
|
||||
streamUrl: url,
|
||||
headers: headers
|
||||
}];
|
||||
|
||||
return JSON.stringify({
|
||||
streams: streams,
|
||||
subtitle: ""
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.log("Stream error:", err);
|
||||
return JSON.stringify({
|
||||
streams: [{
|
||||
title: "Error",
|
||||
streamUrl: "",
|
||||
headers: {}
|
||||
}],
|
||||
subtitle: ""
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null, encoding: 'utf-8' }) {
|
||||
try {
|
||||
return await fetchv2(
|
||||
url,
|
||||
options.headers ?? {},
|
||||
options.method ?? 'GET',
|
||||
options.body ?? null,
|
||||
true,
|
||||
options.encoding ?? 'utf-8'
|
||||
);
|
||||
} catch(e) {
|
||||
try {
|
||||
return await fetch(url, options);
|
||||
} catch(error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function extractNextActionIdentifier() {
|
||||
const htmlResponse = await soraFetch(atob("aHR0cHM6Ly9tYXBwbGUuc2l0ZS9hdWRpb2Jvb2tz"));
|
||||
const htmlText = await htmlResponse.text();
|
||||
|
||||
const pageMatch = htmlText.match(/<script[^>]*src="([^"]*app\/audiobooks\/page-[^"]*\.js)"[^>]*><\/script>/);
|
||||
|
||||
if (!pageMatch || !pageMatch[1]) {
|
||||
throw new Error("error 1");
|
||||
}
|
||||
|
||||
const beforePageMatch = htmlText.match(/<script[^>]*src="([^"]*\.js)"[^>]*><\/script><script[^>]*src="[^"]*app\/audiobooks\/page-[^"]*\.js"/);
|
||||
|
||||
if (!beforePageMatch || !beforePageMatch[1]) {
|
||||
throw new Error("error 2");
|
||||
}
|
||||
|
||||
let targetUrl = beforePageMatch[1];
|
||||
if (targetUrl.startsWith('/_next/')) {
|
||||
targetUrl = 'https://mapple.site' + targetUrl;
|
||||
} else if (!targetUrl.startsWith('http')) {
|
||||
targetUrl = 'https://mapple.site/' + targetUrl;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await soraFetch(targetUrl);
|
||||
const text = await response.text();
|
||||
|
||||
let actionMatch = text.match(/createServerReference\)\("([a-f0-9]{40,})"[^"]*"getStreamUrl/);
|
||||
if (!actionMatch) {
|
||||
actionMatch = text.match(/createServerReference\)\("([a-f0-9]{40,})"/);
|
||||
}
|
||||
if (!actionMatch) {
|
||||
actionMatch = text.match(/"([a-f0-9]{40,})"[^"]*"getStreamUrl/);
|
||||
}
|
||||
|
||||
if (actionMatch && actionMatch[1]) {
|
||||
return actionMatch[1];
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error("error 3: " + e);
|
||||
}
|
||||
|
||||
throw new Error("error 4");
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "Audible",
|
||||
"iconUrl": "https://m.media-amazon.com/images/I/51iKw5dFQoL.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "English",
|
||||
"streamType": "MP3",
|
||||
"quality": "Audio",
|
||||
"baseUrl": "https://www.audible.co.uk/",
|
||||
"searchBaseUrl": "https://www.audible.co.uk/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/audible/audible.js",
|
||||
"type": "audiobooks",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://www.az-animex.com/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const ulMatch = html.match(/<ul class="columns-4 wp-block-post-template[^"]*">([\s\S]*?)<\/ul>/);
|
||||
if (!ulMatch) return JSON.stringify(results);
|
||||
|
||||
const ulContent = ulMatch[1];
|
||||
|
||||
const liRegex = /<li class="wp-block-post[^"]*">([\s\S]*?)<\/li>/g;
|
||||
let liMatch;
|
||||
while ((liMatch = liRegex.exec(ulContent)) !== null) {
|
||||
const liHtml = liMatch[1];
|
||||
|
||||
const itemMatch = liHtml.match(
|
||||
/<a href="([^"]+)"[^>]*>\s*<img[^>]+src="([^"]+)"[^>]*>[\s\S]*?<h2[^>]*>\s*<a href="[^"]+"[^>]*>([^<]+)<\/a>/i
|
||||
);
|
||||
if (itemMatch) {
|
||||
results.push({
|
||||
href: itemMatch[1].trim(),
|
||||
image: itemMatch[2].trim(),
|
||||
title: itemMatch[3].replace(/\[.*?\]/g, '').trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const descMatch = html.match(/<div class="su-spoiler-content[^"]*">([\s\S]*?)<\/div>/i);
|
||||
const description = descMatch
|
||||
? descMatch[1].replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim()
|
||||
: "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const initialResponse = await fetchv2(url);
|
||||
const initialHtml = await initialResponse.text();
|
||||
|
||||
const filemoonButtonRegex = /<a\s+href="(https:\/\/filemoon\.(?:sx|to)\/[^"]+)"\s+class="su-button\s+su-button-style-glass"/i;
|
||||
const filemoonMatch = filemoonButtonRegex.exec(initialHtml);
|
||||
|
||||
if (!filemoonMatch) {
|
||||
throw new Error('Filemoon button not found');
|
||||
}
|
||||
|
||||
const filemoonUrl = filemoonMatch[1];
|
||||
|
||||
const filemoonResponse = await fetchv2(filemoonUrl);
|
||||
const filemoonHtml = await filemoonResponse.text();
|
||||
|
||||
const episodeRegex = /<li class="d-flex flex-wrap align-items-center justify-content-between mb-2">[\s\S]*?<h4>([^<]+)<\/h4>[\s\S]*?<a class="btn btn-success"[^>]*href="([^"]+)"[\s\S]*?<\/li>/g;
|
||||
|
||||
let match;
|
||||
let episodeCounter = 1;
|
||||
while ((match = episodeRegex.exec(filemoonHtml)) !== null) {
|
||||
let href = match[2].trim();
|
||||
|
||||
href = href.replace('/d/', '/e/');
|
||||
|
||||
results.push({
|
||||
href: href,
|
||||
number: episodeCounter
|
||||
});
|
||||
|
||||
episodeCounter++;
|
||||
}
|
||||
return JSON.stringify(results);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error extracting episodes:', err);
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
let streamUrl = null;
|
||||
try {
|
||||
streamUrl = await filemoonExtractor(html, url);
|
||||
} catch (error) {
|
||||
console.log("filemoon HD extraction error:" + error);
|
||||
}
|
||||
|
||||
console.log("filemoon Stream URL: " + streamUrl);
|
||||
if (streamUrl && streamUrl !== false && streamUrl !== null) {
|
||||
return streamUrl;
|
||||
}
|
||||
|
||||
console.log("No stream URL found");
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.log("Fetch error:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* SCHEME START */
|
||||
/* {REQUIRED PLUGINS: unbaser} */
|
||||
|
||||
/**
|
||||
* @name filemoonExtractor
|
||||
* @author Cufiy - Inspired by Churly
|
||||
*/
|
||||
|
||||
async function filemoonExtractor(html, url = null) {
|
||||
// check if contains iframe, if does, extract the src and get the url
|
||||
const regex = /<iframe[^>]+src="([^"]+)"[^>]*><\/iframe>/;
|
||||
const match = html.match(regex);
|
||||
if (match) {
|
||||
console.log("Iframe URL: " + match[1]);
|
||||
const iframeUrl = match[1];
|
||||
const iframeResponse = await soraFetch(iframeUrl, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Referer": url,
|
||||
}
|
||||
});
|
||||
console.log("Iframe Response: " + iframeResponse.status);
|
||||
html = await iframeResponse.text();
|
||||
}
|
||||
// console.log("HTML: " + html);
|
||||
// get /<script[^>]*>([\s\S]*?)<\/script>/gi
|
||||
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
||||
const scripts = [];
|
||||
let scriptMatch;
|
||||
while ((scriptMatch = scriptRegex.exec(html)) !== null) {
|
||||
scripts.push(scriptMatch[1]);
|
||||
}
|
||||
// get the script with eval and m3u8
|
||||
const evalRegex = /eval\((.*?)\)/;
|
||||
const m3u8Regex = /m3u8/;
|
||||
// console.log("Scripts: " + scripts);
|
||||
const evalScript = scripts.find(script => evalRegex.test(script) && m3u8Regex.test(script));
|
||||
if (!evalScript) {
|
||||
console.log("No eval script found");
|
||||
return null;
|
||||
}
|
||||
const unpackedScript = unpack(evalScript);
|
||||
// get the m3u8 url
|
||||
const m3u8Regex2 = /https?:\/\/[^\s]+master\.m3u8[^\s]*?(\?[^"]*)?/;
|
||||
const m3u8Match = unpackedScript.match(m3u8Regex2);
|
||||
if (m3u8Match) {
|
||||
return m3u8Match[0];
|
||||
} else {
|
||||
console.log("No M3U8 URL found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* REMOVE_START */
|
||||
|
||||
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function unpack(source) {
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses Sora's fetchv2 on ipad, fallbacks to regular fetch on Windows
|
||||
* @author ShadeOfChaos
|
||||
*
|
||||
* @param {string} url The URL to make the request to.
|
||||
* @param {object} [options] The options to use for the request.
|
||||
* @param {object} [options.headers] The headers to send with the request.
|
||||
* @param {string} [options.method='GET'] The method to use for the request.
|
||||
* @param {string} [options.body=null] The body of the request.
|
||||
*
|
||||
* @returns {Promise<Response|null>} The response from the server, or null if the
|
||||
* request failed.
|
||||
*/
|
||||
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
|
||||
try {
|
||||
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
|
||||
} catch(e) {
|
||||
try {
|
||||
return await fetch(url, options);
|
||||
} catch(error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* REMOVE_END */
|
||||
|
||||
/* SCHEME END */
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "AZ-Animex",
|
||||
"iconUrl": "https://www.az-animex.com/wp-content/uploads/2023/11/Logo-icono-144x144-1.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://www.az-animex.com/",
|
||||
"searchBaseUrl": "https://www.az-animex.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/az-animex/az-animex.js",
|
||||
"type": "Anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const baseUrl = "https://www.beatz-anime.net";
|
||||
try {
|
||||
const response = await fetchv2("https://www.beatz-anime.net/busqueda.php");
|
||||
const html = await response.text();
|
||||
|
||||
const cardRegex = /<div class="col-lg-2 mb-4">([\s\S]*?)<\/div>\s*<\/div>/gs;
|
||||
let cardMatch;
|
||||
while ((cardMatch = cardRegex.exec(html)) !== null) {
|
||||
const cardHTML = cardMatch[1];
|
||||
|
||||
const infoRegex = /<a href="([^"]+)">.*?<span class="titulo"[^>]*>([^<]+)<\/span>.*?<img[^>]+src="([^"]+)"/s;
|
||||
const infoMatch = infoRegex.exec(cardHTML);
|
||||
if (infoMatch) {
|
||||
const title = infoMatch[2].trim();
|
||||
if (title.toLowerCase().includes(keyword.toLowerCase())) {
|
||||
results.push({
|
||||
href: baseUrl + infoMatch[1].trim(),
|
||||
title: title,
|
||||
image: baseUrl + infoMatch[3].trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const pMatch = html.match(/<p class="post-text"[^>]*>([\s\S]*?)<\/p>/);
|
||||
let description = "N/A";
|
||||
let aliases = "N/A";
|
||||
|
||||
if (pMatch) {
|
||||
const content = pMatch[1];
|
||||
|
||||
const descMatch = content.split(/<span class="mr-3"/)[0].replace(/<br\s*\/?>/gi, "\n").trim();
|
||||
if (descMatch) description = descMatch;
|
||||
|
||||
const aliasMatch = content.match(/<b>Sinónimos:<\/b>\s*([^<]+)/);
|
||||
if (aliasMatch) aliases = aliasMatch[1].trim();
|
||||
}
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: aliases,
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const rows = html.match(/<tbody>([\s\S]*?)<\/tbody>/)?.[1].match(/<tr>[\s\S]*?<\/tr>/g) || [];
|
||||
|
||||
rows.forEach((row, index) => {
|
||||
const hrefMatch = row.match(/<td class="text-center"><a href="([^"]+)"/);
|
||||
const href = hrefMatch ? hrefMatch[1] : "N/A";
|
||||
|
||||
results.push({
|
||||
href: href,
|
||||
number: index + 1
|
||||
});
|
||||
});
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
return url;
|
||||
} catch (err) {
|
||||
return "https://error.org/";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"sourceName": "Beatz-Anime",
|
||||
"iconUrl": "https://files.catbox.moe/vij8dw.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://www.beatz-anime.net/",
|
||||
"searchBaseUrl": "https://www.beatz-anime.net/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/beatz-anime/beatz-anime.js",
|
||||
"type": "Anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"note": "Use external player!",
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "Tsumi (詰み) - Literally Everything 2.0",
|
||||
"iconUrl": "https://files.catbox.moe/krovkt.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.7",
|
||||
"language": "English",
|
||||
"streamType": "HLS",
|
||||
"quality": "4K",
|
||||
"baseUrl": "https://google.com/",
|
||||
"searchBaseUrl": "https://google.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/checkmate/checkmate.js",
|
||||
"type": "anime/movies/shows",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://www.cinecalidad.ec/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const containerMatch = html.match(/<div id="archive-content"[^>]*>([\s\S]*?)<\/div>\s*<\/div>\s*<\/div>/);
|
||||
if (containerMatch) {
|
||||
const container = containerMatch[1];
|
||||
|
||||
const regex = /<article[^>]*>([\s\S]*?)<\/article>/g;
|
||||
|
||||
let articleMatch;
|
||||
while ((articleMatch = regex.exec(container)) !== null) {
|
||||
const articleContent = articleMatch[1];
|
||||
|
||||
const imageMatch = articleContent.match(/data-src="([^"]+)"/);
|
||||
|
||||
const hrefMatch = articleContent.match(/<a href="([^"]+)"/);
|
||||
|
||||
const titleMatch = articleContent.match(/<div class="in_title">([^<]+)<\/div>/);
|
||||
|
||||
if (imageMatch && hrefMatch && titleMatch) {
|
||||
results.push({
|
||||
title: titleMatch[1].trim(),
|
||||
image: imageMatch[1].trim(),
|
||||
href: hrefMatch[1].trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const descMatch = html.match(/<p><p>([^<]+)<\/p>/);
|
||||
|
||||
const description = descMatch ? descMatch[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const seasonRegex = /<div id="jstab"[^>]*>([\s\S]*?)<\/div>\s*<\/div>/g;
|
||||
let seasonMatch;
|
||||
|
||||
while ((seasonMatch = seasonRegex.exec(html)) !== null) {
|
||||
const seasonContent = seasonMatch[1];
|
||||
|
||||
const episodeRegex = /<a href="([^"]+)"[^>]*>Episodio \d+<\/a>/g;
|
||||
let episodeMatch;
|
||||
let episodeCount = 1;
|
||||
|
||||
while ((episodeMatch = episodeRegex.exec(seasonContent)) !== null) {
|
||||
results.push({
|
||||
href: episodeMatch[1].trim(),
|
||||
number: episodeCount
|
||||
});
|
||||
episodeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
results.push({
|
||||
href: url,
|
||||
number: 1
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/data-option="(https:\/\/lamovie\.link\/embed-[^"]+)"/);
|
||||
if (!match) return "https://error.org/";
|
||||
|
||||
const embedUrl = match[1];
|
||||
const embedResponse = await fetchv2(embedUrl);
|
||||
const embedHtml = await embedResponse.text();
|
||||
|
||||
const obfuscatedScript = embedHtml.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
|
||||
if (!obfuscatedScript) return "https://error.org/";
|
||||
|
||||
const unpackedScript = unpack(obfuscatedScript[1]);
|
||||
|
||||
const fileMatch = unpackedScript.match(/file:\s*"([^"]+)"/);
|
||||
if (!fileMatch) return "https://error.org/";
|
||||
|
||||
return fileMatch[1];
|
||||
} catch (err) {
|
||||
return "https://error.org/";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************
|
||||
* UNPACKER MODULE
|
||||
* Credit to GitHub user "mnsrulz" for Unpacker Node library
|
||||
* https://github.com/mnsrulz/unpacker
|
||||
***********************************************************/
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
/* Functor for a given base. Will efficiently convert
|
||||
strings to natural numbers. */
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
// fill elements 37...61, if necessary
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
// If base can be handled by int() builtin, let it do it for us
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
// Build conversion dictionary cache
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
/* Decodes a value to an integer. */
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
function detect(source) {
|
||||
/* Detects whether `source` is P.A.C.K.E.R. coded. */
|
||||
return source.replace(" ", "").startsWith("eval(function(p,a,c,k,e,");
|
||||
}
|
||||
|
||||
function unpack(source) {
|
||||
/* Unpacks P.A.C.K.E.R. packed js code. */
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
/* Look up symbols in the synthetic symtab. */
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
//throw Error("symtab unknown");
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
/* Juice from a source file the four args needed by decoder. */
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
//const args = re.search(juicer, source, re.DOTALL);
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
//don't know what it is
|
||||
// a = list(a);
|
||||
// a[1] = 62;
|
||||
// a = tuple(a);
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
/* Strip string lookup table (list) and replace values in source. */
|
||||
/* Need to work on this. */
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "CineCalidad",
|
||||
"iconUrl": "https://www.cinecalidad.ec/wp-content/themes/Cinecalidad/assets/img/favicon.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://www.cinecalidad.ec/",
|
||||
"searchBaseUrl": "https://www.cinecalidad.ec/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/cinecalidad/cinecalidad.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const response = await fetchv2(`https://cksub.org/?s=${keyword}`);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<article class="bs"[^>]*>.*?<a href="([^"]+)"[^>]*>.*?<img src="([^"]+)"[^>]*>.*?<h2[^>]*>(.*?)<\/h2>/gs;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: match[3].trim(),
|
||||
image: match[2].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<div class="entry-content"[^>]*>([\s\S]*?)<\/div>/);
|
||||
|
||||
let description = "N/A";
|
||||
if (match) {
|
||||
description = match[1]
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&/g, "&")
|
||||
.trim();
|
||||
}
|
||||
|
||||
results.push({
|
||||
description: description,
|
||||
aliases: 'N/A',
|
||||
airdate: 'N/A'
|
||||
});
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)">\s*<div class="epl-num">([\d.]+)<\/div>/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
results.reverse();
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const iframeMatch = html.match(/dailymotion\.com\/player\/[a-zA-Z0-9]+\.html\?video=([a-zA-Z0-9]+)/); if (!iframeMatch) return "no iframe";
|
||||
|
||||
const videoId = iframeMatch[1];
|
||||
|
||||
const metaRes = await fetchv2(`https://www.dailymotion.com/player/metadata/video/${videoId}`);
|
||||
const metaJson = await metaRes.json();
|
||||
const hlsLink = metaJson.qualities?.auto?.[0]?.url;
|
||||
if (!hlsLink) return "no hls";
|
||||
|
||||
async function getBestHls(hlsUrl) {
|
||||
try {
|
||||
const res = await fetchv2(hlsUrl);
|
||||
const text = await res.text();
|
||||
const regex = /#EXT-X-STREAM-INF:.*RESOLUTION=(\d+)x(\d+).*?\n(https?:\/\/[^\n]+)/g;
|
||||
const streams = [];
|
||||
let match;
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
streams.push({ width: parseInt(match[1]), height: parseInt(match[2]), url: match[3] });
|
||||
}
|
||||
if (streams.length === 0) return hlsUrl;
|
||||
streams.sort((a, b) => b.height - a.height);
|
||||
return streams[0].url;
|
||||
} catch {
|
||||
return hlsUrl;
|
||||
}
|
||||
}
|
||||
|
||||
const bestHls = await getBestHls(hlsLink);
|
||||
|
||||
return bestHls;
|
||||
} catch {
|
||||
const empty = "{ streams: [";
|
||||
console.log("Extracted stream result:" + JSON.stringify(empty));
|
||||
return JSON.stringify(empty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "CKSub",
|
||||
"iconUrl": "https://cksub.org/wp-content/uploads/2024/12/cropped-i-am-the-fated-villain-gu-changge-192x192.jpg",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Chinese",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://cksub.org/",
|
||||
"searchBaseUrl": "https://cksub.org/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/cksub/cksub.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://cloudy.pk/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<div class="image-hover-wrapper">[\s\S]*?<a href="([^"]+)"[^>]*>[\s\S]*?<img [^>]*src="([^"]+)"[^>]*>[\s\S]*?<h2 class="entry-title"><a href="[^"]+"[^>]*>([^<]+)<\/a><\/h2>/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
image: match[2].trim(),
|
||||
title: match[3].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<p[^>]*><strong>([\s\S]*?)<\/strong><\/p>/);
|
||||
const description = match ? match[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<a [^>]*href="(https:\/\/videospk[^"]+)"[^>]*>.*?<\/a>/);
|
||||
if (match) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: 1
|
||||
});
|
||||
} else {
|
||||
results.push({
|
||||
href: url,
|
||||
number: 1
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const obfuscatedScript = html.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
|
||||
const unpackedScript = unpack(obfuscatedScript[1]);
|
||||
|
||||
const streamMatch = unpackedScript.match(/["'](\/stream\/[^"']+)["']/);
|
||||
const hlsLink = streamMatch ? streamMatch[1] : null;
|
||||
|
||||
console.log("HLS Link:" + hlsLink);
|
||||
|
||||
return "https://videospk.xyz" + hlsLink;
|
||||
} catch (err) {
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************
|
||||
* UNPACKER MODULE
|
||||
* Credit to GitHub user "mnsrulz" for Unpacker Node library
|
||||
* https://github.com/mnsrulz/unpacker
|
||||
***********************************************************/
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
/* Functor for a given base. Will efficiently convert
|
||||
strings to natural numbers. */
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
// fill elements 37...61, if necessary
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
// If base can be handled by int() builtin, let it do it for us
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
// Build conversion dictionary cache
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
/* Decodes a value to an integer. */
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
function detect(source) {
|
||||
/* Detects whether `source` is P.A.C.K.E.R. coded. */
|
||||
return source.replace(" ", "").startsWith("eval(function(p,a,c,k,e,");
|
||||
}
|
||||
|
||||
function unpack(source) {
|
||||
/* Unpacks P.A.C.K.E.R. packed js code. */
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
/* Look up symbols in the synthetic symtab. */
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
//throw Error("symtab unknown");
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
/* Juice from a source file the four args needed by decoder. */
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
//const args = re.search(juicer, source, re.DOTALL);
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
//don't know what it is
|
||||
// a = list(a);
|
||||
// a[1] = 62;
|
||||
// a = tuple(a);
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
/* Strip string lookup table (list) and replace values in source. */
|
||||
/* Need to work on this. */
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "Cloudy",
|
||||
"iconUrl": "https://cloudy.pk/wp-content/uploads/2018/02/cloudy.pk-icon-200x200.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Hindi (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://cloudy.pk/",
|
||||
"searchBaseUrl": "https://cloudy.pk/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/cloudy/cloudy.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
// Settings start
|
||||
const preferedQualityOption = "Auto"; // ["Auto", "2160p", "1080p", "720p", "480p"]
|
||||
const maxResultsPerResolution = 0;
|
||||
const maxSize = 0;
|
||||
const cachedOnly = false; // [true, false]
|
||||
const removeTrash = true; // [true, false]
|
||||
const resultFormat = "all"; // ["all"]
|
||||
const debridService = "realdebrid"; // ["realdebrid", "alldebrid", "premiumize", "torbox", "debridlink"]
|
||||
const debridApiKey = "";
|
||||
const debridStreamProxyPassword = "";
|
||||
const languagesExclude = "";
|
||||
const languagesPreferred = "en";
|
||||
const removeRanksUnder = -10000000000;
|
||||
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(
|
||||
"https://v3-cinemeta.strem.io/catalog/movie/top/search=" + encodeURIComponent(keyword) + ".json"
|
||||
);
|
||||
const moviesdata = await moviesresponse.json();
|
||||
|
||||
const results = moviesdata.metas.map(item => ({
|
||||
title: item.name.trim(),
|
||||
image: item.poster.trim(),
|
||||
href: "Movie: " + (item.id.startsWith("tt") ? item.id : "")
|
||||
}));
|
||||
|
||||
const showsresponse = await fetchv2(
|
||||
"https://v3-cinemeta.strem.io/catalog/series/top/search=" + encodeURIComponent(keyword) + ".json"
|
||||
);
|
||||
const showsdata = await showsresponse.json();
|
||||
|
||||
const showResults = showsdata.metas.map(item => ({
|
||||
title: item.name.trim(),
|
||||
image: item.poster.trim(),
|
||||
href: "TV: " + (item.id.startsWith("tt") ? item.id : "")
|
||||
}));
|
||||
|
||||
results.push(...showResults);
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(ID) {
|
||||
try {
|
||||
let decodedID = decodeURIComponent(ID);
|
||||
let actualID = decodedID;
|
||||
let type = "movie";
|
||||
|
||||
if (decodedID.startsWith("Movie: ")) {
|
||||
actualID = decodedID.replace("Movie: ", "");
|
||||
type = "movie";
|
||||
} else if (decodedID.startsWith("TV: ")) {
|
||||
actualID = decodedID.replace("TV: ", "");
|
||||
type = "series";
|
||||
}
|
||||
|
||||
const url = "https://v3-cinemeta.strem.io/meta/" + type + "/" + actualID + ".json";
|
||||
const response = await fetchv2(url);
|
||||
const data = await response.json();
|
||||
|
||||
return JSON.stringify([{
|
||||
description: data.meta.description || "N/A",
|
||||
aliases: "N/A",
|
||||
airdate: data.meta.released || "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(ID) {
|
||||
let decodedID = decodeURIComponent(ID);
|
||||
let actualID = decodedID;
|
||||
let type = "movie";
|
||||
|
||||
if (decodedID.startsWith("Movie: ")) {
|
||||
actualID = decodedID.replace("Movie: ", "");
|
||||
type = "movie";
|
||||
} else if (decodedID.startsWith("TV: ")) {
|
||||
actualID = decodedID.replace("TV: ", "");
|
||||
type = "series";
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
if (type === "series") {
|
||||
const response = await fetchv2("https://v3-cinemeta.strem.io/meta/series/" + actualID + ".json");
|
||||
const data = await response.json();
|
||||
|
||||
const videos = data.meta.videos || [];
|
||||
|
||||
const shouldAdjust = videos.length > 0 && videos[0].season === 0;
|
||||
|
||||
let currentSeason = 0;
|
||||
let episodeCounter = 0;
|
||||
|
||||
for (const video of videos) {
|
||||
const adjustedSeason = shouldAdjust ? video.season + 1 : video.season;
|
||||
if (adjustedSeason !== currentSeason) {
|
||||
currentSeason = adjustedSeason;
|
||||
episodeCounter = 0;
|
||||
}
|
||||
|
||||
episodeCounter++;
|
||||
|
||||
let adjustedId = video.id || "";
|
||||
if (adjustedId && shouldAdjust) {
|
||||
const idParts = adjustedId.split(':');
|
||||
if (idParts.length === 3) {
|
||||
idParts[1] = String(adjustedSeason);
|
||||
adjustedId = idParts.join(':');
|
||||
}
|
||||
}
|
||||
|
||||
results.push({
|
||||
href: "TV: " + adjustedId,
|
||||
number: episodeCounter
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} else if (type === "movie") {
|
||||
return JSON.stringify([{
|
||||
href: "Movie: " + (actualID || ""),
|
||||
number: 1
|
||||
}]);
|
||||
}
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(ID) {
|
||||
let decodedID = decodeURIComponent(ID);
|
||||
let actualID = decodedID;
|
||||
let type = "movie";
|
||||
|
||||
if (decodedID.startsWith("Movie: ")) {
|
||||
actualID = decodedID.replace("Movie: ", "");
|
||||
type = "movie";
|
||||
} else if (decodedID.startsWith("TV: ")) {
|
||||
actualID = decodedID.replace("TV: ", "");
|
||||
type = "series";
|
||||
}
|
||||
|
||||
const config = {
|
||||
maxResultsPerResolution: maxResultsPerResolution,
|
||||
maxSize: maxSize,
|
||||
cachedOnly: cachedOnly,
|
||||
removeTrash: removeTrash,
|
||||
resultFormat: [resultFormat],
|
||||
debridService: debridService,
|
||||
debridApiKey: debridApiKey,
|
||||
debridStreamProxyPassword: debridStreamProxyPassword,
|
||||
languages: {
|
||||
exclude: languagesExclude ? languagesExclude.split(",").map(s => s.trim()) : [],
|
||||
preferred: languagesPreferred ? languagesPreferred.split(",").map(s => s.trim()) : ["en"]
|
||||
},
|
||||
resolutions: {},
|
||||
options: {
|
||||
remove_ranks_under: removeRanksUnder,
|
||||
allow_english_in_languages: allowEnglishInLanguages,
|
||||
remove_unknown_languages: removeUnknownLanguages
|
||||
}
|
||||
};
|
||||
const encodedConfig = btoa(JSON.stringify(config));
|
||||
|
||||
try {
|
||||
const endpoint = type === "movie"
|
||||
? "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();
|
||||
|
||||
if (!data.streams || !Array.isArray(data.streams)) {
|
||||
return JSON.stringify({
|
||||
streams: [],
|
||||
subtitle: "https://none.com"
|
||||
});
|
||||
}
|
||||
|
||||
const streamsByQuality = {
|
||||
"2160p": [],
|
||||
"1080p": [],
|
||||
"720p": [],
|
||||
"480p": []
|
||||
};
|
||||
|
||||
for (const stream of data.streams) {
|
||||
const name = stream.name || "";
|
||||
let quality = null;
|
||||
|
||||
if (name.includes("2160p")) quality = "2160p";
|
||||
else if (name.includes("1080p")) quality = "1080p";
|
||||
else if (name.includes("720p")) quality = "720p";
|
||||
else if (name.includes("480p")) quality = "480p";
|
||||
|
||||
if (quality && streamsByQuality[quality]) {
|
||||
streamsByQuality[quality].push({
|
||||
title: stream.name || "Unknown",
|
||||
streamUrl: stream.url || "",
|
||||
headers: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let results = [];
|
||||
|
||||
if (preferedQualityOption === "Auto") {
|
||||
results.push(...streamsByQuality["2160p"].slice(0, 5));
|
||||
results.push(...streamsByQuality["1080p"].slice(0, 5));
|
||||
results.push(...streamsByQuality["720p"].slice(0, 5));
|
||||
results.push(...streamsByQuality["480p"].slice(0, 5));
|
||||
} else {
|
||||
if (streamsByQuality[preferedQualityOption]) {
|
||||
results = streamsByQuality[preferedQualityOption].slice(0, 10);
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
streams: results,
|
||||
subtitle: "https://none.com"
|
||||
});
|
||||
} catch (err) {
|
||||
return JSON.stringify({
|
||||
streams: [],
|
||||
subtitle: "https://none.com"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "Comet",
|
||||
"iconUrl": "https://i.imgur.com/jmVoVMu.jpeg",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.2",
|
||||
"language": "English",
|
||||
"streamType": "MKV",
|
||||
"quality": "4K",
|
||||
"baseUrl": "https://www.google.com/",
|
||||
"searchBaseUrl": "https://www.google.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/comet/comet.js",
|
||||
"type": "anime/movies/shows",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": true,
|
||||
"settings": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
async function searchResults(keyword) {
|
||||
const searchUrl = `https://ddys.pro/?s=${encodeURIComponent(keyword)}`;
|
||||
try {
|
||||
const response = await fetchv2(searchUrl);
|
||||
const html = await response.text();
|
||||
const results = [];
|
||||
|
||||
const articleRegex = /<article id="post-\d+"[^>]*>[\s\S]*?<h2 class="post-title"><a href="([^"]+)"[^>]*>([^<]+)<\/a><\/h2>/g;
|
||||
let match;
|
||||
|
||||
while ((match = articleRegex.exec(html)) !== null) {
|
||||
const href = match[1].trim();
|
||||
const title = match[2].trim();
|
||||
const imageUrl = "https://i.ibb.co/Y4b38sTG/Search-has-no-images.png";
|
||||
|
||||
results.push({
|
||||
title,
|
||||
image: imageUrl,
|
||||
href
|
||||
});
|
||||
}
|
||||
|
||||
console.log(results);
|
||||
return JSON.stringify(results);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const aliasMatch = html.match(/又名:\s*([^<]+)/);
|
||||
const descriptionMatch = html.match(/简介:\s*([\s\S]*?)<\/div>/);
|
||||
const airdateMatch = html.match(/年份:\s*(\d{4})/);
|
||||
|
||||
const alias = aliasMatch ? aliasMatch[1].trim() : "N/A";
|
||||
const description = descriptionMatch ? descriptionMatch[1].trim() : "No description available.";
|
||||
const airdate = airdateMatch ? airdateMatch[1].trim() : "N/A";
|
||||
|
||||
const details = [{
|
||||
alias,
|
||||
description,
|
||||
airdate
|
||||
}];
|
||||
|
||||
console.log(JSON.stringify(details));
|
||||
return JSON.stringify(details);
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
const episodes = [];
|
||||
|
||||
const scriptMatch = html.match(/<script class="wp-playlist-script" type="application\/json">(\{[\s\S]*?\})<\/script>/);
|
||||
|
||||
if (scriptMatch) {
|
||||
const jsonData = JSON.parse(scriptMatch[1]);
|
||||
|
||||
jsonData.tracks.forEach(track => {
|
||||
if (track.src0) {
|
||||
const episodeMatch = track.src0.match(/S01E(\d+)/) || track.caption.match(/\u7b2c(\d+)\u96c6/);
|
||||
const episodeNumber = episodeMatch ? parseInt(episodeMatch[1], 10) : null; // Convert to integer
|
||||
|
||||
episodes.push({
|
||||
href: `https://v.ddys.pro${track.src0.trim()}`,
|
||||
number: episodeNumber
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(episodes);
|
||||
return JSON.stringify(episodes);
|
||||
}
|
||||
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
return url;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"sourceName": "DDYS",
|
||||
"iconUrl": "https://ddys.pro/apple-touch-icon.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Chinese",
|
||||
"streamType": "mp4",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://ddys.pro/",
|
||||
"searchBaseUrl": "https://ddys.pro/?s=%s",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/ddys/ddys.js",
|
||||
"asyncJS": true,
|
||||
"type": "movies/shows",
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const response = await fetchv2(`https://desu-online.pl/?s=${keyword}`);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<article class="bs"[^>]*>.*?<a href="([^"]+)"[^>]*>.*?<img src="([^"]+)"[^>]*>.*?<h2[^>]*>(.*?)<\/h2>/gs;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: match[3].trim(),
|
||||
image: match[2].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<div class="entry-content"[^>]*>([\s\S]*?)<\/div>/);
|
||||
|
||||
let description = "N/A";
|
||||
if (match) {
|
||||
description = match[1]
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&/g, "&")
|
||||
.trim();
|
||||
}
|
||||
|
||||
results.push({
|
||||
description: description,
|
||||
aliases: 'N/A',
|
||||
airdate: 'N/A'
|
||||
});
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)">\s*<div class="epl-num">([\d.]+)<\/div>/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
results.reverse();
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
|
||||
async function getBestHls(hlsUrl) {
|
||||
try {
|
||||
const res = await fetchv2(hlsUrl);
|
||||
const text = await res.text();
|
||||
|
||||
const regex = /#EXT-X-STREAM-INF:.*RESOLUTION=(\d+)x(\d+).*?\n(https?:\/\/[^\n]+)/g;
|
||||
let match;
|
||||
const streams = [];
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
const width = parseInt(match[1]);
|
||||
const height = parseInt(match[2]);
|
||||
const url = match[3];
|
||||
streams.push({ width, height, url });
|
||||
}
|
||||
|
||||
if (streams.length === 0) return hlsUrl;
|
||||
streams.sort((a, b) => b.height - a.height);
|
||||
return streams[0].url;
|
||||
} catch (err) {
|
||||
return hlsUrl;
|
||||
}
|
||||
}
|
||||
|
||||
async function extractVideoIdFromIframely(iframelyUrl) {
|
||||
try {
|
||||
const iframeRes = await fetchv2(iframelyUrl);
|
||||
const iframeHtml = await iframeRes.text();
|
||||
const canonicalMatch = iframeHtml.match(/<meta name="canonical" content="https:\/\/www\.dailymotion\.com\/video\/([^"]+)"/);
|
||||
return canonicalMatch ? canonicalMatch[1] : null;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const optionRegex = /<option value="([^"]+)"[^>]*>\s*DailyMotion\s*<\/option>/g;
|
||||
const videoOptions = [];
|
||||
let match;
|
||||
|
||||
while ((match = optionRegex.exec(html)) !== null) {
|
||||
const base64Value = match[1];
|
||||
try {
|
||||
|
||||
const decodedHtml = atob(base64Value);
|
||||
|
||||
const srcMatch = decodedHtml.match(/<iframe src="([^"]+)"/);
|
||||
if (srcMatch) {
|
||||
const iframeUrl = srcMatch[1];
|
||||
const videoId = await extractVideoIdFromIframely("https:" + iframeUrl);
|
||||
if (videoId) {
|
||||
videoOptions.push({
|
||||
videoId: videoId,
|
||||
label: "Polish Hardsub"
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const streams = [];
|
||||
for (const option of videoOptions) {
|
||||
try {
|
||||
const metaRes = await fetchv2(`https://www.dailymotion.com/player/metadata/video/${option.videoId}`);
|
||||
const metaJson = await metaRes.json();
|
||||
const hlsLink = metaJson.qualities?.auto?.[0]?.url;
|
||||
if (!hlsLink) continue;
|
||||
|
||||
const bestHls = await getBestHls(hlsLink);
|
||||
streams.push(option.label);
|
||||
streams.push(bestHls);
|
||||
} catch (err) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
streams: streams,
|
||||
subtitles: ""
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "Desu-Online",
|
||||
"iconUrl": "https://desu-online.pl/wp-content/uploads/2021/03/53454540000-300x300.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Polish",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://desu-online.pl/",
|
||||
"searchBaseUrl": "https://desu-online.pl/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/desu-online/desu-online.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": false,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
function decodeHtmlEntities(text) {
|
||||
return text
|
||||
.replace(/’/g, "'")
|
||||
.replace(/“/g, '"')
|
||||
.replace(/”/g, '"')
|
||||
.replace(/…/g, '...')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&#(\d+);/g, (_, num) => String.fromCharCode(num));
|
||||
}
|
||||
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const regex = /<li class="border-radius-5 box-shadow">[\s\S]*?<img[^>]+src="([^"]+)"[^>]+title="([^"]+)"[^>]*>[\s\S]*?<a[^>]+href="([^"]+)"[^>]*>/gi;
|
||||
|
||||
try {
|
||||
const response = await fetchv2("https://www1.divxfilmeonline.net/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
regex.lastIndex = 0;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
image: match[1].trim(),
|
||||
title: decodeHtmlEntities(match[2].trim()),
|
||||
href: match[3].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
return JSON.stringify([{
|
||||
description: "Description not available",
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
async function extractEpisodes(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const filemoonMatch = /<li[^>]*class="server server-active"[^>]*data-vs="([^"]*fastvid\.co[^"]*)"[^>]*>Filemoon/i.exec(html);
|
||||
|
||||
if (filemoonMatch) {
|
||||
return JSON.stringify([{
|
||||
href: filemoonMatch[1].trim(),
|
||||
number: 1
|
||||
}]);
|
||||
} else {
|
||||
throw new Error("Filemoon episode not found");
|
||||
}
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error",
|
||||
message: err.message
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Referer": "https://www1.divxfilmeonline.net/"
|
||||
};
|
||||
const response = await fetchv2(url, headers);
|
||||
const html = await response.text();
|
||||
|
||||
const iframeMatch = /<iframe[^>]+src="([^"]+)"[^>]*><\/iframe>/i.exec(html);
|
||||
if (!iframeMatch) throw new Error("Iframe not found");
|
||||
const iframeUrl = iframeMatch[1].trim();
|
||||
|
||||
const iframeResponse = await fetchv2(iframeUrl, headers);
|
||||
const iframeContent = await iframeResponse.text();
|
||||
|
||||
console.log(`[Debug] Iframe content: ${iframeContent}`);
|
||||
|
||||
const scriptMatch = iframeContent.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
|
||||
|
||||
if (!scriptMatch) throw new Error("Obfuscated script not found");
|
||||
const evalContent = scriptMatch[1];
|
||||
|
||||
const unpackedScript = unpack(evalContent);
|
||||
|
||||
const fileMatch = /sources:\s*\[\s*\{\s*file:\s*"([^"]+\.m3u8[^"]*)"/.exec(unpackedScript);
|
||||
if (!fileMatch) throw new Error("Stream URL not found");
|
||||
|
||||
|
||||
const subtitleMatch = /tracks:\s*\[\s*\{\s*file:\s*"([^"]+\.vtt[^"]*)"/.exec(unpackedScript);
|
||||
|
||||
return JSON.stringify({
|
||||
stream: fileMatch[1],
|
||||
subtitles: subtitleMatch ? subtitleMatch[1] : null
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error extracting stream URL:", err);
|
||||
return JSON.stringify({
|
||||
stream: "https://files.catbox.moe/avolvc.mp4",
|
||||
subtitles: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* UNPACKER MODULE
|
||||
* Credit to GitHub user "mnsrulz" for Unpacker Node library
|
||||
* https://github.com/mnsrulz/unpacker
|
||||
*/
|
||||
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
/* Functor for a given base. Will efficiently convert
|
||||
strings to natural numbers. */
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
// fill elements 37...61, if necessary
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
// If base can be handled by int() builtin, let it do it for us
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
// Build conversion dictionary cache
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
/* Decodes a value to an integer. */
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
function detect(source) {
|
||||
/* Detects whether `source` is P.A.C.K.E.R. coded. */
|
||||
return source.replace(" ", "").startsWith("eval(function(p,a,c,k,e,");
|
||||
}
|
||||
|
||||
function unpack(source) {
|
||||
/* Unpacks P.A.C.K.E.R. packed js code. */
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
/* Look up symbols in the synthetic symtab. */
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
//throw Error("symtab unknown");
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
/* Juice from a source file the four args needed by decoder. */
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
//const args = re.search(juicer, source, re.DOTALL);
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
//don't know what it is
|
||||
// a = list(a);
|
||||
// a[1] = 62;
|
||||
// a = tuple(a);
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
/* Strip string lookup table (list) and replace values in source. */
|
||||
/* Need to work on this. */
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "DivXFilmeOnline",
|
||||
"iconUrl": "https://www1.divxfilmeonline.net/wp-content/uploads/2015/11/cropped-movies-40-180x180.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Romanian (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://divxfilmeonline.net/",
|
||||
"searchBaseUrl": "https://divxfilmeonline.net/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/divxfilmeonline/divxfilmeonline.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const workerUrl = "https://passthrough-worker.simplepostrequest.workers.dev/?url=" +
|
||||
encodeURIComponent("https://www.dmand5.com/index.php?m=vod-search") +
|
||||
"&type=multipart&body=" + encodeURIComponent(JSON.stringify({
|
||||
wd: keyword
|
||||
}));
|
||||
|
||||
const response = await fetchv2(workerUrl);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="(\/detail\/\d+\.html)" title="(.*?)" target="_blank"><img .*?src="(.*?)"/g;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: match[2].trim(),
|
||||
image: match[3].trim(),
|
||||
href: "https://www.dmand5.com" + match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Search results:" + JSON.stringify(results));
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const descMatch = html.match(/<div class="des">(.*?)<\/div>/s);
|
||||
const description = descMatch ? descMatch[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const blockMatch = html.match(/<ul class="mn_list_li_movie"[^>]*>([\s\S]*?)<\/ul>/);
|
||||
const blockHtml = blockMatch ? blockMatch[1] : "";
|
||||
|
||||
const regex = /<a href="(\/play\/\d+-\d+-(\d+)\.html)"[^>]*>第\d+集<\/a>/g;
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(blockHtml)) !== null) {
|
||||
results.push({
|
||||
href: parseInt(match[2], 10) + " https://www.dmand5.com" + match[1].trim(),
|
||||
number: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Episodes:" + JSON.stringify(results));
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const leadingNumberMatch = url.match(/^(\d+)\s+(.+)$/);
|
||||
const episodeNumber = leadingNumberMatch ? parseInt(leadingNumberMatch[1], 10) : 1;
|
||||
const cleanUrl = leadingNumberMatch ? leadingNumberMatch[2] : url;
|
||||
|
||||
const fetchUrl = cleanUrl;
|
||||
|
||||
console.log(fetchUrl);
|
||||
const response = await fetchv2(fetchUrl);
|
||||
const html = await response.text();
|
||||
|
||||
const macMatch = html.match(/mac_url\s*=\s*unescape\(\s*'([^']+)'\s*\)/s);
|
||||
if (!macMatch) return "https://files.catbox.moe/avolvc.mp4";
|
||||
|
||||
let raw = macMatch[1];
|
||||
|
||||
raw = raw.replace(/%u([\dA-F]{4})/gi, (_, g1) =>
|
||||
String.fromCharCode(parseInt(g1, 16))
|
||||
);
|
||||
|
||||
const decoded = decodeURIComponent(raw);
|
||||
|
||||
const episodes = decoded.split("#");
|
||||
|
||||
const epEntry = episodes[episodeNumber - 1] || episodes[0];
|
||||
const hlsUrl = epEntry.split("$")[1];
|
||||
|
||||
return hlsUrl || "https://files.catbox.moe/avolvc.mp4";
|
||||
} catch (err) {
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "Dmand5",
|
||||
"iconUrl": "https://www.dmand5.com/template/dmd8pc/images/logo.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Chinese",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://www.dmand5.com/",
|
||||
"searchBaseUrl": "https://www.dmand5.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/dmand5/dmand5.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(`https://donghuanosekai.com/wp-json/site/search/?keyword=${encodeURIComponent(keyword)}&type=undefined&nonce=4c4380bfaa`);
|
||||
const data = await response.json();
|
||||
|
||||
for (const key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
const item = data[key];
|
||||
results.push({
|
||||
title: item.title,
|
||||
image: item.img,
|
||||
href: item.url
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<div class="context">\s*<p>([\s\S]*?)<\/p>/i;
|
||||
const match = html.match(regex);
|
||||
const description = match ? match[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)"[^>]*>\s*.*?<span class="episode">Episódio\s+(\d+)<\/span>/g;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results.reverse());
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const divRegex = /<div class="slideItem"[^>]*data-video-url="([^"]+)"[^>]*>\s*Player 2\s*<\/div>/i;
|
||||
const divMatch = html.match(divRegex);
|
||||
if (!divMatch) return "https://error.org/";
|
||||
|
||||
const playerUrl = divMatch[1].trim();
|
||||
|
||||
const headers = {
|
||||
"Referer": url,
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
|
||||
};
|
||||
const playerResponse = await fetchv2(playerUrl, headers);
|
||||
const playerHtml = await playerResponse.text();
|
||||
|
||||
const iframeRegex = /<iframe[^>]+src="([^"]+)"/i;
|
||||
const iframeMatch = playerHtml.match(iframeRegex);
|
||||
|
||||
if (!iframeMatch) return "https://error.org/";
|
||||
|
||||
const iframeSrc = iframeMatch[1];
|
||||
|
||||
const m3u8Regex = /v=(https:\/\/[^&"]+\.m3u8[^&"]*)/i;
|
||||
const m3u8Match = iframeSrc.match(m3u8Regex);
|
||||
|
||||
return m3u8Match ? decodeURIComponent(m3u8Match[1]) : "https://error.org/";
|
||||
} catch {
|
||||
return "https://error.org/";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "Donghanosekai",
|
||||
"iconUrl": "https://files.catbox.moe/vgb4mq.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://donghuanosekai.com/",
|
||||
"searchBaseUrl": "https://donghuanosekai.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/donghuanosekai/donghuanosekai.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": true,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "DoraBash",
|
||||
"iconUrl": "https://dorabash.com/wp-content/uploads/2023/06/cropped-Untitled_design-removebg-192x192.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.3",
|
||||
"language": "Hindi",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://dorabash.com/",
|
||||
"searchBaseUrl": "https://dorabash.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/dorabash/dorabash.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const url = "https://www.doramasyt.com/buscar?q=" + encodeURIComponent(keyword);
|
||||
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
console.log(html);
|
||||
|
||||
const regex = /<li class="col mb-3 ficha_efecto">\s*<article>\s*<a href="([^"]+)"[\s\S]*?<img[^>]*data-src="([^"]+)"[^>]*>[\s\S]*?<h3[^>]*>(.*?)<\/h3>[\s\S]*?<\/a>\s*<\/article>\s*<\/li>/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: cleanTitle(match[3].trim().replace(/<[^>]*>/g, '')),
|
||||
image: match[2].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanTitle(title) {
|
||||
return title
|
||||
.replace(/’/g, "'")
|
||||
.replace(/–/g, "-")
|
||||
.replace(/&#[0-9]+;/g, "");
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const descMatch = html.match(/<h1[^>]*>([^<]+)<\/h1>/);
|
||||
const aliasMatch = html.match(/<span>([^<]+)<\/span>/);
|
||||
|
||||
const description = descMatch ? descMatch[1].trim() : "N/A";
|
||||
const aliases = aliasMatch ? aliasMatch[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: cleanTitle(description),
|
||||
aliases: cleanTitle(aliases),
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await networkFetch(url, {
|
||||
timeoutSeconds: 2,
|
||||
returnHTML: true
|
||||
});
|
||||
const html = await response.html;
|
||||
|
||||
const regex = /<a class="ko chapter-link" href="([^"]+)"[^>]*data-episode="(\d+)"/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const responseOne = await fetchv2(url);
|
||||
const htmlOne = await responseOne.text();
|
||||
const matchOne = htmlOne.match(/<button[^>]*data-player="([^"]+)"[^>]*>filemoon<\/button>/i);
|
||||
const filemoonData = matchOne ? matchOne[1] : null;
|
||||
|
||||
const response = await fetchv2("https://www.doramasyt.com/reproductor?video=" + encodeURIComponent(filemoonData));
|
||||
const html = await response.text();
|
||||
|
||||
const matchIframe = html.match(/<iframe[^>]*src="([^"]*filemoon\.sx\/e\/[^"]+)"/i);
|
||||
const filemoonUrl = matchIframe ? matchIframe[1] : null;
|
||||
console.log(filemoonUrl);
|
||||
|
||||
const responseTwo = await fetchv2(filemoonUrl);
|
||||
const htmlTwo = await responseTwo.text();
|
||||
|
||||
let streamUrl = null;
|
||||
try {
|
||||
streamUrl = await filemoonExtractor(htmlTwo, filemoonUrl);
|
||||
} catch (error) {
|
||||
console.log("filemoon HD extraction error:" + error);
|
||||
}
|
||||
|
||||
console.log("filemoon Stream URL: " + streamUrl);
|
||||
if (streamUrl && streamUrl !== false && streamUrl !== null) {
|
||||
return streamUrl;
|
||||
}
|
||||
|
||||
console.log("No stream URL found");
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.log("Fetch error:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* SCHEME START */
|
||||
/* {REQUIRED PLUGINS: unbaser} */
|
||||
|
||||
/**
|
||||
* @name filemoonExtractor
|
||||
* @author Cufiy - Inspired by Churly
|
||||
*/
|
||||
|
||||
async function filemoonExtractor(html, url = null) {
|
||||
// check if contains iframe, if does, extract the src and get the url
|
||||
const regex = /<iframe[^>]+src="([^"]+)"[^>]*><\/iframe>/;
|
||||
const match = html.match(regex);
|
||||
if (match) {
|
||||
console.log("Iframe URL: " + match[1]);
|
||||
const iframeUrl = match[1];
|
||||
const iframeResponse = await soraFetch(iframeUrl, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Referer": url,
|
||||
}
|
||||
});
|
||||
console.log("Iframe Response: " + iframeResponse.status);
|
||||
html = await iframeResponse.text();
|
||||
}
|
||||
// console.log("HTML: " + html);
|
||||
// get /<script[^>]*>([\s\S]*?)<\/script>/gi
|
||||
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
||||
const scripts = [];
|
||||
let scriptMatch;
|
||||
while ((scriptMatch = scriptRegex.exec(html)) !== null) {
|
||||
scripts.push(scriptMatch[1]);
|
||||
}
|
||||
// get the script with eval and m3u8
|
||||
const evalRegex = /eval\((.*?)\)/;
|
||||
const m3u8Regex = /m3u8/;
|
||||
// console.log("Scripts: " + scripts);
|
||||
const evalScript = scripts.find(script => evalRegex.test(script) && m3u8Regex.test(script));
|
||||
if (!evalScript) {
|
||||
console.log("No eval script found");
|
||||
return null;
|
||||
}
|
||||
const unpackedScript = unpack(evalScript);
|
||||
// get the m3u8 url
|
||||
const m3u8Regex2 = /https?:\/\/[^\s]+master\.m3u8[^\s]*?(\?[^"]*)?/;
|
||||
const m3u8Match = unpackedScript.match(m3u8Regex2);
|
||||
if (m3u8Match) {
|
||||
return m3u8Match[0];
|
||||
} else {
|
||||
console.log("No M3U8 URL found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* REMOVE_START */
|
||||
|
||||
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function unpack(source) {
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses Sora's fetchv2 on ipad, fallbacks to regular fetch on Windows
|
||||
* @author ShadeOfChaos
|
||||
*
|
||||
* @param {string} url The URL to make the request to.
|
||||
* @param {object} [options] The options to use for the request.
|
||||
* @param {object} [options.headers] The headers to send with the request.
|
||||
* @param {string} [options.method='GET'] The method to use for the request.
|
||||
* @param {string} [options.body=null] The body of the request.
|
||||
*
|
||||
* @returns {Promise<Response|null>} The response from the server, or null if the
|
||||
* request failed.
|
||||
*/
|
||||
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
|
||||
try {
|
||||
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
|
||||
} catch(e) {
|
||||
try {
|
||||
return await fetch(url, options);
|
||||
} catch(error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* REMOVE_END */
|
||||
|
||||
/* SCHEME END */
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "DoramasYT",
|
||||
"iconUrl": "https://files.catbox.moe/afzsms.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://www.doramasyt.com/",
|
||||
"searchBaseUrl": "https://www.doramasyt.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/doramasyt/doramasyt.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://egydead.com.co/search?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)"[^>]*style="background-image:\s*url\(([^)]+)\)[^"]*"[^>]*>[\s\S]*?<p class="title">(.*?)<\/p>/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
image: match[2].trim(),
|
||||
title: match[3].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<span class="description">\s*([\s\S]*?)\s*<\/span>/i;
|
||||
const match = regex.exec(html);
|
||||
|
||||
const description = match ? match[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)" class="btn watch">/i;
|
||||
const match = regex.exec(html);
|
||||
|
||||
if (match) {
|
||||
return JSON.stringify([{
|
||||
href: match[1].trim(),
|
||||
number: 1
|
||||
}]);
|
||||
}
|
||||
|
||||
return JSON.stringify([]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
console.log(html);
|
||||
|
||||
const jsonRegex = /let servers = JSON\.parse\('(\[.*?\])'\);/;
|
||||
const jsonMatch = jsonRegex.exec(html);
|
||||
|
||||
if (jsonMatch) {
|
||||
const serversJson = jsonMatch[1].replace(/\\/g, '');
|
||||
const servers = JSON.parse(serversJson);
|
||||
|
||||
const fdewsdcServer = servers.find(server => server.name === "EarnVids");
|
||||
if (fdewsdcServer) {
|
||||
console.log("Stream URL: " + fdewsdcServer.url);
|
||||
return await extractEarnVids(fdewsdcServer.url);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEarnVids(url) {
|
||||
const headers = {
|
||||
"Referer": "https://shahed4u.day/",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"
|
||||
};
|
||||
try {
|
||||
const response = await fetchv2(url, headers);
|
||||
const html = await response.text();
|
||||
|
||||
const obfuscatedScript = html.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
|
||||
const unpackedScript = unpack(obfuscatedScript[1]);
|
||||
|
||||
const streamMatch = unpackedScript.match(/["'](\/stream\/[^"']+)["']/);
|
||||
const hlsLink = streamMatch ? streamMatch[1] : null;
|
||||
|
||||
const baseUrl = url.match(/^(https?:\/\/[^/]+)/)[1];
|
||||
|
||||
console.log("HLS Link:" + baseUrl + hlsLink);
|
||||
|
||||
return baseUrl + hlsLink;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************
|
||||
* UNPACKER MODULE
|
||||
* Credit to GitHub user "mnsrulz" for Unpacker Node library
|
||||
* https://github.com/mnsrulz/unpacker
|
||||
***********************************************************/
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
/* Functor for a given base. Will efficiently convert
|
||||
strings to natural numbers. */
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
// fill elements 37...61, if necessary
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
// If base can be handled by int() builtin, let it do it for us
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
// Build conversion dictionary cache
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
/* Decodes a value to an integer. */
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
function detect(source) {
|
||||
/* Detects whether `source` is P.A.C.K.E.R. coded. */
|
||||
return source.replace(" ", "").startsWith("eval(function(p,a,c,k,e,");
|
||||
}
|
||||
|
||||
function unpack(source) {
|
||||
/* Unpacks P.A.C.K.E.R. packed js code. */
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
/* Look up symbols in the synthetic symtab. */
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
//throw Error("symtab unknown");
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
/* Juice from a source file the four args needed by decoder. */
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
//const args = re.search(juicer, source, re.DOTALL);
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
//don't know what it is
|
||||
// a = list(a);
|
||||
// a[1] = 62;
|
||||
// a = tuple(a);
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
/* Strip string lookup table (list) and replace values in source. */
|
||||
/* Need to work on this. */
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sourceName": "EgyDead (Fasel reuploader)",
|
||||
"iconUrl": "https://tv3.egydead.live/wp-content/uploads/2019/01/cropped-yXYdE2f-192x192.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"language": "Arabic",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://egydead.com.co/",
|
||||
"searchBaseUrl": "https://egydead.com.co/?s=%s",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/egydead%20(Fasel%20reuploader)/egydead.js",
|
||||
"type": "shows/movies/anime",
|
||||
"asyncJS": true,
|
||||
"downloadSupport": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://tv3.egydead.live/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<li class="movieItem">\s*<a href="([^"]+)" title="([^"]+)">\s*<img src="([^"]+)">/g;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
title: match[2].trim(),
|
||||
image: match[3].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<div class="extra-content">\s*<span>القصه<\/span>\s*<p>([\s\S]*?)<\/p>/);
|
||||
const description = match ? match[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)"[^>]*>\s*حلقه\s*(\d+)\s*<\/a>/g;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
results.push({
|
||||
href: url,
|
||||
number: 1
|
||||
});
|
||||
}
|
||||
return JSON.stringify(results.reverse());
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const header = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/",
|
||||
"Referer": url
|
||||
};
|
||||
const postData = "View=1";
|
||||
const response = await fetchv2(url, header, "POST", postData);
|
||||
const html = await response.text();
|
||||
|
||||
const earnvidsMatch = html.match(/<li[^>]*data-link=["']([^"']+)["'][^>]*>\s*<span>\s*<p>\s*EarnVids\s*<\/p>/i);
|
||||
const embedResponse = await fetchv2(earnvidsMatch[1], header);
|
||||
const embedHtml = await embedResponse.text();
|
||||
if (earnvidsMatch) {
|
||||
const stream = await earnvidsExtractor(embedHtml, url);
|
||||
return stream;
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.error("Error in extractStreamUrl:", err);
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* SCHEME START */
|
||||
/* {REQUIRED PLUGINS: unbaser} */
|
||||
|
||||
/**
|
||||
* @name earnvidsExtractor
|
||||
* @author 50/50
|
||||
*/
|
||||
async function earnvidsExtractor(html, url = null) {
|
||||
try {
|
||||
const obfuscatedScript = html.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
|
||||
const unpackedScript = unpack(obfuscatedScript[1]);
|
||||
|
||||
const streamMatch = unpackedScript.match(/["'](\/stream\/[^"']+)["']/);
|
||||
const hlsLink = streamMatch ? streamMatch[1] : null;
|
||||
|
||||
const baseUrl = url.match(/^(https?:\/\/[^/]+)/)[1];
|
||||
|
||||
console.log("HLS Link:" + baseUrl + hlsLink);
|
||||
|
||||
return baseUrl + hlsLink;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
}
|
||||
|
||||
/* REMOVE_START */
|
||||
|
||||
/***********************************************************
|
||||
* UNPACKER MODULE
|
||||
* Credit to GitHub user "mnsrulz" for Unpacker Node library
|
||||
* https://github.com/mnsrulz/unpacker
|
||||
***********************************************************/
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
/* Functor for a given base. Will efficiently convert
|
||||
strings to natural numbers. */
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
// fill elements 37...61, if necessary
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
// If base can be handled by int() builtin, let it do it for us
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
// Build conversion dictionary cache
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
/* Decodes a value to an integer. */
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
function detect(source) {
|
||||
/* Detects whether `source` is P.A.C.K.E.R. coded. */
|
||||
return source.replace(" ", "").startsWith("eval(function(p,a,c,k,e,");
|
||||
}
|
||||
|
||||
function unpack(source) {
|
||||
/* Unpacks P.A.C.K.E.R. packed js code. */
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
/* Look up symbols in the synthetic symtab. */
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
//throw Error("symtab unknown");
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
/* Juice from a source file the four args needed by decoder. */
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
//const args = re.search(juicer, source, re.DOTALL);
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
//don't know what it is
|
||||
// a = list(a);
|
||||
// a[1] = 62;
|
||||
// a = tuple(a);
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
/* Strip string lookup table (list) and replace values in source. */
|
||||
/* Need to work on this. */
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
/* REMOVE_END */
|
||||
|
||||
/* SCHEME END */
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sourceName": "EgyDead",
|
||||
"iconUrl": "https://tv3.egydead.live/wp-content/uploads/2019/01/cropped-yXYdE2f-192x192.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"language": "Arabic",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://tv3.egydead.live/",
|
||||
"searchBaseUrl": "https://tv3.egydead.live/?s=%s",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/egydead/egydead.js",
|
||||
"type": "shows/movies/anime",
|
||||
"asyncJS": true,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
function cleanTitle(title) {
|
||||
return title
|
||||
.replace(/’/g, "'")
|
||||
.replace(/–/g, "-")
|
||||
.replace(/&#[0-9]+;/g, "");
|
||||
}
|
||||
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const response = await fetchv2(`https://estrenosdoramas.es/?s=${keyword}`);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<article[^>]*class="[^"]*bs[^"]*"[^>]*>.*?<a href="([^"]+)"[^>]*>.*?<div class="ttzz">\s*(.*?)\s*<\/div>.*?<img[^>]*src="([^"]+)"[^>]*>/gs;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: cleanTitle(match[2].trim()),
|
||||
image: match[3].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
|
||||
async function extractDetails(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<div class="entry-content"[^>]*>([\s\S]*?)<\/div>/);
|
||||
|
||||
let description = "N/A";
|
||||
if (match) {
|
||||
description = match[1]
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&/g, "&")
|
||||
.trim();
|
||||
}
|
||||
|
||||
results.push({
|
||||
description: description,
|
||||
aliases: 'N/A',
|
||||
airdate: 'N/A'
|
||||
});
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<li[^>]*id="epcheck"[^>]*>[\s\S]*?<a href="([^"]+)">/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push(match[1].trim());
|
||||
}
|
||||
|
||||
const total = results.length;
|
||||
const final = results.map((href, index) => ({
|
||||
href: href,
|
||||
number: total - index
|
||||
}));
|
||||
|
||||
return JSON.stringify(final.reverse());
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const embedMatch = html.match(/<div class="pembed" data-embed="([^"]+)"/);
|
||||
if (!embedMatch) return "error";
|
||||
const embedUrl = embedMatch[1].trim();
|
||||
console.log(embedUrl);
|
||||
const headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Referer": url
|
||||
};
|
||||
|
||||
const embedResponse = await fetchv2(embedUrl.replace("//", "https://"), headers);
|
||||
const embedHtml = await embedResponse.text();
|
||||
console.log(embedHtml);
|
||||
const fileMatch = embedHtml.match(/sources:\s*\[\{file:"([^"]+)"/);
|
||||
if (!fileMatch) return "error";
|
||||
|
||||
return fileMatch[1];
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "EstrenosDoramas",
|
||||
"iconUrl": "https://i0.wp.com/estrenosdoramas.es/wp-content/uploads/2023/12/cropped-unnamed-7-192x192.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://vidmoly.to/",
|
||||
"searchBaseUrl": "https://vidmoly.to/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/estrenosdoramas/estrenosdoramas.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
function decodeHtmlEntities(text) {
|
||||
return text
|
||||
.replace(/’/g, "'")
|
||||
.replace(/“/g, '"')
|
||||
.replace(/”/g, '"')
|
||||
.replace(/…/g, '...')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&#(\d+);/g, (_, num) => String.fromCharCode(num));
|
||||
}
|
||||
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const regex = /<article[^>]*class="item (movies|seasons)"[^>]*>[\s\S]*?<img\s+src="([^"]+)"[^>]*title="([^"]+)"[^>]*>[\s\S]*?<a\s+href="([^"]+)"[^>]*>/gi;
|
||||
|
||||
try {
|
||||
const response = await fetchv2("https://filmehd.to/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
regex.lastIndex = 0;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
image: match[2].trim(),
|
||||
title: decodeHtmlEntities(match[3].trim()),
|
||||
href: match[4].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = /<div class="wp-content"[^>]*>\s*<p>(.*?)<\/p>/i.exec(html);
|
||||
const description = match ? match[1].trim() : "No description found";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const episodes = [];
|
||||
|
||||
const iframeMatch = html.match(/<iframe[^>]+src\s*=\s*"([^"]*movies\/iframe\/[^"]*?)"/i);
|
||||
if (iframeMatch) {
|
||||
episodes.push({
|
||||
href: iframeMatch[1].trim(),
|
||||
number: 1
|
||||
});
|
||||
} else {
|
||||
const allDataVs = [];
|
||||
const dataVsRegex = /data-vs\s*=\s*"([^"]*?)"/g;
|
||||
let match;
|
||||
while ((match = dataVsRegex.exec(html)) !== null) {
|
||||
allDataVs.push(match[1].trim());
|
||||
}
|
||||
|
||||
const allNumbers = [];
|
||||
const numberRegex = /<span[^>]*class="servers"[^>]*>(\d+)<\/span>/g;
|
||||
while ((match = numberRegex.exec(html)) !== null) {
|
||||
allNumbers.push(parseInt(match[1], 10));
|
||||
}
|
||||
|
||||
const server1Start = html.indexOf('SERVER 1');
|
||||
let server1End = html.indexOf('SERVER 2');
|
||||
if (server1End === -1) server1End = html.length;
|
||||
|
||||
if (server1Start !== -1) {
|
||||
const server1Section = html.substring(server1Start, server1End);
|
||||
const server1Count = (server1Section.match(/<span[^>]*class="servers"/g) || []).length;
|
||||
|
||||
for (let i = 0; i < server1Count && i < allDataVs.length && i < allNumbers.length; i++) {
|
||||
episodes.push({
|
||||
href: allDataVs[i],
|
||||
number: allNumbers[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
episodes.sort((a, b) => a.number - b.number);
|
||||
|
||||
if (episodes.length === 0) {
|
||||
throw new Error("No episodes found");
|
||||
}
|
||||
|
||||
return JSON.stringify(episodes);
|
||||
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error",
|
||||
message: err.message
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Referer": "https://filmehd.to/"
|
||||
};
|
||||
const response = await fetchv2(url, headers);
|
||||
const html = await response.text();
|
||||
|
||||
const iframeMatch = /<iframe[^>]+src="([^"]+)"[^>]*><\/iframe>/i.exec(html);
|
||||
if (!iframeMatch) throw new Error("Iframe not found");
|
||||
const iframeUrl = iframeMatch[1].trim();
|
||||
|
||||
const iframeResponse = await fetchv2(iframeUrl, headers);
|
||||
const iframeContent = await iframeResponse.text();
|
||||
|
||||
console.log(`[Debug] Iframe content: ${iframeContent}`);
|
||||
|
||||
const scriptMatch = iframeContent.match(/<script[^>]*>\s*(eval\(function\(p,a,c,k,e,d.*?\)[\s\S]*?)<\/script>/);
|
||||
|
||||
if (!scriptMatch) throw new Error("Obfuscated script not found");
|
||||
const evalContent = scriptMatch[1];
|
||||
|
||||
const unpackedScript = unpack(evalContent);
|
||||
|
||||
const fileMatch = /sources:\s*\[\s*\{\s*file:\s*"([^"]+\.m3u8[^"]*)"/.exec(unpackedScript);
|
||||
if (!fileMatch) throw new Error("Stream URL not found");
|
||||
|
||||
|
||||
const subtitleMatch = /tracks:\s*\[\s*\{\s*file:\s*"([^"]+\.vtt[^"]*)"/.exec(unpackedScript);
|
||||
|
||||
return JSON.stringify({
|
||||
stream: fileMatch[1],
|
||||
subtitles: subtitleMatch ? subtitleMatch[1] : null
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error extracting stream URL:", err);
|
||||
return JSON.stringify({
|
||||
stream: "https://files.catbox.moe/avolvc.mp4",
|
||||
subtitles: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* UNPACKER MODULE
|
||||
* Credit to GitHub user "mnsrulz" for Unpacker Node library
|
||||
* https://github.com/mnsrulz/unpacker
|
||||
*/
|
||||
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
/* Functor for a given base. Will efficiently convert
|
||||
strings to natural numbers. */
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
// fill elements 37...61, if necessary
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
// If base can be handled by int() builtin, let it do it for us
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
// Build conversion dictionary cache
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
/* Decodes a value to an integer. */
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
function detect(source) {
|
||||
/* Detects whether `source` is P.A.C.K.E.R. coded. */
|
||||
return source.replace(" ", "").startsWith("eval(function(p,a,c,k,e,");
|
||||
}
|
||||
|
||||
function unpack(source) {
|
||||
/* Unpacks P.A.C.K.E.R. packed js code. */
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
/* Look up symbols in the synthetic symtab. */
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
//throw Error("symtab unknown");
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
/* Juice from a source file the four args needed by decoder. */
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
//const args = re.search(juicer, source, re.DOTALL);
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
//don't know what it is
|
||||
// a = list(a);
|
||||
// a[1] = 62;
|
||||
// a = tuple(a);
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
/* Strip string lookup table (list) and replace values in source. */
|
||||
/* Need to work on this. */
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "FilmeHD",
|
||||
"iconUrl": "https://filmehd.to/wp-content/uploads/2020/01/favicon.ico",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "3.0.1",
|
||||
"language": "Romanian (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://filmehd.to/",
|
||||
"searchBaseUrl": "https://filmehd.to/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/filmehd/filmehd.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "FlixLatam",
|
||||
"iconUrl": "https://flixlatam.com/wp-content/uploads/2022/04/cropped-Series-Latinoamerica-192x192.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://flixlatam.com/",
|
||||
"searchBaseUrl": "https://flixlatam.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/flixlatam/flixlatam.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
async function searchResults(keyword) {
|
||||
try {
|
||||
const response = await fetchv2("https://api.franime.fr/api/animes");
|
||||
const data = await response.json();
|
||||
|
||||
const results = data
|
||||
.filter(anime => (anime.title?.toLowerCase().includes(keyword.toLowerCase())) ||
|
||||
(anime.titleO?.toLowerCase().includes(keyword.toLowerCase())))
|
||||
.map(anime => ({
|
||||
href: anime.id.toString(),
|
||||
title: (anime.title || anime.titleO),
|
||||
image: anime.affiche
|
||||
}));
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(id) {
|
||||
try {
|
||||
const response = await fetchv2("https://api.franime.fr/api/animes");
|
||||
const data = await response.json();
|
||||
|
||||
const anime = data.find(item => item.id === Number(id));
|
||||
if (!anime) {
|
||||
return JSON.stringify([{
|
||||
description: "Not found",
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
}
|
||||
|
||||
return JSON.stringify([{
|
||||
description: anime.description || "N/A",
|
||||
aliases: anime.titles ? Object.values(anime.titles).join(", ") : "N/A",
|
||||
airdate: anime.startDate || "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(id) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://api.franime.fr/api/animes");
|
||||
const data = await response.json();
|
||||
|
||||
const anime = data.find(item => item.id === Number(id));
|
||||
if (!anime || !anime.saisons) {
|
||||
return JSON.stringify([{
|
||||
href: "N/A",
|
||||
number: "N/A"
|
||||
}]);
|
||||
}
|
||||
|
||||
anime.saisons.forEach((season, seasonIndex) => {
|
||||
season.episodes.forEach((episode, episodeIndex) => {
|
||||
results.push({
|
||||
href: `${id}/${seasonIndex}/${episodeIndex}`,
|
||||
number: episodeIndex + 1
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(id) {
|
||||
try {
|
||||
const headers = {
|
||||
"Host": "api.franime.fr",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0",
|
||||
"Referer": "https://franime.fr/",
|
||||
"Content-Type": "application/json",
|
||||
"Origin": "https://franime.fr",
|
||||
"Connection": "keep-alive",
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Site": "same-site",
|
||||
"Priority": "u=0",
|
||||
"TE": "trailers"
|
||||
};
|
||||
|
||||
async function fetchUntilMp4(type) {
|
||||
let i = 0;
|
||||
while (i < 10) {
|
||||
const url = `https://api.franime.fr/api/anime/${id}/${type}/${i}`;
|
||||
const res = await fetchv2(url, headers);
|
||||
const text = await res.text();
|
||||
console.log(type.toUpperCase(), i, text);
|
||||
|
||||
if (text.includes("Aucun lecteur disponible")) return null;
|
||||
|
||||
if (text.endsWith(".mp4")) return text;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
const vf = await fetchUntilMp4("vf");
|
||||
const vo = await fetchUntilMp4("vo");
|
||||
|
||||
const streams = [];
|
||||
|
||||
if (vf) streams.push("VF", vf);
|
||||
if (vo) streams.push("VOSTFR", vo);
|
||||
|
||||
const final = {
|
||||
streams,
|
||||
subtitles: ""
|
||||
};
|
||||
|
||||
console.log("RETURN: " + JSON.stringify(final));
|
||||
return JSON.stringify(final);
|
||||
|
||||
} catch (error) {
|
||||
console.log("Error in extractStreamUrl:", error);
|
||||
return JSON.stringify({
|
||||
streams: [],
|
||||
subtitles: ""
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceName": "Franime",
|
||||
"iconUrl": "https://franime.fr/logos/apple-touch-icon.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.3",
|
||||
"language": "French (DUB/SUB)",
|
||||
"streamType": "mp4",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://franime.fr/",
|
||||
"searchBaseUrl": "https://franime.fr/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/franime/franime.js",
|
||||
"type": "anime",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": true,
|
||||
"supportsMojuru": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://funmovieslix.com/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)" class="poster-wrap">[\s\S]*?<img[^>]+src="([^"]+)"[^>]*>[\s\S]*?<h3 class="title">([^<]+)<\/h3>/g;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: match[3].trim(),
|
||||
image: match[2].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const descMatch = html.match(/<div class="entry-content entry-content-single"[^>]*>[\s\S]*?<p>([\s\S]*?)<\/p>/);
|
||||
const description = descMatch ? descMatch[1].trim() : "N/A";
|
||||
|
||||
const dateMatch = html.match(/<time itemprop="dateCreated"[^>]*>([^<]+)<\/time>/);
|
||||
const airdate = dateMatch ? dateMatch[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: airdate
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a class="button button-shadow" href="([^"]+)"[^>]*>S\d+\sEps?(\d+)<\/a>/g;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
results.push({
|
||||
href: url,
|
||||
number: 1
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<iframe src="(https:\/\/filemoon\.(?:to|sx)\/e\/[^"]+)"/);
|
||||
const filemoonUrl = match ? match[1] : "https://files.catbox.moe/avolvc.mp4";
|
||||
|
||||
console.log("Filemoon URL:"+ filemoonUrl);
|
||||
const filemoonResponse = await fetchv2(filemoonUrl);
|
||||
const filemoonHtml = await filemoonResponse.text();
|
||||
return filemoonExtractor(filemoonHtml, filemoonUrl) || filemoonUrl;
|
||||
} catch (err) {
|
||||
const fallback = "https://files.catbox.moe/avolvc.mp4";
|
||||
console.log("Filemoon URL:"+ fallback);
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* SCHEME START */
|
||||
/* {REQUIRED PLUGINS: unbaser} */
|
||||
|
||||
/**
|
||||
* @name filemoonExtractor
|
||||
* @author Cufiy - Inspired by Churly
|
||||
*/
|
||||
|
||||
async function filemoonExtractor(html, url = null) {
|
||||
// check if contains iframe, if does, extract the src and get the url
|
||||
const regex = /<iframe[^>]+src="([^"]+)"[^>]*><\/iframe>/;
|
||||
const match = html.match(regex);
|
||||
if (match) {
|
||||
console.log("Iframe URL: " + match[1]);
|
||||
const iframeUrl = match[1];
|
||||
const iframeResponse = await soraFetch(iframeUrl, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Referer": url,
|
||||
}
|
||||
});
|
||||
console.log("Iframe Response: " + iframeResponse.status);
|
||||
html = await iframeResponse.text();
|
||||
}
|
||||
// console.log("HTML: " + html);
|
||||
// get /<script[^>]*>([\s\S]*?)<\/script>/gi
|
||||
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
||||
const scripts = [];
|
||||
let scriptMatch;
|
||||
while ((scriptMatch = scriptRegex.exec(html)) !== null) {
|
||||
scripts.push(scriptMatch[1]);
|
||||
}
|
||||
// get the script with eval and m3u8
|
||||
const evalRegex = /eval\((.*?)\)/;
|
||||
const m3u8Regex = /m3u8/;
|
||||
// console.log("Scripts: " + scripts);
|
||||
const evalScript = scripts.find(script => evalRegex.test(script) && m3u8Regex.test(script));
|
||||
if (!evalScript) {
|
||||
console.log("No eval script found");
|
||||
return null;
|
||||
}
|
||||
const unpackedScript = unpack(evalScript);
|
||||
// get the m3u8 url
|
||||
const m3u8Regex2 = /https?:\/\/[^\s]+master\.m3u8[^\s]*?(\?[^"]*)?/;
|
||||
const m3u8Match = unpackedScript.match(m3u8Regex2);
|
||||
if (m3u8Match) {
|
||||
return m3u8Match[0];
|
||||
} else {
|
||||
console.log("No M3U8 URL found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* REMOVE_START */
|
||||
|
||||
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function unpack(source) {
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses Sora's fetchv2 on ipad, fallbacks to regular fetch on Windows
|
||||
* @author ShadeOfChaos
|
||||
*
|
||||
* @param {string} url The URL to make the request to.
|
||||
* @param {object} [options] The options to use for the request.
|
||||
* @param {object} [options.headers] The headers to send with the request.
|
||||
* @param {string} [options.method='GET'] The method to use for the request.
|
||||
* @param {string} [options.body=null] The body of the request.
|
||||
*
|
||||
* @returns {Promise<Response|null>} The response from the server, or null if the
|
||||
* request failed.
|
||||
*/
|
||||
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
|
||||
try {
|
||||
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
|
||||
} catch(e) {
|
||||
try {
|
||||
return await fetch(url, options);
|
||||
} catch(error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* REMOVE_END */
|
||||
|
||||
/* SCHEME END */
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "FunMoviesLix",
|
||||
"iconUrl": "https://funmovieslix.com/wp-content/uploads/2023/12/cropped-download.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Hindi",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://funmovieslix.com/",
|
||||
"searchBaseUrl": "https://funmovieslix.com/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/funmovieslix/funmovieslix.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://ww3.gnulahd.nu/?s=" + keyword);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)"[^>]*itemprop="url"[\s\S]*?<img src="(https:\/\/i[0-9]+\.wp\.com\/[^"]+)"[^>]*>[\s\S]*?<h2[^>]*itemprop="headline">([^<]+)<\/h2>/gi;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
image: match[2].trim(),
|
||||
title: match[3].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const match = html.match(/<div class="mindesc">\s*<p>([\s\S]*?)<\/p>\s*<\/div>/);
|
||||
const description = match ? match[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<li[^>]*>\s*<a href="([^"]+)">/g;
|
||||
let match;
|
||||
let count = 1;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: count
|
||||
});
|
||||
count++;
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
const dummyLink = "https://files.catbox.moe/avolvc.mp4";
|
||||
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
const iframeMatch = html.match(/<iframe[^>]+src="([^"]+embed\.php[^"]*)"[^>]*>/i);
|
||||
|
||||
if (!iframeMatch) {
|
||||
return dummyLink;
|
||||
}
|
||||
|
||||
const fjireo = await fetchv2(iframeMatch[1]);
|
||||
const kitri = await fjireo.text();
|
||||
|
||||
const videoArrayMatches = kitri.match(/var videos(\w+) = (\[.*?\]);/g);
|
||||
|
||||
if (!videoArrayMatches) {
|
||||
console.log("Could not find video arrays");
|
||||
return dummyLink;
|
||||
}
|
||||
|
||||
const providers = {};
|
||||
|
||||
function processVideoArray(videoArrayString, language) {
|
||||
try {
|
||||
const videoArray = JSON.parse(videoArrayString);
|
||||
|
||||
videoArray.forEach(([providerName, url]) => {
|
||||
const idMatch = url.match(/id=([A-Za-z0-9+/=]+)/);
|
||||
if (idMatch) {
|
||||
const base64Id = idMatch[1];
|
||||
const decodedUrl = atob(base64Id);
|
||||
|
||||
if (decodedUrl.includes('filemoon.to')) {
|
||||
let key = decodedUrl;
|
||||
let counter = 1;
|
||||
while (providers[key]) {
|
||||
key = `${decodedUrl}#${counter}`;
|
||||
counter++;
|
||||
}
|
||||
providers[key] = language;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error processing ${language} video array:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
videoArrayMatches.forEach(match => {
|
||||
const arrayMatch = match.match(/var videos(\w+) = (\[.*?\]);/);
|
||||
if (arrayMatch) {
|
||||
const language = arrayMatch[1].toLowerCase();
|
||||
const arrayString = arrayMatch[2];
|
||||
processVideoArray(arrayString, language);
|
||||
}
|
||||
});
|
||||
|
||||
const streams = [];
|
||||
|
||||
for (const [filemoonUrl, language] of Object.entries(providers)) {
|
||||
try {
|
||||
console.log(`Processing ${filemoonUrl} for language: ${language}`);
|
||||
|
||||
const filemoonResponse = await fetchv2(filemoonUrl);
|
||||
const filemoonHtml = await filemoonResponse.text();
|
||||
|
||||
const m3u8Url = await filemoonExtractor(filemoonHtml, filemoonUrl);
|
||||
|
||||
if (m3u8Url) {
|
||||
const cleanLanguage = language.replace(/#\d+$/, '').toUpperCase();
|
||||
streams.push(cleanLanguage, m3u8Url);
|
||||
console.log(`Successfully extracted: ${m3u8Url} for ${cleanLanguage}`);
|
||||
} else {
|
||||
console.log(`Failed to extract m3u8 URL from ${filemoonUrl}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing ${filemoonUrl}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const final = {
|
||||
streams,
|
||||
subtitles: ""
|
||||
};
|
||||
|
||||
console.log("RETURN: " + JSON.stringify(final));
|
||||
|
||||
if (streams.length > 0) {
|
||||
return JSON.stringify(final);
|
||||
} else {
|
||||
return JSON.stringify({
|
||||
streams: [],
|
||||
subtitles: ""
|
||||
});
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error("Error in extractStreamUrl:", err);
|
||||
return dummyLink;
|
||||
}
|
||||
}
|
||||
|
||||
/* SCHEME START */
|
||||
/* {REQUIRED PLUGINS: unbaser} */
|
||||
|
||||
/**
|
||||
* @name filemoonExtractor
|
||||
* @author Cufiy - Inspired by Churly
|
||||
*/
|
||||
|
||||
async function filemoonExtractor(html, url = null) {
|
||||
// check if contains iframe, if does, extract the src and get the url
|
||||
const regex = /<iframe[^>]+src="([^"]+)"[^>]*><\/iframe>/;
|
||||
const match = html.match(regex);
|
||||
if (match) {
|
||||
console.log("Iframe URL: " + match[1]);
|
||||
const iframeUrl = match[1];
|
||||
const iframeResponse = await soraFetch(iframeUrl, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Referer": url,
|
||||
}
|
||||
});
|
||||
console.log("Iframe Response: " + iframeResponse.status);
|
||||
html = await iframeResponse.text();
|
||||
}
|
||||
// console.log("HTML: " + html);
|
||||
// get /<script[^>]*>([\s\S]*?)<\/script>/gi
|
||||
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
||||
const scripts = [];
|
||||
let scriptMatch;
|
||||
while ((scriptMatch = scriptRegex.exec(html)) !== null) {
|
||||
scripts.push(scriptMatch[1]);
|
||||
}
|
||||
// get the script with eval and m3u8
|
||||
const evalRegex = /eval\((.*?)\)/;
|
||||
const m3u8Regex = /m3u8/;
|
||||
// console.log("Scripts: " + scripts);
|
||||
const evalScript = scripts.find(script => evalRegex.test(script) && m3u8Regex.test(script));
|
||||
if (!evalScript) {
|
||||
console.log("No eval script found");
|
||||
return null;
|
||||
}
|
||||
const unpackedScript = unpack(evalScript);
|
||||
// get the m3u8 url
|
||||
const m3u8Regex2 = /https?:\/\/[^\s]+master\.m3u8[^\s]*?(\?[^"]*)?/;
|
||||
const m3u8Match = unpackedScript.match(m3u8Regex2);
|
||||
if (m3u8Match) {
|
||||
return m3u8Match[0];
|
||||
} else {
|
||||
console.log("No M3U8 URL found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* REMOVE_START */
|
||||
|
||||
|
||||
class Unbaser {
|
||||
constructor(base) {
|
||||
this.ALPHABET = {
|
||||
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
95: "' !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'",
|
||||
};
|
||||
this.dictionary = {};
|
||||
this.base = base;
|
||||
if (36 < base && base < 62) {
|
||||
this.ALPHABET[base] = this.ALPHABET[base] ||
|
||||
this.ALPHABET[62].substr(0, base);
|
||||
}
|
||||
if (2 <= base && base <= 36) {
|
||||
this.unbase = (value) => parseInt(value, base);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
[...this.ALPHABET[base]].forEach((cipher, index) => {
|
||||
this.dictionary[cipher] = index;
|
||||
});
|
||||
}
|
||||
catch (er) {
|
||||
throw Error("Unsupported base encoding.");
|
||||
}
|
||||
this.unbase = this._dictunbaser;
|
||||
}
|
||||
}
|
||||
_dictunbaser(value) {
|
||||
let ret = 0;
|
||||
[...value].reverse().forEach((cipher, index) => {
|
||||
ret = ret + ((Math.pow(this.base, index)) * this.dictionary[cipher]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function unpack(source) {
|
||||
let { payload, symtab, radix, count } = _filterargs(source);
|
||||
if (count != symtab.length) {
|
||||
throw Error("Malformed p.a.c.k.e.r. symtab.");
|
||||
}
|
||||
let unbase;
|
||||
try {
|
||||
unbase = new Unbaser(radix);
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unknown p.a.c.k.e.r. encoding.");
|
||||
}
|
||||
function lookup(match) {
|
||||
const word = match;
|
||||
let word2;
|
||||
if (radix == 1) {
|
||||
word2 = symtab[parseInt(word)];
|
||||
}
|
||||
else {
|
||||
word2 = symtab[unbase.unbase(word)];
|
||||
}
|
||||
return word2 || word;
|
||||
}
|
||||
source = payload.replace(/\b\w+\b/g, lookup);
|
||||
return _replacestrings(source);
|
||||
function _filterargs(source) {
|
||||
const juicers = [
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)/,
|
||||
/}\('(.*)', *(\d+|\[\]), *(\d+), *'(.*)'\.split\('\|'\)/,
|
||||
];
|
||||
for (const juicer of juicers) {
|
||||
const args = juicer.exec(source);
|
||||
if (args) {
|
||||
let a = args;
|
||||
if (a[2] == "[]") {
|
||||
}
|
||||
try {
|
||||
return {
|
||||
payload: a[1],
|
||||
symtab: a[4].split("|"),
|
||||
radix: parseInt(a[2]),
|
||||
count: parseInt(a[3]),
|
||||
};
|
||||
}
|
||||
catch (ValueError) {
|
||||
throw Error("Corrupted p.a.c.k.e.r. data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error("Could not make sense of p.a.c.k.e.r data (unexpected code structure)");
|
||||
}
|
||||
function _replacestrings(source) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses Sora's fetchv2 on ipad, fallbacks to regular fetch on Windows
|
||||
* @author ShadeOfChaos
|
||||
*
|
||||
* @param {string} url The URL to make the request to.
|
||||
* @param {object} [options] The options to use for the request.
|
||||
* @param {object} [options.headers] The headers to send with the request.
|
||||
* @param {string} [options.method='GET'] The method to use for the request.
|
||||
* @param {string} [options.body=null] The body of the request.
|
||||
*
|
||||
* @returns {Promise<Response|null>} The response from the server, or null if the
|
||||
* request failed.
|
||||
*/
|
||||
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null }) {
|
||||
try {
|
||||
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
|
||||
} catch(e) {
|
||||
try {
|
||||
return await fetch(url, options);
|
||||
} catch(error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* REMOVE_END */
|
||||
|
||||
/* SCHEME END */
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "GnulaHD",
|
||||
"iconUrl": "https://i3.wp.com/ww3.gnulahd.nu/wp-content/uploads/2025/07/cropped-faviconhd3-192x192.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Spanish (DUB/SUB)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://ww3.gnulahd.nu/",
|
||||
"searchBaseUrl": "https://ww3.gnulahd.nu/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/gnulahd/gnulahd.js",
|
||||
"type": "anime/shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://hdhub4u.navy/?s=" + keyword);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<li class="thumb[^>]*>[\s\S]*?<img src="([^"]+)"[^>]*alt="([^"]+)"[\s\S]*?<a href="([^"]+)"/g;
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
const title = cleanTitle(match[2].trim());
|
||||
if (!/episode/i.test(title)) {
|
||||
results.push({
|
||||
title: title,
|
||||
image: match[1].trim(),
|
||||
href: match[3].trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regexTitle = /<h1 class="page-title">[\s\S]*?<span class="material-text">([\s\S]*?)<\/span>/i;
|
||||
const matchTitle = regexTitle.exec(html);
|
||||
const titleText = matchTitle ? matchTitle[1].replace(/<[^>]+>/g, "").trim() : "";
|
||||
|
||||
let description;
|
||||
if (/episodes/i.test(titleText)) {
|
||||
description = "SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED - SHOWS NOT SUPPORTED ";
|
||||
} else {
|
||||
const regexDesc = /<div class="kno-rdesc">([\s\S]*?)<\/div>/i;
|
||||
const matchDesc = regexDesc.exec(html);
|
||||
description = matchDesc ? matchDesc[1].replace(/<[^>]+>/g, "").trim() : "N/A";
|
||||
}
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
async function extractEpisodes(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a\s+[^>]*href="([^"]+)"[^>]*>([^<]*(?:\d+p|\d+\.\d*(?:GB|MB)|480p|720p|1080p|4K|2160p)[^<]*)<\/a>/gi;
|
||||
|
||||
let matches = [];
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
const href = match[1].trim();
|
||||
const text = match[2].trim();
|
||||
|
||||
if (!href.includes('join-our-group') &&
|
||||
!href.includes('telegram') &&
|
||||
!href.includes('whatsapp') &&
|
||||
!text.toLowerCase().includes('join') &&
|
||||
!text.toLowerCase().includes('group')) {
|
||||
matches.push({
|
||||
href: href,
|
||||
text: text
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const lastMatch = matches.length > 0 ? matches[matches.length - 1] : null;
|
||||
|
||||
return JSON.stringify([{
|
||||
href: lastMatch ? lastMatch.href : "N/A",
|
||||
number: 1,
|
||||
text: lastMatch ? lastMatch.text : "N/A"
|
||||
}]);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error extracting episodes:', err);
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error",
|
||||
text: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<title>HubDrive \| (.*?)<\/title>/i;
|
||||
const match = regex.exec(html);
|
||||
const filename = match ? match[1].trim() : "default.mp4";
|
||||
|
||||
const baseUrls = ["https://fsl.fastcloud.lol/", "https://fsl.fastcloud.buzz/"];
|
||||
let fullUrl = null;
|
||||
|
||||
for (const base of baseUrls) {
|
||||
const testUrl = base + filename;
|
||||
try {
|
||||
const passthroughUrl = "https://passthrough-worker.simplepostrequest.workers.dev/?head=" + encodeURIComponent(testUrl);
|
||||
const resText = await fetchv2(passthroughUrl).then(r => r.text());
|
||||
if (!/Status:404/i.test(resText)) {
|
||||
fullUrl = testUrl;
|
||||
break;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (!fullUrl) fullUrl = "https://files.catbox.moe/avolvc.mp4";
|
||||
|
||||
console.log("Full URL:"+ fullUrl);
|
||||
return fullUrl;
|
||||
} catch (err) {
|
||||
console.log("Full URL:"+ "https://files.catbox.moe/avolvc.mp4");
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
}
|
||||
|
||||
function cleanTitle(title) {
|
||||
return title
|
||||
.replace(/’|’/g, "'")
|
||||
.replace(/‘|‘/g, "'")
|
||||
.replace(/“|“/g, '"')
|
||||
.replace(/”|”/g, '"')
|
||||
.replace(/–|–/g, "-")
|
||||
.replace(/—|—/g, "--")
|
||||
.replace(/…|…/g, "...")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/ /g, " ")
|
||||
.replace(/&#[0-9]+;/g, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "hdhub4u",
|
||||
"iconUrl": "https://hdhub4u.navy/wp-content/uploads/2021/05/cropped-cropped-1-1-1-2-1-180x180.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Hindi",
|
||||
"streamType": "MKV",
|
||||
"quality": "4K - 1080p",
|
||||
"baseUrl": "https://hdhub4u.co/",
|
||||
"searchBaseUrl": "https://hdhub4u.co/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/hdhub4u/hdhub4u.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"softsub": false,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
async function searchResults(keyword) {
|
||||
try {
|
||||
const encodedKeyword = encodeURIComponent(keyword);
|
||||
const url = `https://helioscans.com/series/?q=${encodedKeyword}`;
|
||||
const response = await soraFetch(url);
|
||||
const html = await response.text();
|
||||
|
||||
const results = [];
|
||||
const regex = /<button[^>]+?title="([^"]+?)"[^>]*?>[\s\S]*?<a href="([^"]+?)"[\s\S]*?background-image:url\(([^)]+)\)/g;
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
const title = match[1];
|
||||
const href = `https://helioscans.com${match[2]}`;
|
||||
const rawImage = match[3].replace(/&/g, "&");
|
||||
const image = rawImage.startsWith("http") ? rawImage : `https:${rawImage}`;
|
||||
|
||||
results.push({ title, href, image });
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(results));
|
||||
return JSON.stringify(results);
|
||||
} catch (error) {
|
||||
console.error("Error fetching or parsing: " + error);
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
href: "",
|
||||
image: ""
|
||||
}]);
|
||||
}
|
||||
}
|
||||
extractChapters('https://helioscans.com/series/63a6054296b/');
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await soraFetch(url);
|
||||
const htmlText = await response.text();
|
||||
|
||||
const metaMatch = htmlText.match(/<meta name="description" content="([\s\S]*?)">/i);
|
||||
const description = metaMatch
|
||||
? metaMatch[1].replace(/\s+/g, ' ').trim()
|
||||
: "No description available";
|
||||
|
||||
const aliases = 'N/A';
|
||||
const airdate = 'N/A';
|
||||
|
||||
const transformedResults = [{
|
||||
description,
|
||||
aliases,
|
||||
airdate
|
||||
}];
|
||||
|
||||
console.log(JSON.stringify(transformedResults));
|
||||
return JSON.stringify(transformedResults);
|
||||
|
||||
} catch (error) {
|
||||
console.log('Details error:' + error);
|
||||
return JSON.stringify([{
|
||||
description: 'Error loading description',
|
||||
aliases: 'N/A',
|
||||
airdate: 'N/A'
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractChapters(url) {
|
||||
try {
|
||||
const response = await soraFetch(url);
|
||||
const htmlText = await response.text();
|
||||
console.log(htmlText);
|
||||
|
||||
const chapters = [];
|
||||
const chapterLinkRegex = /<a\s+[^>]*href="([^"]*\/chapter\/[^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
|
||||
let linkMatch;
|
||||
|
||||
while ((linkMatch = chapterLinkRegex.exec(htmlText)) !== null) {
|
||||
const fullLinkHtml = linkMatch[0];
|
||||
const href = `https://helioscans.com${linkMatch[1]}`;
|
||||
|
||||
const titleRegex = /<span[^>]*class="[^"]*\btext-sm\b[^"]*\btruncate\b[^"]*"[^>]*>([^<]+)<\/span>/i;
|
||||
const titleMatch = titleRegex.exec(fullLinkHtml);
|
||||
const rawTitle = titleMatch ? titleMatch[1].trim() : "";
|
||||
|
||||
if (!rawTitle) continue;
|
||||
|
||||
const isLocked = /Coin\.svg/i.test(fullLinkHtml);
|
||||
const title = isLocked ? `${rawTitle} (Locked – 100 credits)` : rawTitle;
|
||||
|
||||
chapters.push({ title, href });
|
||||
}
|
||||
|
||||
chapters.sort((a, b) => {
|
||||
const numA = parseFloat(a.title.match(/Chapter\s+(\d+)/i)?.[1]) || 0;
|
||||
const numB = parseFloat(b.title.match(/Chapter\s+(\d+)/i)?.[1]) || 0;
|
||||
return numA - numB;
|
||||
});
|
||||
|
||||
chapters.forEach((chapter, index) => {
|
||||
chapter.number = index + 1;
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(chapters));
|
||||
return JSON.stringify(chapters);
|
||||
} catch (error) {
|
||||
console.error('Fetch error in extractChapters:', error);
|
||||
return JSON.stringify([{
|
||||
href: url,
|
||||
title: "Error fetching chapters",
|
||||
number: 0
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractText(url) {
|
||||
try {
|
||||
const headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'Connection': 'keep-alive',
|
||||
'Upgrade-Insecure-Requests': '1'
|
||||
};
|
||||
|
||||
const response = await soraFetch(url, headers);
|
||||
const htmlText = await response.text();
|
||||
|
||||
const startMarker = '<div id="pages"';
|
||||
const startIndex = htmlText.indexOf(startMarker);
|
||||
if (startIndex === -1) {
|
||||
throw new Error("Pages content div start (<div id=\"pages\") not found");
|
||||
}
|
||||
|
||||
const startTagEndIndex = htmlText.indexOf('>', startIndex);
|
||||
if (startTagEndIndex === -1) {
|
||||
throw new Error("Could not find the end of the opening <div id=\"pages\"> tag");
|
||||
}
|
||||
|
||||
const contentStartIndex = startTagEndIndex + 1;
|
||||
let depth = 1;
|
||||
let pos = contentStartIndex;
|
||||
let endIndex = -1;
|
||||
|
||||
while (depth > 0 && pos < htmlText.length) {
|
||||
const nextOpenDiv = htmlText.indexOf('<div', pos);
|
||||
const nextCloseDiv = htmlText.indexOf('</div', pos);
|
||||
|
||||
if (nextCloseDiv === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (nextOpenDiv !== -1 && nextOpenDiv < nextCloseDiv) {
|
||||
depth++;
|
||||
pos = nextOpenDiv + 4;
|
||||
} else {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
endIndex = nextCloseDiv;
|
||||
} else {
|
||||
pos = nextCloseDiv + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (endIndex === -1) {
|
||||
throw new Error("Matching closing </div> for pages content div not found");
|
||||
}
|
||||
|
||||
let innerContent = htmlText.substring(contentStartIndex, endIndex);
|
||||
|
||||
innerContent = innerContent.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
||||
innerContent = innerContent.replace(/<div[^>]*class="[^"]*fixed[^"]*z-\[60\][^"]*top-0[^"]*left-0[^"]*w-full[^"]*h-full[^"]*bg-black\/90[^"]*flex[^"]*justify-center[^"]*items-center[\s\S]*?<\/div>/gi, '');
|
||||
|
||||
const paragraphRegex = /<p[^>]*>(.*?)<\/p>/gi;
|
||||
let textContent = '';
|
||||
let match;
|
||||
|
||||
while ((match = paragraphRegex.exec(innerContent)) !== null) {
|
||||
const paragraphText = match[1].replace(/<[^>]*>/g, '').trim();
|
||||
if (paragraphText) {
|
||||
textContent += paragraphText + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
innerContent = innerContent.trim();
|
||||
|
||||
if (!innerContent && !textContent) {
|
||||
throw new Error("Chapter text not found or empty after cleaning");
|
||||
}
|
||||
console.log(innerContent || textContent);
|
||||
return innerContent;
|
||||
|
||||
} catch (error) {
|
||||
console.error("Fetch error in extractText: " + error.message);
|
||||
return '<p>Error: This is chapter is locked as early access by the website, you will have to pay on the website or wait for the chapter to be released globally</p>';
|
||||
}
|
||||
}
|
||||
|
||||
async function soraFetch(url, options = {
|
||||
headers: {},
|
||||
method: 'GET',
|
||||
body: null
|
||||
}) {
|
||||
try {
|
||||
return await fetchv2(url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null);
|
||||
} catch (e) {
|
||||
try {
|
||||
return await fetch(url, options);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function decodeHtmlEntities(text) {
|
||||
const entities = {
|
||||
'—': '—',
|
||||
'–': '–',
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
''': "'",
|
||||
'/': '/',
|
||||
'`': '`',
|
||||
'=': '=',
|
||||
' ': ' '
|
||||
};
|
||||
|
||||
return text.replace(/&#x[\dA-Fa-f]+;|&\w+;/g, (match) => {
|
||||
return entities[match] || match;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sourceName": "HelioScan",
|
||||
"iconUrl": "https://wsrv.nl/?url=image.meowing.org/uploads/_9FxZ8P7Tik&w=44",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"language": "English",
|
||||
"streamType": "novels",
|
||||
"quality": "N/A",
|
||||
"baseUrl": "https://helioscans.com/",
|
||||
"searchBaseUrl": "https://helioscans.com/%s",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/helioscans/helioscans.js",
|
||||
"type": "novels",
|
||||
"asyncJS": true,
|
||||
"novel": true,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
const response = await fetchv2("https://hicartoon.to/search?keyword=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const blocks = html.split('<div class="flw-item">').slice(1);
|
||||
|
||||
for (const block of blocks) {
|
||||
const href = block.match(/<a href="([^"]+)"/);
|
||||
const image = block.match(/data-src="([^"]+)"/) || block.match(/src="([^"]+)"/);
|
||||
const title = block.match(/title="([^"]+?)"/);
|
||||
|
||||
if (href && image && title) {
|
||||
results.push({
|
||||
title: title[1].trim(),
|
||||
image: image[1].trim(),
|
||||
href: href[1].trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const descMatch = html.match(/<div class="film-description m-hide">[\s\S]*?<div class="text">\s*([\s\S]*?)\s*<\/div>/);
|
||||
const dateMatch = html.match(/<strong>Released:\s*<\/strong>\s*([^<\n]+)/);
|
||||
|
||||
const description = descMatch ? descMatch[1].trim() : "N/A";
|
||||
const airdate = dateMatch ? dateMatch[1].trim() : "N/A";
|
||||
|
||||
return JSON.stringify([{
|
||||
description: description,
|
||||
aliases: "N/A",
|
||||
airdate: airdate
|
||||
}]);
|
||||
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
let watchUrl = url;
|
||||
if (!/\/watch\//.test(watchUrl)) {
|
||||
watchUrl = watchUrl.replace(/\/([^\/]+)$/, '/watch/$1');
|
||||
}
|
||||
|
||||
const watchResp = await fetchv2(watchUrl);
|
||||
const watchHtml = await watchResp.text();
|
||||
const idMatch = watchHtml.match(/<div[^>]+id="wrapper"[^>]+data-id="(\d+)"[^>]*>/);
|
||||
if (!idMatch) throw new Error("movie_id not found");
|
||||
const movieId = idMatch[1];
|
||||
|
||||
const epListResp = await fetchv2(`https://hicartoon.to/ajax/v2/episode/list?movie_id=${movieId}`);
|
||||
const epListJson = await epListResp.json();
|
||||
const epHtml = epListJson.html;
|
||||
|
||||
const epRegex = /<a[^>]+class="ssl-item ep-item"[^>]+data-id="(\d+)"[^>]+href="([^"]+)"[^>]*>[\s\S]*?<div class="ssli-order"[^>]*>([^<]+)<\/div>[\s\S]*?<div class="ep-name e-dynamic-name"[^>]*>([^<]+)<\/div>/g;
|
||||
let match;
|
||||
let idx = 1;
|
||||
while ((match = epRegex.exec(epHtml)) !== null) {
|
||||
results.push({
|
||||
href: match[1],
|
||||
number: idx
|
||||
});
|
||||
idx++;
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return JSON.stringify([{ id: "Error", href: "Error", number: "Error", title: "Error" }]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(ID) {
|
||||
try {
|
||||
let response;
|
||||
let json;
|
||||
let iframeMatch;
|
||||
|
||||
// Try tserver
|
||||
try {
|
||||
response = await fetchv2(`https://hicartoon.to/ajax/v2/episode/sources?id=${ID}&s=tserver`);
|
||||
json = await response.json();
|
||||
iframeMatch = json.value ? json.value.match(/<iframe[^>]+src="([^"]+)"/) : null;
|
||||
} catch (e) {
|
||||
console.log("tserver failed, trying drserver");
|
||||
}
|
||||
|
||||
if (!iframeMatch) {
|
||||
try {
|
||||
response = await fetchv2(`https://hicartoon.to/ajax/v2/episode/sources?id=${ID}&s=drserver`);
|
||||
json = await response.json();
|
||||
iframeMatch = json.value ? json.value.match(/<iframe[^>]+src="([^"]+)"/) : null;
|
||||
} catch (e) {
|
||||
console.log("drserver failed, trying vhserver");
|
||||
}
|
||||
}
|
||||
|
||||
if (!iframeMatch) {
|
||||
try {
|
||||
response = await fetchv2(`https://hicartoon.to/ajax/v2/episode/sources?id=${ID}&s=vhserver`);
|
||||
json = await response.json();
|
||||
iframeMatch = json.value ? json.value.match(/<iframe[^>]+src="([^"]+)"/) : null;
|
||||
} catch (e) {
|
||||
console.log("vhserver failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (!iframeMatch) return "https://error.org/";
|
||||
const iframeUrl = iframeMatch[1];
|
||||
|
||||
|
||||
const streamHeaders = {
|
||||
"Referer": "https://kem.clvd.xyz/",
|
||||
"Origin": "https://kem.clvd.xyz/"
|
||||
};
|
||||
|
||||
const iframeResp = await fetchv2(iframeUrl);
|
||||
const iframeHtml = await iframeResp.text();
|
||||
|
||||
const hlsMatch = iframeHtml.match(/sources:\s*\[\{\s*"file":\s*"([^"]+)"/);
|
||||
const streamUrl = hlsMatch ? hlsMatch[1] : "";
|
||||
|
||||
const subtitle = "";
|
||||
return JSON.stringify({
|
||||
streams: [
|
||||
{
|
||||
title: "Server 1",
|
||||
streamUrl: streamUrl,
|
||||
headers: streamHeaders
|
||||
}
|
||||
],
|
||||
subtitle: subtitle
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return "https://error.org/";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "HiCartoon",
|
||||
"iconUrl": "https://hicartoon.to/assets/images/icons-512.png",
|
||||
"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://hicartoon.to/home",
|
||||
"searchBaseUrl": "https://hicartoon.to/home",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/hicartoon/hicartoon.js",
|
||||
"type": "anime/movies/shows",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": true,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://api.hicine.info/rpc/search/" + encodeURIComponent(keyword));
|
||||
const data = await response.json();
|
||||
|
||||
for (const item of data) {
|
||||
if (item.data) {
|
||||
results.push({
|
||||
title: item.data.title,
|
||||
image: item.data.featured_image,
|
||||
href: `https://api.hicine.info/api/${item.source_table}/${item.data.record_id}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 data = await response.json();
|
||||
|
||||
return JSON.stringify([{
|
||||
description: data.categories,
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const data = await response.json();
|
||||
|
||||
for (let seasonNum = 1; seasonNum <= 10; seasonNum++) {
|
||||
const seasonKey = `season_${seasonNum}`;
|
||||
const seasonData = data[seasonKey];
|
||||
|
||||
if (!seasonData) continue;
|
||||
|
||||
const episodes = seasonData.split('\n').filter(line => line.trim().startsWith('Episode'));
|
||||
let episodeNum = 1;
|
||||
|
||||
for (const episodeLine of episodes) {
|
||||
const qualityLinks = [];
|
||||
const qualityParts = episodeLine.split(' : ').slice(1);
|
||||
|
||||
for (const part of qualityParts) {
|
||||
const match = part.match(/^(.+?),\s*([^,]*),(.+?)$/);
|
||||
if (match) {
|
||||
const fullUrl = match[1].trim();
|
||||
const size = match[2].trim();
|
||||
const quality = match[3].trim();
|
||||
|
||||
const vcloudMatch = fullUrl.match(/vcloud=(https:\/\/vcloud\.zip\/[^,&]+)/);
|
||||
const vcloudUrl = vcloudMatch ? vcloudMatch[1] : fullUrl;
|
||||
|
||||
qualityLinks.push({
|
||||
link: vcloudUrl,
|
||||
quality: quality,
|
||||
size: size
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (qualityLinks.length > 0) {
|
||||
results.push({
|
||||
season: seasonNum,
|
||||
number: episodeNum,
|
||||
href: JSON.stringify(qualityLinks)
|
||||
});
|
||||
episodeNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.links && results.length === 0) {
|
||||
const links = data.links.split('\n').filter(line => line.trim());
|
||||
const qualityLinks = [];
|
||||
|
||||
for (const line of links) {
|
||||
const match = line.match(/vcloud=(https:\/\/vcloud\.zip\/[^,]+),\s*Link2,\s*Link3,\s*Link4,\s*Link5,\s*Link6,\s*Link7,\s*(.+?)(?:,\s*(\d+(?:\.\d+)?(?:MB|GB)))?$/);
|
||||
if (match) {
|
||||
const vcloudUrl = match[1].trim();
|
||||
const quality = match[2].trim();
|
||||
const size = match[3] ? match[3].trim() : '';
|
||||
qualityLinks.push({
|
||||
link: vcloudUrl,
|
||||
quality: quality,
|
||||
size: size
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (qualityLinks.length > 0) {
|
||||
results.push({
|
||||
href: JSON.stringify(qualityLinks),
|
||||
number: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(results.length > 0 ? results : [{
|
||||
href: "Error",
|
||||
number: "Error",
|
||||
season: "Error"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error",
|
||||
season: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const qualityLinks = JSON.parse(url);
|
||||
|
||||
const vcloudPromises = qualityLinks.map(async (quality, idx) => {
|
||||
try {
|
||||
const vcloudUrl = quality.link;
|
||||
const qualityName = quality.quality;
|
||||
|
||||
const vcloudResponse = await fetchv2(vcloudUrl, {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||
"Referer": "https://vcloud.zip/"
|
||||
});
|
||||
const vcloudHtml = await vcloudResponse.text();
|
||||
|
||||
const urlMatch = vcloudHtml.match(/var\s+url\s*=\s*'(https:\/\/[^']+)'/);
|
||||
if (!urlMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hubcloudUrl = urlMatch[1];
|
||||
|
||||
const hubcloudResponse = await fetchv2(hubcloudUrl, {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||
"Referer": vcloudUrl,
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
});
|
||||
const hubcloudHtml = await hubcloudResponse.text();
|
||||
|
||||
const fslMatch = hubcloudHtml.match(/<a\s+href="(https:\/\/love\.polgen\.buzz\/[^"]+)"[^>]*>[\s\S]*?Download\s*\[FSL\s*Server\]/i);
|
||||
if (fslMatch) {
|
||||
return {
|
||||
title: qualityName,
|
||||
streamUrl: fslMatch[1],
|
||||
headers: {}
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.error(`Error in quality ${idx}:`, err.message);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const streams = (await Promise.all(vcloudPromises)).filter(s => s !== null);
|
||||
|
||||
const result = {
|
||||
type: "servers",
|
||||
streams: streams,
|
||||
subtitle: "nonrt"
|
||||
};
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (err) {
|
||||
console.error(" Top-level error:", err.message);
|
||||
return JSON.stringify({
|
||||
type: "servers",
|
||||
streams: [],
|
||||
subtitle: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "Hicine",
|
||||
"iconUrl": "https://files.catbox.moe/y8v199.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Hindi",
|
||||
"streamType": "MKV",
|
||||
"quality": "4K",
|
||||
"baseUrl": "https://hicine.info/",
|
||||
"searchBaseUrl": "https://hicine.info/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/hicine/hicine.js",
|
||||
"type": "anime/movies/shows",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": true,
|
||||
"supportsSora": false,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
async function searchResults(keyword) {
|
||||
try {
|
||||
let transformedResults = [];
|
||||
|
||||
const keywordGroups = {
|
||||
trending: ["!trending", "!hot", "!tr", "!!"],
|
||||
topRatedMovie: ["!top-rated-movie", "!topmovie", "!tm", "??"],
|
||||
topRatedTV: ["!top-rated-tv", "!toptv", "!tt", "::"],
|
||||
popularMovie: ["!popular-movie", "!popmovie", "!pm", ";;"],
|
||||
popularTV: ["!popular-tv", "!poptv", "!pt", "++"],
|
||||
};
|
||||
|
||||
const skipTitleFilter = Object.values(keywordGroups).flat();
|
||||
|
||||
const shouldFilter = !matchesKeyword(keyword, skipTitleFilter);
|
||||
|
||||
const encodedKeyword = encodeURIComponent(keyword);
|
||||
let baseUrlTemplate = null;
|
||||
|
||||
if (matchesKeyword(keyword, keywordGroups.trending)) {
|
||||
baseUrlTemplate = (page) => `https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/trending/all/week?api_key=9801b6b0548ad57581d111ea690c85c8&include_adult=false&page=${page}`)}&simple=true`;
|
||||
} else if (matchesKeyword(keyword, keywordGroups.topRatedMovie)) {
|
||||
baseUrlTemplate = (page) => `https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/movie/top_rated?api_key=9801b6b0548ad57581d111ea690c85c8&include_adult=false&page=${page}`)}&simple=true`;
|
||||
} else if (matchesKeyword(keyword, keywordGroups.topRatedTV)) {
|
||||
baseUrlTemplate = (page) => `https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/tv/top_rated?api_key=9801b6b0548ad57581d111ea690c85c8&include_adult=false&page=${page}`)}&simple=true`;
|
||||
} else if (matchesKeyword(keyword, keywordGroups.popularMovie)) {
|
||||
baseUrlTemplate = (page) => `https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/movie/popular?api_key=9801b6b0548ad57581d111ea690c85c8&include_adult=false&page=${page}`)}&simple=true`;
|
||||
} else if (matchesKeyword(keyword, keywordGroups.popularTV)) {
|
||||
baseUrlTemplate = (page) => `https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/tv/popular?api_key=9801b6b0548ad57581d111ea690c85c8&include_adult=false&page=${page}`)}&simple=true`;
|
||||
} else {
|
||||
baseUrlTemplate = (page) => `https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/search/multi?api_key=9801b6b0548ad57581d111ea690c85c8&query=${encodedKeyword}&include_adult=false&page=${page}`)}&simple=true`;
|
||||
}
|
||||
|
||||
let dataResults = [];
|
||||
|
||||
if (baseUrlTemplate) {
|
||||
const pagePromises = Array.from({ length: 5 }, (_, i) =>
|
||||
soraFetch(baseUrlTemplate(i + 1)).then(r => r.json())
|
||||
);
|
||||
const pages = await Promise.all(pagePromises);
|
||||
dataResults = pages.flatMap(p => p.results || []);
|
||||
}
|
||||
|
||||
if (dataResults.length > 0) {
|
||||
transformedResults = transformedResults.concat(
|
||||
dataResults
|
||||
.map(result => {
|
||||
if (result.media_type === "movie" || result.title) {
|
||||
return {
|
||||
title: result.title || result.name || result.original_title || result.original_name || "Untitled",
|
||||
image: result.poster_path ? `https://image.tmdb.org/t/p/w500${result.poster_path}` : "",
|
||||
href: `movie/${result.id}`,
|
||||
};
|
||||
} else if (result.media_type === "tv" || result.name) {
|
||||
return {
|
||||
title: result.name || result.title || result.original_name || result.original_title || "Untitled",
|
||||
image: result.poster_path ? `https://image.tmdb.org/t/p/w500${result.poster_path}` : "",
|
||||
href: `tv/${result.id}/1/1`,
|
||||
};
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
.filter(r => !shouldFilter || r.title.toLowerCase().includes(keyword.toLowerCase()))
|
||||
);
|
||||
}
|
||||
|
||||
console.log("Transformed Results: " + JSON.stringify(transformedResults));
|
||||
return JSON.stringify(transformedResults);
|
||||
} catch (error) {
|
||||
console.log("Fetch error in searchResults: " + error);
|
||||
return JSON.stringify([{ title: "Error", image: "", href: "" }]);
|
||||
}
|
||||
}
|
||||
|
||||
function matchesKeyword(keyword, commands) {
|
||||
const lower = keyword.toLowerCase();
|
||||
return commands.some(cmd => lower.startsWith(cmd.toLowerCase()));
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
if(url.includes('movie')) {
|
||||
const match = url.match(/movie\/([^\/]+)/);
|
||||
if (!match) throw new Error("Invalid URL format");
|
||||
|
||||
const movieId = match[1];
|
||||
const responseText = await soraFetch(`https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/movie/${movieId}?api_key=ad301b7cc82ffe19273e55e4d4206885`)}&simple=true`);
|
||||
const data = await responseText.json();
|
||||
|
||||
const transformedResults = [{
|
||||
description: data.overview || 'No description available',
|
||||
aliases: `Duration: ${data.runtime ? data.runtime + " minutes" : 'Unknown'}`,
|
||||
airdate: `Released: ${data.release_date ? data.release_date : 'Unknown'}`
|
||||
}];
|
||||
|
||||
return JSON.stringify(transformedResults);
|
||||
} else if(url.includes('tv')) {
|
||||
const match = url.match(/tv\/([^\/]+)/);
|
||||
if (!match) throw new Error("Invalid URL format");
|
||||
|
||||
const showId = match[1];
|
||||
const responseText = await soraFetch(`https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/tv/${showId}?api_key=ad301b7cc82ffe19273e55e4d4206885`)}&simple=true`);
|
||||
const data = await responseText.json();
|
||||
|
||||
const transformedResults = [{
|
||||
description: data.overview || 'No description available',
|
||||
aliases: `Duration: ${data.episode_run_time && data.episode_run_time.length ? data.episode_run_time.join(', ') + " minutes" : 'Unknown'}`,
|
||||
airdate: `Aired: ${data.first_air_date ? data.first_air_date : 'Unknown'}`
|
||||
}];
|
||||
|
||||
console.log(JSON.stringify(transformedResults));
|
||||
return JSON.stringify(transformedResults);
|
||||
} else {
|
||||
throw new Error("Invalid URL format");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Details error: ' + error);
|
||||
return JSON.stringify([{
|
||||
description: 'Error loading description',
|
||||
aliases: 'Duration: Unknown',
|
||||
airdate: 'Aired/Released: Unknown'
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
try {
|
||||
if(url.includes('movie')) {
|
||||
const match = url.match(/movie\/([^\/]+)/);
|
||||
|
||||
if (!match) throw new Error("Invalid URL format");
|
||||
|
||||
const movieId = match[1];
|
||||
|
||||
const movie = [
|
||||
{ href: `/movie/${movieId}`, number: 1, title: "Full Movie" }
|
||||
];
|
||||
|
||||
console.log(movie);
|
||||
return JSON.stringify(movie);
|
||||
} else if(url.includes('tv')) {
|
||||
const match = url.match(/tv\/([^\/]+)\/([^\/]+)\/([^\/]+)/);
|
||||
|
||||
if (!match) throw new Error("Invalid URL format");
|
||||
|
||||
const showId = match[1];
|
||||
|
||||
const showResponseText = await soraFetch(`https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/tv/${showId}?api_key=ad301b7cc82ffe19273e55e4d4206885`)}&simple=true`);
|
||||
const showData = await showResponseText.json();
|
||||
|
||||
let allEpisodes = [];
|
||||
for (const season of showData.seasons) {
|
||||
const seasonNumber = season.season_number;
|
||||
|
||||
if(seasonNumber === 0) continue;
|
||||
|
||||
const seasonResponseText = await soraFetch(`https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/tv/${showId}/season/${seasonNumber}?api_key=ad301b7cc82ffe19273e55e4d4206885`)}&simple=true`);
|
||||
const seasonData = await seasonResponseText.json();
|
||||
|
||||
if (seasonData.episodes && seasonData.episodes.length) {
|
||||
const episodes = seasonData.episodes.map(episode => ({
|
||||
href: `/tv/${showId}/${seasonNumber}/${episode.episode_number}`,
|
||||
number: episode.episode_number,
|
||||
title: episode.name || ""
|
||||
}));
|
||||
allEpisodes = allEpisodes.concat(episodes);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(allEpisodes);
|
||||
return JSON.stringify(allEpisodes);
|
||||
} else {
|
||||
throw new Error("Invalid URL format");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Fetch error in extractEpisodes: ' + error);
|
||||
return JSON.stringify([]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(ID) {
|
||||
if (ID.includes('movie')) {
|
||||
const tmdbID = ID.replace('/movie/', '');
|
||||
|
||||
const responseText = await soraFetch(`https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/movie/${tmdbID}?api_key=ad301b7cc82ffe19273e55e4d4206885`)}&simple=true`);
|
||||
const data = await responseText.json();
|
||||
|
||||
const slug = data.title.toLowerCase().replace(/\s+/g, '-') + '-' + data.release_date.split('-')[0];
|
||||
const url = `https://hollymoviehd.cc/${slug}/`;
|
||||
console.log(url);
|
||||
|
||||
const responseTwo = await soraFetch(url);
|
||||
const pageText = await responseTwo.text();
|
||||
const streamkeyMatch = pageText.match(/data-streamkey="([^"]*)"/);
|
||||
const nonceMatch = pageText.match(/data-wpnonce="([^"]*)"/);
|
||||
if (streamkeyMatch && nonceMatch) {
|
||||
const streamkey = streamkeyMatch[1];
|
||||
const nonce = nonceMatch[1];
|
||||
const ajaxParams = `action=ajax_getlinkstream&streamkey=${streamkey}&nonce=${nonce}&imdbid=&tmdbid=${tmdbID}`;
|
||||
console.log(ajaxParams);
|
||||
const headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"Referer": "https://hollymoviehd.cc",
|
||||
"Origin": "https://hollymoviehd.cc"
|
||||
};
|
||||
const apiResponse = await fetchv2("https://hollymoviehd.cc/wp-admin/admin-ajax.php", headers, "POST", ajaxParams);
|
||||
const apiData = await apiResponse.json();
|
||||
const serverUrl = apiData.servers_iframe.streamsvr;
|
||||
console.log(serverUrl);
|
||||
|
||||
const serverResponse = await soraFetch(serverUrl);
|
||||
const serverText = await serverResponse.text();
|
||||
const csrfMatch = serverText.match(/id="csrf_token" value="([^"]*)"/);
|
||||
if (csrfMatch) {
|
||||
const csrfToken = csrfMatch[1];
|
||||
const boundary = '----geckoformboundary7183c2c77a4fd6afe70224d86261ff15';
|
||||
const body = `--${boundary}\r\nContent-Disposition: form-data; name="token"\r\n\r\n\r\n--${boundary}\r\nContent-Disposition: form-data; name="csrf_token"\r\n\r\n${csrfToken}\r\n--${boundary}--`;
|
||||
const headers = {
|
||||
'Referer': 'https://flashstream.cc/',
|
||||
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
||||
'Origin': 'https://flashstream.cc'
|
||||
};
|
||||
const postResponse = await soraFetch(serverUrl, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: body
|
||||
});
|
||||
const postText = await postResponse.text();
|
||||
console.log(postText);
|
||||
const data = JSON.parse(postText);
|
||||
const sources = data.sources;
|
||||
const hlsSource = sources.find(s => s.type === 'hls' && s.file.startsWith('https://flashstream.cc/pl/'));
|
||||
if (hlsSource) {
|
||||
const streamUrl = hlsSource.file;
|
||||
const result = {
|
||||
streams: [
|
||||
{
|
||||
title: "default",
|
||||
streamUrl: streamUrl,
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0",
|
||||
"Accept": "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Range": "bytes=0-",
|
||||
"Alt-Used": "flashstream.cc",
|
||||
"Connection": "keep-alive",
|
||||
"Referer": "https://flashstream.cc/t: video",
|
||||
"Sec-Fetch-Mode": "no-cors",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Accept-Encoding": "identity",
|
||||
"Priority": "u=4",
|
||||
"TE": "trailers"
|
||||
}
|
||||
}
|
||||
],
|
||||
subtitle: ""
|
||||
};
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (ID.includes('tv')) {
|
||||
const parts = ID.split('/');
|
||||
const tmdbID = parts[2];
|
||||
const seasonNumber = parts[3];
|
||||
const episodeNumber = parts[4];
|
||||
|
||||
const responseText = await soraFetch(`https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/tv/${tmdbID}?api_key=ad301b7cc82ffe19273e55e4d4206885`)}&simple=true`);
|
||||
const data = await responseText.json();
|
||||
|
||||
const slug = data.name.toLowerCase().replace(/\s+/g, '-') + '-season-' + seasonNumber + '-episode-' + episodeNumber;
|
||||
const url = `https://hollymoviehd.cc/episode/${slug}/`;
|
||||
console.log(url);
|
||||
const responseTwo = await soraFetch(url);
|
||||
const pageText = await responseTwo.text();
|
||||
const streamkeyMatch = pageText.match(/data-streamkey="([^"]*)"/);
|
||||
const nonceMatch = pageText.match(/data-wpnonce="([^"]*)"/);
|
||||
if (streamkeyMatch && nonceMatch) {
|
||||
const streamkey = streamkeyMatch[1];
|
||||
const nonce = nonceMatch[1];
|
||||
const ajaxParams = `action=ajax_getlinkstream&streamkey=${streamkey}&nonce=${nonce}&imdbid=&tmdbid=${tmdbID}`;
|
||||
console.log(ajaxParams);
|
||||
const headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"Referer": "https://hollymoviehd.cc",
|
||||
"Origin": "https://hollymoviehd.cc"
|
||||
};
|
||||
const apiResponse = await fetchv2("https://hollymoviehd.cc/wp-admin/admin-ajax.php", headers, "POST", ajaxParams);
|
||||
const apiData = await apiResponse.json();
|
||||
const serverUrl = apiData.servers_iframe.streamsvr;
|
||||
console.log(serverUrl);
|
||||
const serverResponse = await soraFetch(serverUrl);
|
||||
const serverText = await serverResponse.text();
|
||||
const csrfMatch = serverText.match(/id="csrf_token" value="([^"]*)"/);
|
||||
if (csrfMatch) {
|
||||
const csrfToken = csrfMatch[1];
|
||||
const boundary = '----geckoformboundary7183c2c77a4fd6afe70224d86261ff15';
|
||||
const body = `--${boundary}\r\nContent-Disposition: form-data; name="token"\r\n\r\n\r\n--${boundary}\r\nContent-Disposition: form-data; name="csrf_token"\r\n\r\n${csrfToken}\r\n--${boundary}--`;
|
||||
const headers = {
|
||||
'Referer': 'https://flashstream.cc/',
|
||||
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
||||
'Origin': 'https://flashstream.cc'
|
||||
};
|
||||
const postResponse = await soraFetch(serverUrl, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: body
|
||||
});
|
||||
const postText = await postResponse.text();
|
||||
console.log(postText);
|
||||
const data = JSON.parse(postText);
|
||||
const sources = data.sources;
|
||||
const hlsSource = sources.find(s => s.type === 'hls' && s.file.startsWith('https://flashstream.cc/pl/'));
|
||||
if (hlsSource) {
|
||||
const streamUrl = hlsSource.file;
|
||||
const result = {
|
||||
streams: [
|
||||
{
|
||||
title: "default",
|
||||
streamUrl: streamUrl,
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0",
|
||||
"Accept": "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Range": "bytes=0-",
|
||||
"Alt-Used": "flashstream.cc",
|
||||
"Connection": "keep-alive",
|
||||
"Referer": "https://flashstream.cc/t: video",
|
||||
"Sec-Fetch-Mode": "no-cors",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Accept-Encoding": "identity",
|
||||
"Priority": "u=4",
|
||||
"TE": "trailers"
|
||||
}
|
||||
}
|
||||
],
|
||||
subtitle: ""
|
||||
};
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null, encoding: 'utf-8' }) {
|
||||
try {
|
||||
return await fetchv2(
|
||||
url,
|
||||
options.headers ?? {},
|
||||
options.method ?? 'GET',
|
||||
options.body ?? null,
|
||||
true,
|
||||
options.encoding ?? 'utf-8'
|
||||
);
|
||||
} catch(e) {
|
||||
try {
|
||||
return await fetch(url, options);
|
||||
} catch(error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"sourceName": "HollyMovieHD",
|
||||
"iconUrl": "https://files.catbox.moe/r092e6.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "English (Hardsub)",
|
||||
"streamType": "HLS",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://hollymoviehd.cc/home/",
|
||||
"searchBaseUrl": "https://hollymoviehd.cc/home/",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/hollymoviehd/hollymoviehd.js",
|
||||
"type": "anime/movies/shows",
|
||||
"asyncJS": true,
|
||||
"softsub": true,
|
||||
"downloadSupport": false,
|
||||
"supportsLuna": true,
|
||||
"note": "Requires Luna, aka Sora 2.0"
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2("https://tn.honadrama.us/?s=" + encodeURIComponent(keyword));
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<div class="movie"><a href="([^"]+)".*?<img src="([^"]+)"[^>]*>.*?<h3>(.*?)<\/h3>/gs;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: match[3].trim(),
|
||||
image: match[2].trim(),
|
||||
href: match[1].trim()
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
title: "Error",
|
||||
image: "Error",
|
||||
href: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const descriptionMatch = (/<div class="StoryMovie">(.*?)<\/div>/s.exec(html) || [])[1];
|
||||
|
||||
return JSON.stringify([{
|
||||
description: descriptionMatch ? descriptionMatch.trim() : "N/A",
|
||||
aliases: "N/A",
|
||||
airdate: "N/A"
|
||||
}]);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
description: "Error",
|
||||
aliases: "Error",
|
||||
airdate: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="([^"]+)" class="WatchButton">/;
|
||||
const match = regex.exec(html);
|
||||
|
||||
if (match) {
|
||||
results.push({
|
||||
href: match[1].trim(),
|
||||
number: 1
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (err) {
|
||||
return JSON.stringify([{
|
||||
href: "Error",
|
||||
number: "Error"
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
try {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const serverRegex = /<li\s+([^>]*?)data-server="(\d+)"\s+data-q="(\d+)"[^>]*>/g;
|
||||
const servers = [];
|
||||
let match;
|
||||
while ((match = serverRegex.exec(html)) !== null) {
|
||||
servers.push({
|
||||
server: match[2],
|
||||
q: match[3]
|
||||
});
|
||||
}
|
||||
|
||||
let uqloadUrl = null;
|
||||
|
||||
for (const s of servers) {
|
||||
const ajaxUrl = `https://tn.honadrama.us/wp-admin/admin-ajax.php?action=serverPost&server=${s.server}&q=${s.q}`;
|
||||
const serverResp = await fetchv2(ajaxUrl);
|
||||
const serverHtml = await serverResp.text();
|
||||
|
||||
const iframeMatch = /<iframe\s+[^>]*src="([^"]*uqload\.[a-z]{2,3}[^"]*)"/i.exec(serverHtml);
|
||||
|
||||
if (iframeMatch) {
|
||||
uqloadUrl = iframeMatch[1];
|
||||
console.log("[Debug] Uqload iframe found:"+ uqloadUrl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!uqloadUrl) {
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
|
||||
const uqResp = await fetchv2(uqloadUrl);
|
||||
const uqHtml = await uqResp.text();
|
||||
|
||||
const mp4Match = /sources:\s*\["([^"]+\.mp4)"]/i.exec(uqHtml);
|
||||
if (mp4Match) {
|
||||
return mp4Match[1];
|
||||
} else {
|
||||
console.log("[Debug] No MP4 found in uqload page, returning default");
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log("[Error] Fetching stream failed:", err);
|
||||
return "https://files.catbox.moe/avolvc.mp4";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sourceName": "HonaDrama",
|
||||
"iconUrl": "https://tn.honadrama.us/wp-content/uploads/2023/06/cropped-logo-google-192x192.png",
|
||||
"author": {
|
||||
"name": "50/50",
|
||||
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"language": "Arabic",
|
||||
"streamType": "mp4",
|
||||
"quality": "1080p",
|
||||
"baseUrl": "https://uqload.cx/",
|
||||
"searchBaseUrl": "https://uqload.cx/?s=%s",
|
||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/honadrama/honadrama.js",
|
||||
"type": "shows/movies",
|
||||
"asyncJS": true,
|
||||
"downloadSupport": false,
|
||||
"supportsSora": true,
|
||||
"supportsLuna": true
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
async function searchResults(keyword) {
|
||||
const results = [];
|
||||
const response = await fetchv2(`http://www.iyinghua.com/search/${keyword}/`);
|
||||
const html = await response.text();
|
||||
|
||||
const regex = /<a href="(\/show\/\d+\.html)"><img src="(http[^"]+)" alt="([^"]+)"><\/a><h2><a href="[^"]+" title="([^"]+)"/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
results.push({
|
||||
title: match[4].trim(),
|
||||
image: match[2].trim(),
|
||||
href: `http://www.iyinghua.com${match[1].trim()}`
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractDetails(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const descriptionRegex = /<div class="info">([^<]+)<\/div>/;
|
||||
const descriptionMatch = descriptionRegex.exec(html);
|
||||
const description = descriptionMatch ? descriptionMatch[1].trim() : 'N/A';
|
||||
|
||||
results.push({
|
||||
description: description,
|
||||
aliases: 'N/A',
|
||||
airdate: 'N/A'
|
||||
});
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractEpisodes(url) {
|
||||
const results = [];
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
|
||||
const episodeRegex = /<li><a href="(\/v\/\d+-([\dpv]+)\.html)" target="_blank">([^<]+)<\/a><\/li>/g;
|
||||
|
||||
let match;
|
||||
let episodeCount = 0;
|
||||
|
||||
while ((match = episodeRegex.exec(html)) !== null) {
|
||||
const href = `http://www.iyinghua.com${match[1].trim()}`;
|
||||
const number = match[2].match(/^\d+$/) ? parseInt(match[2], 10) : null;
|
||||
|
||||
if (number !== null) {
|
||||
results.push({ href, number });
|
||||
} else {
|
||||
episodeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length === 0 && episodeCount > 0) {
|
||||
episodeRegex.lastIndex = 0;
|
||||
let index = 1;
|
||||
while ((match = episodeRegex.exec(html)) !== null) {
|
||||
results.push({
|
||||
href: `http://www.iyinghua.com${match[1].trim()}`,
|
||||
number: index++
|
||||
});
|
||||
}
|
||||
}
|
||||
results.reverse();
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
||||
async function extractStreamUrl(url) {
|
||||
const response = await fetchv2(url);
|
||||
const html = await response.text();
|
||||
console.error(html);
|
||||
|
||||
const streamRegex = /data-vid="([^"]+)"/;
|
||||
const match = streamRegex.exec(html);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user