fix: update animetsu and movies4u providers

This commit is contained in:
Himanshu
2026-06-19 13:10:37 +05:30
parent 3fedeb8816
commit 695dd89f4e
11 changed files with 300 additions and 178 deletions
+5 -9
View File
@@ -1,23 +1,19 @@
export const catalog = [
{
title: "Popular",
filter:
"/api/anime/search?query=&page=1&perPage=35&year=any&sort=favourites&season=any&format=any&status=any",
filter: "popular",
},
{
title: "Trending",
filter:
"/api/anime/search?query=&page=1&perPage=35&year=any&sort=trending&season=any&format=any&status=any",
filter: "trending",
},
{
title: "Top Rated",
filter:
"/api/anime/search?query=&page=1&perPage=35&year=any&sort=rating&season=any&format=any&status=any",
filter: "top",
},
{
title: "Favourites",
filter:
"/api/anime/search?query=&page=1&perPage=35&year=any&sort=updated&season=any&format=any&status=any",
title: "Seasonal",
filter: "seasonal",
},
];
+84 -58
View File
@@ -8,15 +8,33 @@ export const getMeta = async function ({
providerContext: ProviderContext;
}): Promise<Info> {
try {
const { axios } = providerContext;
const baseUrl = "https://backend.animetsu.to";
const url = `${baseUrl}/api/anime/info/${link}`;
const { axios, openWebView, commonHeaders } = providerContext;
const baseUrl = "https://animetsu.net";
const url = `${baseUrl}/v2/api/anime/${link}`;
const res = await axios.get(url, {
headers: {
Referer: "https://animetsu.to/",
},
});
let cookies: string | undefined;
let res: any;
try {
res = await axios.get(url, {
headers: { ...commonHeaders, Referer: baseUrl },
});
} catch (error: any) {
if (error.response?.status === 403) {
const wafResult = await openWebView(baseUrl, {
title: "Solve the captcha below and click done",
description: "Required to bypass Animetsu anti-bot protection.",
headers: { ...commonHeaders, Referer: baseUrl },
force: true,
waitForCookie: "cf_clearance",
});
cookies = wafResult.cookies;
res = await axios.get(url, {
headers: { ...commonHeaders, Referer: baseUrl, Cookie: cookies },
});
} else {
throw error;
}
}
const data = res.data;
const meta = {
@@ -24,12 +42,12 @@ export const getMeta = async function ({
data.title?.english || data.title?.romaji || data.title?.native || "",
synopsis: data.description || "",
image:
data.coverImage?.extraLarge ||
data.coverImage?.large ||
data.coverImage?.medium ||
data.cover_image?.large ||
data.cover_image?.medium ||
data.cover_image?.small ||
"",
tags: [data?.format, data?.status, ...(data?.genres || [])].filter(
Boolean
Boolean,
),
imdbId: "",
type: data.format === "MOVIE" ? "movie" : "series",
@@ -37,58 +55,66 @@ export const getMeta = async function ({
const linkList: Link[] = [];
// Get episodes data
try {
const episodesRes = await axios.get(`${baseUrl}/api/anime/eps/${link}`, {
headers: {
Referer: "https://animetsu.to/",
},
});
const episodes = episodesRes.data;
const seasons = data.seasons;
if (seasons && seasons.length > 0) {
await Promise.all(
seasons.map(async (season: any) => {
const seasonTitle =
season.title?.english ||
season.title?.romaji ||
season.title?.native;
const directLinks: Link["directLinks"] = [];
if (episodes && episodes.length > 0) {
const directLinks: Link["directLinks"] = [];
try {
const epsRes = await axios.get(
`${baseUrl}/api/anime/eps/${season.id}`,
{
headers: {
...commonHeaders,
Referer: baseUrl,
...(cookies ? { Cookie: cookies } : {}),
},
},
);
const episodes = epsRes.data;
if (episodes && episodes.length > 0) {
episodes.forEach((ep: any) => {
directLinks.push({
title: `Episode ${ep.number}`,
link: `${season.id}:${ep.number}`,
});
});
}
} catch {
// fallback: use total_eps count
const total = season.total_eps || 1;
for (let i = 1; i <= total; i++) {
directLinks.push({
title: `Episode ${i}`,
link: `${season.id}:${i}`,
});
}
}
episodes.forEach((episode: any) => {
const title = `Episode ${episode.number}`;
const episodeLink = `${link}:${episode.number}`;
if (episodeLink && title) {
directLinks.push({
title,
link: episodeLink,
if (directLinks.length > 0) {
linkList.push({
title: seasonTitle || meta.title,
directLinks,
});
}
});
linkList.push({
title: meta.title,
directLinks: directLinks,
});
} else {
// Movie case - single episode
linkList.push({
title: meta.title,
directLinks: [
{
title: "Movie",
link: `${link}:1`,
},
],
}),
);
} else {
// Movie or single-season fallback
const total = data.total_eps || 1;
const directLinks: Link["directLinks"] = [];
for (let i = 1; i <= total; i++) {
directLinks.push({
title: total === 1 ? "Movie" : `Episode ${i}`,
link: `${link}:${i}`,
});
}
} catch (episodeErr) {
console.error("Error fetching episodes:", episodeErr);
// Fallback for movie or single episode
linkList.push({
title: meta.title,
directLinks: [
{
title: meta.title,
link: `${link}:1`,
},
],
});
linkList.push({ title: meta.title, directLinks });
}
return {
+61 -20
View File
@@ -12,14 +12,21 @@ export const getPosts = async function ({
signal: AbortSignal;
providerContext: ProviderContext;
}): Promise<Post[]> {
const { axios } = providerContext;
const baseUrl = "https://backend.animetsu.to";
if (page > 1) {
return [];
}
const { axios, commonHeaders } = providerContext;
const baseUrl = "https://animetsu.net";
const url = `${baseUrl}/v2/api/anime/home`;
// Parse filter to modify page parameter
const url = baseUrl + filter + "&page=" + page.toString();
console.log("animetsuGetPosts url", url);
return posts({ url: url.toString(), signal, axios });
return posts({
url,
filter,
signal,
axios,
providerContext,
headers: commonHeaders,
});
};
export const getSearchPosts = async function ({
@@ -34,32 +41,62 @@ export const getSearchPosts = async function ({
signal: AbortSignal;
providerContext: ProviderContext;
}): Promise<Post[]> {
const { axios } = providerContext;
const baseUrl = "https://backend.animetsu.to";
const url = `${baseUrl}/api/anime/search?query=${encodeURIComponent(
searchQuery
)}&page=${page}&perPage=35&year=any&sort=favourites&season=any&format=any&status=any`;
const { axios, commonHeaders } = providerContext;
const baseUrl = "https://animetsu.net";
const url = `${baseUrl}/v2/api/anime/search/?query=${encodeURIComponent(
searchQuery,
)}`;
return posts({ url, signal, axios });
return posts({ url, signal, axios, providerContext, headers: commonHeaders });
};
async function posts({
url,
filter,
signal,
axios,
providerContext,
headers,
}: {
url: string;
filter?: string;
signal: AbortSignal;
axios: ProviderContext["axios"];
providerContext: ProviderContext;
headers?: Record<string, string>;
}): Promise<Post[]> {
const baseUrl = "https://animetsu.net";
const { openWebView } = providerContext;
try {
const res = await axios.get(url, {
signal,
headers: {
Referer: "https://animetsu.to/",
},
});
const data = res.data?.results;
let cookies: string | undefined;
let res: any;
try {
res = await axios.get(url, {
signal,
headers: {
...headers,
Referer: baseUrl,
},
});
} catch (error: any) {
if (error.response?.status === 403) {
const wafResult = await openWebView(baseUrl, {
title: "Solve the captcha below and click done",
description: "Required to bypass Animetsu anti-bot protection.",
headers: { ...headers, Referer: baseUrl },
force: true,
waitForCookie: "cf_clearance",
});
cookies = wafResult.cookies;
res = await axios.get(url, {
signal,
headers: { ...headers, Referer: baseUrl, Cookie: cookies },
});
} else {
throw error;
}
}
const data = filter ? res.data?.[filter] : res.data?.results || res.data;
const catalog: Post[] = [];
data?.map((element: any) => {
@@ -69,6 +106,10 @@ async function posts({
element.title?.native;
const link = element.id?.toString();
const image =
element.cover_image?.large ||
element.cover_image?.extraLarge ||
element.cover_image?.medium ||
element.cover_image?.small ||
element.coverImage?.large ||
element.coverImage?.extraLarge ||
element.coverImage?.medium;
+102 -75
View File
@@ -8,8 +8,26 @@ export const getStream = async function ({
providerContext: ProviderContext;
}): Promise<Stream[]> {
try {
const { axios } = providerContext;
const baseUrl = "https://backend.animetsu.to";
const { axios, openWebView, commonHeaders } = providerContext;
const baseUrl = "https://animetsu.net";
let wafCookies: string | undefined;
try {
await axios.get(baseUrl, {
headers: { ...commonHeaders, Referer: baseUrl },
});
} catch (error: any) {
if (error.response?.status === 403) {
const wafResult = await openWebView(baseUrl, {
title: "Solve the captcha below and click done",
description: "Required to bypass Animetsu anti-bot protection.",
headers: { ...commonHeaders, Referer: baseUrl },
force: true,
waitForCookie: "cf_clearance",
});
wafCookies = wafResult.cookies;
}
}
// Parse link format: "animeId:episodeNumber"
const [animeId, episodeNumber] = id.split(":");
@@ -18,61 +36,65 @@ export const getStream = async function ({
throw new Error("Invalid link format");
}
const servers = ["pahe", "zoro"]; // Available servers based on API structure
const servers = ["sage", "meg", "dio", "kite"];
const streamLinks: Stream[] = [];
await Promise.all(
servers.map(async (server) => {
try {
const url = `${baseUrl}/api/anime/tiddies?server=${server}&id=${animeId}&num=${episodeNumber}&subType=sub`;
const url = `${baseUrl}/v2/api/anime/oppai/${animeId}/${episodeNumber}?server=${server}&source_type=sub`;
const res = await axios.get(url, {
headers: {
Referer: "https://animetsu.to/",
...commonHeaders,
Referer: baseUrl,
...(wafCookies ? { Cookie: wafCookies } : {}),
},
});
if (res.data && res.data.sources) {
const subtitles: TextTracks = [];
// if (res.data.subtitles && Array.isArray(res.data.subtitles)) {
// res.data.subtitles.forEach((sub: any) => {
// if (sub.url && sub.lang) {
// // Extract language code from lang string (e.g., "English" -> "en", "Arabic - CR" -> "ar")
// const langCode = sub.lang.toLowerCase().includes("english")
// ? "en"
// : sub.lang.toLowerCase().includes("arabic")
// ? "ar"
// : sub.lang.toLowerCase().includes("french")
// ? "fr"
// : sub.lang.toLowerCase().includes("german")
// ? "de"
// : sub.lang.toLowerCase().includes("italian")
// ? "it"
// : sub.lang.toLowerCase().includes("portuguese")
// ? "pt"
// : sub.lang.toLowerCase().includes("russian")
// ? "ru"
// : sub.lang.toLowerCase().includes("spanish")
// ? "es"
// : "und";
if (res.data.subs && Array.isArray(res.data.subs)) {
res.data.subs.forEach((sub: any) => {
if (sub.url && sub.lang) {
const langCode = sub.lang.toLowerCase().includes("english")
? "en"
: sub.lang.toLowerCase().includes("arabic")
? "ar"
: sub.lang.toLowerCase().includes("french")
? "fr"
: sub.lang.toLowerCase().includes("german")
? "de"
: sub.lang.toLowerCase().includes("italian")
? "it"
: sub.lang.toLowerCase().includes("portuguese")
? "pt"
: sub.lang.toLowerCase().includes("russian")
? "ru"
: sub.lang.toLowerCase().includes("spanish")
? "es"
: "und";
// subtitles.push({
// title: sub.lang,
// language: langCode,
// type: "text/vtt",
// uri: sub.url,
// });
// }
// });
// }
subtitles.push({
title: sub.lang,
language: langCode,
type: "text/vtt",
uri: sub.url,
});
}
});
}
res.data.sources.forEach((source: any) => {
const sourceUrl = source.url.startsWith("/")
? `${baseUrl}${source.url}`
: source.url;
streamLinks.push({
server: server + `: ${source.quality}`,
link: `https://m3u8.8man.workers.dev?url=${source.url}`,
server: `${server} (Sub): ${source.quality}`,
link: `https://m3u8.8man.workers.dev?url=${encodeURIComponent(sourceUrl)}`,
type: "m3u8",
quality: source.quality,
headers: {
referer: "https://animetsu.to/",
referer: baseUrl,
},
subtitles: subtitles.length > 0 ? subtitles : [],
});
@@ -81,62 +103,67 @@ export const getStream = async function ({
} catch (e) {
console.log(`Error with server ${server}:`, e);
}
})
}),
);
// Try dub version as well
await Promise.all(
servers.map(async (server) => {
try {
const url = `${baseUrl}/api/anime/tiddies?server=${server}&id=${animeId}&num=${episodeNumber}&subType=dub`;
const url = `${baseUrl}/v2/api/anime/oppai/${animeId}/${episodeNumber}?server=${server}&source_type=dub`;
const res = await axios.get(url, {
headers: {
referer: "https://animetsu.to/",
...commonHeaders,
Referer: baseUrl,
...(wafCookies ? { Cookie: wafCookies } : {}),
},
});
if (res.data && res.data.sources) {
const subtitles: TextTracks = [];
// if (res.data.subtitles && Array.isArray(res.data.subtitles)) {
// res.data.subtitles.forEach((sub: any) => {
// if (sub.url && sub.lang) {
// // Extract language code from lang string (e.g., "English" -> "en", "Arabic - CR" -> "ar")
// const langCode = sub.lang.toLowerCase().includes("english")
// ? "en"
// : sub.lang.toLowerCase().includes("arabic")
// ? "ar"
// : sub.lang.toLowerCase().includes("french")
// ? "fr"
// : sub.lang.toLowerCase().includes("german")
// ? "de"
// : sub.lang.toLowerCase().includes("italian")
// ? "it"
// : sub.lang.toLowerCase().includes("portuguese")
// ? "pt"
// : sub.lang.toLowerCase().includes("russian")
// ? "ru"
// : sub.lang.toLowerCase().includes("spanish")
// ? "es"
// : "und";
if (res.data.subs && Array.isArray(res.data.subs)) {
res.data.subs.forEach((sub: any) => {
if (sub.url && sub.lang) {
// Extract language code from lang string (e.g., "English" -> "en", "Arabic - CR" -> "ar")
const langCode = sub.lang.toLowerCase().includes("english")
? "en"
: sub.lang.toLowerCase().includes("arabic")
? "ar"
: sub.lang.toLowerCase().includes("french")
? "fr"
: sub.lang.toLowerCase().includes("german")
? "de"
: sub.lang.toLowerCase().includes("italian")
? "it"
: sub.lang.toLowerCase().includes("portuguese")
? "pt"
: sub.lang.toLowerCase().includes("russian")
? "ru"
: sub.lang.toLowerCase().includes("spanish")
? "es"
: "und";
// subtitles.push({
// title: sub.lang,
// language: langCode,
// type: "text/vtt",
// uri: sub.url,
// });
// }
// });
// }
subtitles.push({
title: sub.lang,
language: langCode,
type: "text/vtt",
uri: sub.url,
});
}
});
}
res.data.sources.forEach((source: any) => {
const sourceUrl = source.url.startsWith("/")
? `${baseUrl}${source.url}`
: source.url;
streamLinks.push({
server: `${server} (Dub) : ${source.quality}`,
link: `https://m3u8.8man.workers.dev?url=${source.url}`,
server: `${server} (Dub): ${source.quality}`,
link: `https://m3u8.8man.workers.dev?url=${encodeURIComponent(sourceUrl)}`,
type: "m3u8",
quality: source.quality,
headers: {
referer: "https://animetsu.to/",
referer: baseUrl,
},
subtitles: subtitles.length > 0 ? subtitles : [],
});
@@ -145,7 +172,7 @@ export const getStream = async function ({
} catch (e) {
console.log(`Error with server ${server} (dub):`, e);
}
})
}),
);
console.log("Stream links:", streamLinks);
+40 -8
View File
@@ -15,11 +15,6 @@ const headers = {
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
Cookie:
"xla=s4t; _ga=GA1.1.1081149560.1756378968; _ga_BLZGKYN5PF=GS2.1.s1756378968$o1$g1$t1756378984$j44$l0$h0",
"Upgrade-Insecure-Requests": "1",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
};
export async function getStream({
@@ -33,13 +28,50 @@ export async function getStream({
signal: AbortSignal;
providerContext: ProviderContext;
}) {
const { axios, cheerio, commonHeaders } = providerContext;
const { axios, cheerio, commonHeaders, openWebView } = providerContext;
try {
const streamLinks: Stream[] = [];
console.log("dotlink", link);
if (type === "movie") {
// vlink
const dotlinkRes = await axios(`${link}`, { headers });
let dotlinkRes;
let cookies: string | undefined;
try {
dotlinkRes = await axios(`${link}`, {
headers: {
...commonHeaders,
Referer: link,
},
});
} catch (error: any) {
if (error.response?.status === 403) {
console.log("Solving WAF for Movies4U...");
const wafResult = await openWebView(link, {
title: "Solve the captcha below and click done",
description:
"This is required to bypass the anti-bot protection and retrieve the stream link.",
headers: {
...commonHeaders,
Referer: link,
},
force: true,
waitForCookie: "cf_clearance",
});
console.log("WAF solved", wafResult.cookies);
cookies = wafResult.cookies;
dotlinkRes = await axios(`${link}`, {
headers: {
...commonHeaders,
Referer: link,
Cookie: cookies,
},
});
} else {
throw error;
}
}
const dotlinkText = dotlinkRes.data;
// console.log('dotlinkText', dotlinkText);
const vlink = dotlinkText.match(/<a\s+href="([^"]*cloud\.[^"]*)"/i) || [];