feat: update Movies4U version to 1.4 and modify catalog titles

fix: handle anti-DDoS checks in meta and posts fetching
fix: improve episode extraction logic in episodes
fix: enhance stream link extraction in stream

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Himanshu
2026-05-03 21:24:04 +05:30
parent 80e687da6b
commit 2ce3c737d1
11 changed files with 309 additions and 83 deletions
+4 -4
View File
@@ -1,10 +1,10 @@
export const catalog = [
{
title: "Trending",
title: "Latest",
filter: "",
},
{
title: "Anime",
filter: "/category/anime/",
title: "Web Series",
filter: "/category/web-series/",
},
];
];
+106 -34
View File
@@ -1,6 +1,6 @@
import { EpisodeLink, ProviderContext } from "../types";
// यहाँ `getEpisodes` फ़ंक्शन मान रहा है कि यह उस पेज को स्क्रैप कर रहा है
// यहाँ `getEpisodes` फ़ंक्शन मान रहा है कि यह उस पेज को स्क्रैप कर रहा है
// जो 'Download Links' बटन से प्राप्त हुआ है (जैसे m4ulinks.com/number/42882)
export const getEpisodes = async function ({
@@ -14,7 +14,7 @@ export const getEpisodes = async function ({
console.log("getEpisodeLinks", url);
try {
// Note: Cookies को URL के आधार पर अपडेट करने की आवश्यकता हो सकती है
const res = await axios.get(url, {
let res = await axios.get(url, {
headers: {
...headers,
// Cloudflare/Bot protection के लिए Hardcoded cookie यहाँ आवश्यक हो सकता है
@@ -22,43 +22,115 @@ export const getEpisodes = async function ({
"ext_name=ojplmecpdpgccookcobabopnaifgidhf; cf_clearance=Zl2yiOCN3pzGUd0Bgs.VyBXniJooDbG2Tk1g7DEoRnw-1756381111-1.2.1.1-RVPZoWGCAygGNAHavrVR0YaqASWZlJyYff8A.oQfPB5qbcPrAVud42BzsSwcDgiKAP0gw5D92V3o8XWwLwDRNhyg3DuL1P8wh2K4BCVKxWvcy.iCCxczKtJ8QSUAsAQqsIzRWXk29N6X.kjxuOTYlfB2jrlq12TRDld_zTbsskNcTxaA.XQekUcpGLseYqELuvlNOQU568NZD6LiLn3ICyFThMFAx6mIcgXkxVAvnxU; xla=s4t",
},
});
const $ = cheerio.load(res.data);
const container = $(".entry-content,.entry-inner, .download-links-div");
// .unili-content,.code-block-1 जैसे अवांछित तत्वों को हटा दें
$(".unili-content,.code-block-1").remove();
const episodes: EpisodeLink[] = [];
// HubCloud Links को लक्षित करने के लिए:
// 1. Episode Title (h5) से शुरू करें
// 2. उसके बाद के downloads-btns-div में HubCloud बटन खोजें
container.find("h5").each((index, element) => {
const el = $(element);
const title = el.text().trim(); // e.g., "-:Episodes: 1:- (Grand Premiere)"
// HubCloud लिंक को विशिष्ट स्टाइल और टेक्स्ट से खोजें
// बटन सेलेक्टर: style="background: linear-gradient(135deg,#e629d0,#007bff);color: white;"
const hubCloudLink = el
.next(".downloads-btns-div")
.find(
'a[style*="background: linear-gradient(135deg,#e629d0,#007bff);"]'
)
.attr("href");
if (
res.data &&
res.data.includes("Please turn JavaScript on and reload the page.")
) {
const b1Match = res.data.match(/var b1=atob\(['"]([^'"]+)['"]\)/);
const a2Match = res.data.match(/_0x2aa8=\[['"]([^'"]+)['"]\]/);
const c3Match = res.data.match(/c3=toNumbers\(['"]([^'"]+)['"]\)/);
if (title && hubCloudLink) {
// टाइटल को साफ़ करें (e.g., सिर्फ़ Episode 1: Grand Premiere रखें)
const cleanedTitle = title.replace(/[-:]/g, "").trim();
episodes.push({
title: cleanedTitle,
link: hubCloudLink,
// यदि यह HubCloud/Streaming लिंक है, तो आप 'type' को यहाँ 'stream' भी सेट कर सकते हैं
if (b1Match && a2Match && c3Match) {
const unescapeHexStr = (str: string) =>
str.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) =>
String.fromCharCode(parseInt(hex, 16)),
);
const baseUrl = url.split("/").slice(0, 3).join("/");
const minJsRes = await axios.get(`${baseUrl}/min.js`, {
headers,
});
const b1Hex = atob(unescapeHexStr(b1Match[1]));
const a2Hex = atob(unescapeHexStr(a2Match[1]));
const c3Hex = unescapeHexStr(c3Match[1]);
const solver = new Function(
"c3Hex",
"a1Hex",
"b2Hex",
`
${minJsRes.data}
function toNumbers(d){var e=[];d.replace(/(..)/g,function(d){e.push(parseInt(d,16))});return e}
function toHex(){for(var d=[],d=1==arguments.length&&arguments[0].constructor==Array?arguments[0]:arguments,e='',f=0;f<d.length;f++)e+=(16>d[f]?'0':'')+d[f].toString(16);return e.toLowerCase()}
return toHex(slowAES.decrypt(toNumbers(c3Hex), 2, toNumbers(a1Hex), toNumbers(b2Hex)));
`,
);
const decrypted = solver(c3Hex, a2Hex, b1Hex);
const newCookie = `Antiddos-systems-DH=${decrypted}`;
res = await axios.get(url, {
headers: { ...headers, Cookie: newCookie },
});
}
}
const $ = cheerio.load(res.data);
const container = $(".entry-content,.entry-inner, .download-links-div");
// .unili-content,.code-block-1 जैसे अवांछित तत्वों को हटा दें
$(".unili-content,.code-block-1").remove();
const episodes: EpisodeLink[] = [];
// The site changed its layout. Links are now inside <p> tags following <h3> or <h4> tags with quality info.
// We can also just look for the <a> tags directly and find their preceding quality headers.
const hElements = container.find("h3, h4, h5, p");
hElements.each((index, element) => {
const el = $(element);
const title = el.text().trim();
// The download buttons are usually in the next <p> tag
const downloadButtons = el.nextAll().find("a").first();
const link = downloadButtons.attr("href");
if (
title &&
link &&
title.match(/Episode|Ep|E\d+/i) &&
title.length < 150
) {
// Clean up the title
const cleanedTitle = title.replace(/[-:]/g, "").trim();
// Deduplicate
if (!episodes.some((e) => e.link === link)) {
episodes.push({
title: cleanedTitle,
link: link,
});
}
}
});
// Fallback: if no episodes found by heading, just grab all mdrive/fastdl links
if (episodes.length === 0) {
$("a").each((i, el) => {
const href = $(el).attr("href");
if (
href &&
(href.includes("mdrive") ||
href.includes("fastdl") ||
href.includes("filebee") ||
href.includes("gdflix"))
) {
const title =
$(el).parent().prev().text().trim() ||
$(el).text().trim() ||
`Episode ${i + 1}`;
if (!episodes.some((e) => e.link === href)) {
episodes.push({
title: title.replace(/[-:]/g, "").trim(),
link: href,
});
}
}
});
}
// console.log(episodes);
return episodes;
} catch (err) {
@@ -66,4 +138,4 @@ export const getEpisodes = async function ({
// console.error(err);
return [];
}
};
};
+69 -27
View File
@@ -43,10 +43,53 @@ export const getMeta = async function ({
};
try {
const response = await axios.get(url, {
let response = await axios.get(url, {
headers: { ...headers, Referer: baseUrl },
});
if (
response.data &&
response.data.includes("Please turn JavaScript on and reload the page.")
) {
const b1Match = response.data.match(/var b1=atob\(['"]([^'"]+)['"]\)/);
const a2Match = response.data.match(/_0x2aa8=\[['"]([^'"]+)['"]\]/);
const c3Match = response.data.match(/c3=toNumbers\(['"]([^'"]+)['"]\)/);
if (b1Match && a2Match && c3Match) {
const unescapeHexStr = (str: string) =>
str.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) =>
String.fromCharCode(parseInt(hex, 16)),
);
const minJsRes = await axios.get(`${baseUrl}/min.js`, {
headers: { ...headers, Referer: baseUrl },
});
const b1Hex = atob(unescapeHexStr(b1Match[1]));
const a2Hex = atob(unescapeHexStr(a2Match[1]));
const c3Hex = unescapeHexStr(c3Match[1]);
const solver = new Function(
"c3Hex",
"a1Hex",
"b2Hex",
`
${minJsRes.data}
function toNumbers(d){var e=[];d.replace(/(..)/g,function(d){e.push(parseInt(d,16))});return e}
function toHex(){for(var d=[],d=1==arguments.length&&arguments[0].constructor==Array?arguments[0]:arguments,e='',f=0;f<d.length;f++)e+=(16>d[f]?'0':'')+d[f].toString(16);return e.toLowerCase()}
return toHex(slowAES.decrypt(toNumbers(c3Hex), 2, toNumbers(a1Hex), toNumbers(b2Hex)));
`,
);
const decrypted = solver(c3Hex, a2Hex, b1Hex);
const newCookie = `Antiddos-systems-DH=${decrypted}`;
response = await axios.get(url, {
headers: { ...headers, Referer: baseUrl, Cookie: newCookie },
});
}
}
const $ = cheerio.load(response.data);
const infoContainer = $(".entry-content, .post-inner").length
? $(".entry-content, .post-inner")
@@ -100,29 +143,23 @@ export const getMeta = async function ({
image = "";
result.image = image;
// --- Synopsis ---
result.synopsis =
$("h3.movie-title")
.filter((i, el) => $(el).text().includes("Storyline"))
.next("p")
.text()
.trim() ||
infoContainer.find("p").first().text().trim() ||
"";
// --- LinkList extraction ---
const links: Link[] = [];
const h4Elements = $(".download-links-div").find("> h4");
h4Elements.each((index, element) => {
// The site changed its layout. Links are now inside <p> tags following <h3> or <h4> tags with quality info.
// We can also just look for the <a> tags directly and find their preceding quality headers.
const hElements = infoContainer.find("h3, h4, p");
hElements.each((index, element) => {
const el = $(element);
const titleText = el.text().trim();
const qualityMatch = titleText.match(/\d+p\b/)?.[0];
const fullTitle = titleText;
const downloadButtons = el.next(".downloads-btns-div").find("a");
// The download buttons are usually in the next <p> tag
const downloadButtons = el.nextAll().find("a").first();
if (downloadButtons.length && qualityMatch) {
if (downloadButtons.length && qualityMatch && titleText.length < 350) {
if (result.type === "series") {
links.push({
title: fullTitle,
@@ -134,17 +171,14 @@ export const getMeta = async function ({
// Movie: collect all direct download buttons
const directLinks: Link["directLinks"] = [];
downloadButtons.each((i, btn) => {
const btnEl = $(btn);
const link = btnEl.attr("href");
if (link) {
directLinks.push({
title: btnEl.text().trim() || "Download",
link,
type: "movie", // literal type
});
}
});
const link = downloadButtons.attr("href");
if (link) {
directLinks.push({
title: downloadButtons.text().trim() || "Download",
link,
type: "movie", // literal type
});
}
if (directLinks.length) {
links.push({
@@ -158,7 +192,15 @@ export const getMeta = async function ({
}
});
result.linkList = links;
// Deduplicate links by href
const uniqueLinks = new Map<string, Link>();
links.forEach((link) => {
const href = link.episodesLink || link.directLinks?.[0]?.link;
if (href && !uniqueLinks.has(href)) {
uniqueLinks.set(href, link);
}
});
result.linkList = Array.from(uniqueLinks.values());
return result;
} catch (err) {
console.log("getMeta error:", err);
+80 -11
View File
@@ -1,14 +1,23 @@
import { Post, ProviderContext } from "../types";
const defaultHeaders = {
Referer: "https://www.google.com",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
"(KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
Pragma: "no-cache",
"Cache-Control": "no-cache",
accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-language": "en-US,en;q=0.9",
"cache-control": "no-cache",
pragma: "no-cache",
priority: "u=0, i",
"sec-ch-ua":
'"Microsoft Edge";v="147", "Not.A/Brand";v="8", "Chromium";v="147"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "same-origin",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
cookie: "Antiddos-systems-DH=395a53ac840ad21dff778291a3ffae36",
Referer: "https://movies4u.vg/category/web-series/",
};
// --- Normal catalog posts ---
@@ -81,7 +90,60 @@ async function fetchPosts({
}
const { axios, cheerio } = providerContext;
const res = await axios.get(url, { headers: defaultHeaders, signal });
let res = await axios.get(url, {
headers: defaultHeaders,
signal,
maxRedirects: 5,
});
// Anti-DDoS-Guard check
if (
res.data &&
res.data.includes("Please turn JavaScript on and reload the page.")
) {
const b1Match = res.data.match(/var b1=atob\(['"]([^'"]+)['"]\)/);
const a2Match = res.data.match(/_0x2aa8=\[['"]([^'"]+)['"]\]/);
const c3Match = res.data.match(/c3=toNumbers\(['"]([^'"]+)['"]\)/);
if (b1Match && a2Match && c3Match) {
const unescapeHexStr = (str: string) =>
str.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) =>
String.fromCharCode(parseInt(hex, 16)),
);
// Fetch the min.js payload from the provider to safely execute slowAES
const minJsRes = await axios.get(`${baseUrl}/min.js`, {
headers: defaultHeaders,
signal,
});
const b1Hex = atob(unescapeHexStr(b1Match[1]));
const a2Hex = atob(unescapeHexStr(a2Match[1]));
const c3Hex = unescapeHexStr(c3Match[1]);
// Evaluate the decryption without needing crypto or buffers
const solver = new Function(
"c3Hex",
"a1Hex",
"b2Hex",
`
${minJsRes.data}
function toNumbers(d){var e=[];d.replace(/(..)/g,function(d){e.push(parseInt(d,16))});return e}
function toHex(){for(var d=[],d=1==arguments.length&&arguments[0].constructor==Array?arguments[0]:arguments,e='',f=0;f<d.length;f++)e+=(16>d[f]?'0':'')+d[f].toString(16);return e.toLowerCase()}
return toHex(slowAES.decrypt(toNumbers(c3Hex), 2, toNumbers(a1Hex), toNumbers(b2Hex)));
`,
);
const decrypted = solver(c3Hex, a2Hex, b1Hex);
const newCookie = `Antiddos-systems-DH=${decrypted}`;
res = await axios.get(url, {
headers: { ...defaultHeaders, Cookie: newCookie },
signal,
maxRedirects: 5,
});
}
}
const $ = cheerio.load(res.data || "");
const resolveUrl = (href: string) =>
@@ -100,10 +162,13 @@ async function fetchPosts({
".thumbnail",
".latest-movies",
".movie-item",
".entry-card",
].join(",");
console.log("Fetching posts from URL:", url); // Debug log
$(POST_SELECTORS).each((_, el) => {
const card = $(el);
console.log("Processing card:", card.text().trim().slice(0, 50)); // Debug log
let link = card.find("a[href]").first().attr("href") || "";
if (!link) return;
link = resolveUrl(link);
@@ -114,9 +179,13 @@ async function fetchPosts({
card.find("a[title]").first().attr("title")?.trim() ||
card.text().trim();
title = title
.replace(
/(?:480p|720p|1080p|4k|HDTC|HDRip|BluRay|LiNE|Full Movie).*$/i,
"",
)
.replace(/\[.*?\]/g, "")
.replace(/\(.+?\)/g, "")
.replace(/\s{2,}/g, " ")
.replace(/\s*[|\-]\s*$/, "")
.trim();
if (!title) return;
@@ -135,7 +204,7 @@ async function fetchPosts({
} catch (err) {
console.error(
"HDMovie2 fetchPosts error:",
err instanceof Error ? err.message : String(err)
err instanceof Error ? err.message : String(err),
);
return [];
}
+44 -1
View File
@@ -44,7 +44,50 @@ export async function getStream({
// console.log('dotlinkText', dotlinkText);
const vlink = dotlinkText.match(/<a\s+href="([^"]*cloud\.[^"]*)"/i) || [];
// console.log('vLink', vlink[1]);
link = vlink[1];
if (vlink[1]) {
link = vlink[1];
} else {
// Try to find hubcloud or gdflix links directly
const $ = cheerio.load(dotlinkText);
const directLink = $("a")
.filter((i, el) => {
const href = $(el).attr("href") || "";
return (
href.includes("hubcloud") ||
href.includes("gdflix") ||
href.includes("filebee") ||
href.includes("fastdl")
);
})
.first()
.attr("href");
if (directLink) {
link = directLink;
}
}
// If it's a fastdl link, extract the redirect URL
if (link.includes("fastdl.zip")) {
try {
const fastdlRes = await axios.get(link, { headers });
const reurlMatch = fastdlRes.data.match(/var reurl = "([^"]+)";/);
if (reurlMatch && reurlMatch[1]) {
const actualLink = reurlMatch[1].replace(
"https://fastdl.zip/dl.php?link=",
"",
);
streamLinks.push({
server: "fastdl",
link: actualLink,
type: "mkv",
});
return streamLinks;
}
} catch (error) {
console.log("fastdl error: ", error);
}
}
// filepress link
try {