From 47d2886fbef729bd4c38476e68a695d76853c0a9 Mon Sep 17 00:00:00 2001
From: Inside4ndroid
Date: Fri, 29 May 2026 20:29:01 +0100
Subject: [PATCH] chore: bump to 1.1.1, remove MoviesMod & VidZee providers,
fix LordFlix domain, fix Docker proxy crash
---
CHANGELOG.md | 12 +
Dockerfile | 2 +
README.md | 10 +-
package-lock.json | 4 +-
package.json | 2 +-
providers/VidZee.js | 201 -------
providers/lordflix.js | 4 +-
providers/moviesmod.js | 1205 ----------------------------------------
providers/registry.js | 2 -
public/config.html | 2 +-
public/index.js | 9 +-
utils/user-config.json | 16 +-
12 files changed, 31 insertions(+), 1438 deletions(-)
delete mode 100644 providers/VidZee.js
delete mode 100644 providers/moviesmod.js
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 565e457..1680020 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
All notable changes to this project will be documented in this file.
+## [1.1.1] - 2026-05-29
+
+### Removed
+- **MoviesMod provider**: Permanently removed — `moviesmod.farm` returns HTTP 403 with Cloudflare managed challenge (`cf-mitigated: challenge`). No server-side workaround exists without a browser runtime.
+- **VidZee provider**: Permanently removed — hardcoded AES-256-GCM salt is no longer valid against VidZee's current API key blob, causing all decryption to fail.
+
+### Fixed
+- **LordFlix provider**: Updated upstream domain (`network.hasta-la-vista.site` → `snowhouse.lordflix.club`) and refreshed server list (10 servers).
+- **Docker startup crash**: Added missing `COPY proxy ./proxy` to both build and runtime stages in `Dockerfile` — `apiServer.js` unconditionally requires `./proxy/proxyServer` at startup.
+
+---
+
## [1.1.0] - 2026-05-16
## Dependency Security
diff --git a/Dockerfile b/Dockerfile
index d7fa6d7..bb00b68 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,6 +12,7 @@ RUN npm install --omit=dev
# Copy only required source (avoid sending screenshots, node_modules already installed)
COPY apiServer.js ./
COPY providers ./providers
+COPY proxy ./proxy
COPY public ./public
COPY utils ./utils
COPY README.md ./
@@ -33,6 +34,7 @@ COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/apiServer.js ./
COPY --from=build /app/public ./public
COPY --from=build /app/providers ./providers
+COPY --from=build /app/proxy ./proxy
COPY --from=build /app/utils ./utils
COPY --from=build /app/package.json ./
COPY --from=build /app/README.md ./
diff --git a/README.md b/README.md
index 6ec1940..264958f 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
-
+
@@ -34,7 +34,7 @@
## ✨ Features
- **Multi‑TMDB Key Rotation** – Supply multiple API keys; one is chosen randomly per request.
-- **Provider Aggregation** – Pluggable providers (Showbox/FebBox, 4KHDHub, MoviesMod, VidZee, VixSrc, Videasy, Vidlink, LordFlix, NoTorrent, DahmerMovies) with per‑provider enable toggles + default selection.
+- **Provider Aggregation** – Pluggable providers (Showbox/FebBox, 4KHDHub, VixSrc, Videasy, Vidlink, LordFlix, NoTorrent, DahmerMovies) with per‑provider enable toggles + default selection.
- **🔥 Plugin System** – Drop new provider files in `providers/` and add its exported function to the registry map (`providers/registry.js` → `providerFunctionMap`).
- **Dynamic Filtering** – Minimum quality presets, custom JSON quality map, codec exclusion rules (presets + JSON).
- **Runtime Overrides UI** – Fully interactive web admin at `/` (login protected) writing to `utils/user-config.json`.
@@ -199,7 +199,7 @@ Saving in the UI writes only changed keys. Setting a field to empty removes the
**Override File:** `utils/user-config.json`
```json
{
- "defaultProviders": ["4khdhub","moviesmod"],
+ "defaultProviders": ["4khdhub"],
"tmdbApiKeys": ["KEY_A","KEY_B"],
"enable4khdhubProvider": true
}
@@ -227,8 +227,6 @@ The API supports a plugin system. Drop a new provider file in the `providers/` f
### Current Built-in Providers
- `showbox` - Showbox/FebBox streams (requires FebBox JWT cookie)
- `4khdhub` - 4KHDHub streams
-- `moviesmod` - MoviesMod streams
-- `vidzee` - VidZee streams
- `vixsrc` - VixSrc streams
- `videasy` - Videasy streams (10 servers via enc-dec.app)
- `vidlink` - Vidlink streams (via enc-dec.app)
@@ -246,8 +244,6 @@ The API supports a plugin system. Drop a new provider file in the `providers/` f
const providerFunctionMap = {
'Showbox.js': 'getStreamsFromTmdbId',
'4khdhub.js': 'get4KHDHubStreams',
- 'moviesmod.js': 'getMoviesModStreams',
- 'VidZee.js': 'getVidZeeStreams',
'vixsrc.js': 'getVixsrcStreams',
'videasy.js': 'getVideasyStreams',
'vidlink.js': 'getVidlinkStreams',
diff --git a/package-lock.json b/package-lock.json
index 0a8d4fd..10969a9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "tmdb-embed-api",
- "version": "1.1.0",
+ "version": "1.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tmdb-embed-api",
- "version": "1.1.0",
+ "version": "1.1.1",
"dependencies": {
"aromanize": "^0.1.5",
"axios": "^1.9.0",
diff --git a/package.json b/package.json
index 06e4a08..abbf3df 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "tmdb-embed-api",
- "version": "1.1.0",
+ "version": "1.1.1",
"description": "Standalone streaming embed source aggregation API (TMDB ID in, streams out)",
"main": "apiServer.js",
"scripts": {
diff --git a/providers/VidZee.js b/providers/VidZee.js
deleted file mode 100644
index 5909df7..0000000
--- a/providers/VidZee.js
+++ /dev/null
@@ -1,201 +0,0 @@
-const crypto = require('crypto');
-const axios = require('axios');
-
-const BASE_URL = 'https://core.vidzee.wtf';
-const PLAYER_URL = 'https://player.vidzee.wtf';
-const VIDZEE_HEADERS = {
- 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.7051.98 Safari/537.36',
- 'Accept': 'application/json, text/javascript, */*; q=0.01',
- 'Accept-Language': 'en-US,en;q=0.9',
- 'Referer': PLAYER_URL,
- 'Origin': PLAYER_URL
-};
-
-// Pad/truncate key string to 32 bytes (UTF-8, mirrors CryptoJS behaviour)
-function getKeyBytes(key) {
- const encoded = Buffer.from(key, 'utf8');
- const result = Buffer.alloc(32);
- encoded.copy(result, 0, 0, Math.min(encoded.length, 32));
- return result;
-}
-
-// AES-256-CBC decrypt a VidZee stream link.
-// encryptedData is outer-base64 of "ivBase64:cipherBase64"
-async function decryptLink(encryptedData, decryptionKey) {
- try {
- if (!encryptedData || !decryptionKey) return '';
-
- const decoded = Buffer.from(encryptedData, 'base64').toString('utf8');
- const colonIdx = decoded.indexOf(':');
- if (colonIdx === -1) return '';
-
- const ivBase64 = decoded.slice(0, colonIdx);
- const cipherBase64 = decoded.slice(colonIdx + 1);
- if (!ivBase64 || !cipherBase64) return '';
-
- const iv = Buffer.from(ivBase64, 'base64');
- const cipherBytes = Buffer.from(cipherBase64, 'base64');
- const keyBytes = getKeyBytes(decryptionKey);
-
- const decipher = crypto.createDecipheriv('aes-256-cbc', keyBytes, iv);
- const decrypted = Buffer.concat([decipher.update(cipherBytes), decipher.final()]);
- return decrypted.toString('utf8');
- } catch {
- return '';
- }
-}
-
-// Derive the real AES-CBC decryption key from VidZee's API-key blob via AES-GCM.
-// Mirrors the TypeScript deriveKey() in the reference implementation.
-async function deriveKey(e) {
- try {
- if (!e) return '';
-
- const t = Buffer.from(e.replace(/\s+/g, ''), 'base64');
- if (t.length <= 28) return '';
-
- const n = t.slice(0, 12); // AES-GCM IV
- const r = t.slice(12, 28);
- const a = t.slice(28);
-
- // i = a || r (reference: i.set(a, 0); i.set(r, a.length))
- const i = Buffer.concat([a, r]);
-
- // AES-256-GCM key = SHA-256 of fixed salt string
- const l = crypto.createHash('sha256')
- .update('4f2a9c7d1e8b3a6f0d5c2e9a7b1f4d8c', 'utf8')
- .digest();
-
- // WebCrypto passes ciphertext+tag as one buffer; tag is last 16 bytes
- const ciphertext = i.slice(0, i.length - 16);
- const authTag = i.slice(i.length - 16);
-
- const decipher = crypto.createDecipheriv('aes-256-gcm', l, n);
- decipher.setAuthTag(authTag);
- const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
- return decrypted.toString('utf8');
- } catch {
- return '';
- }
-}
-
-// Fetch and derive the current decryption key from VidZee's API
-async function fetchDecryptionKey() {
- try {
- const response = await axios.get(`${BASE_URL}/api-key`, {
- headers: VIDZEE_HEADERS,
- responseType: 'text',
- timeout: 10000
- });
- if (response.status === 200 && response.data) {
- return await deriveKey(response.data);
- }
- return null;
- } catch {
- return null;
- }
-}
-
-const getVidZeeStreams = async (tmdbId, mediaType, seasonNum, episodeNum) => {
- if (!tmdbId) {
- console.error('[VidZee] Error: TMDB ID (tmdbId) is required.');
- return [];
- }
-
- if (!mediaType || (mediaType !== 'movie' && mediaType !== 'tv')) {
- console.error('[VidZee] Error: mediaType is required and must be either "movie" or "tv".');
- return [];
- }
-
- if (mediaType === 'tv') {
- if (!seasonNum) {
- console.error('[VidZee] Error: Season (seasonNum) is required for TV shows.');
- return [];
- }
- if (!episodeNum) {
- console.error('[VidZee] Error: Episode (episodeNum) is required for TV shows.');
- return [];
- }
- }
-
- // Fetch and derive the decryption key before hitting servers
- const decKey = await fetchDecryptionKey();
- if (!decKey) {
- console.error('[VidZee] Failed to fetch/derive decryption key.');
- return [];
- }
- console.log('[VidZee] Decryption key derived successfully.');
-
- // Servers 0–13 (matches reference implementation)
- const servers = Array.from({ length: 14 }, (_, i) => i);
-
- const streamPromises = servers.map(async (sr) => {
- let apiUrl = `${PLAYER_URL}/api/server?id=${tmdbId}&sr=${sr}`;
- if (mediaType === 'tv') {
- apiUrl += `&ss=${seasonNum}&ep=${episodeNum}`;
- }
-
- try {
- const response = await axios.get(apiUrl, {
- headers: VIDZEE_HEADERS,
- timeout: 7000
- });
-
- const responseData = response.data;
- if (!responseData || typeof responseData !== 'object') return [];
-
- let apiSources = [];
- if (responseData.url && Array.isArray(responseData.url)) {
- apiSources = responseData.url;
- } else if (responseData.link && typeof responseData.link === 'string') {
- apiSources = [responseData];
- }
-
- if (apiSources.length === 0) return [];
-
- const streams = await Promise.all(apiSources.map(async (sourceItem) => {
- const label = sourceItem.name || sourceItem.type || 'VidZee';
- let quality = String(label).match(/^\d+$/) ? `${label}p` : label;
- if (!/\d{3,4}p/.test(quality)) quality = '720p';
- const language = sourceItem.language || sourceItem.lang || 'Unknown';
-
- let rawLink = sourceItem.link;
- if (rawLink && !/^https?:\/\//i.test(rawLink)) {
- const decoded = await decryptLink(rawLink, decKey);
- if (decoded && /^https?:\/\//i.test(decoded)) {
- rawLink = decoded;
- } else {
- console.log(`[VidZee S${sr}] Decryption yielded non-URL, skipping.`);
- return null;
- }
- }
-
- if (!rawLink || !/^https?:\/\//i.test(rawLink)) return null;
-
- return {
- name: `VidZee Server${sr} - ${quality} - ${language}`,
- title: `VidZee Server${sr} - ${quality} - ${language}`,
- url: rawLink,
- quality,
- provider: 'VidZee',
- headers: { 'Referer': `${BASE_URL}/` }
- };
- }));
-
- const valid = streams.filter(Boolean);
- if (valid.length > 0) {
- console.log(`[VidZee S${sr}] ${valid.length} stream(s) extracted.`);
- }
- return valid;
- } catch (error) {
- console.error(`[VidZee S${sr}] Error: ${error.message}`);
- return [];
- }
- });
-
- const allStreams = (await Promise.all(streamPromises)).flat();
- console.log(`[VidZee] Total streams: ${allStreams.length}`);
- return allStreams;
-};
-
-module.exports = { getVidZeeStreams };
diff --git a/providers/lordflix.js b/providers/lordflix.js
index 6b92f27..8012255 100644
--- a/providers/lordflix.js
+++ b/providers/lordflix.js
@@ -8,9 +8,9 @@ const LORDFLIX_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
};
-const LORDFLIX_API = 'https://network.hasta-la-vista.site';
+const LORDFLIX_API = 'https://snowhouse.lordflix.club';
const MULTI_DECRYPT_API = 'https://enc-dec.app/api';
-const SERVERS = ['Berlin', 'Tokyo', 'Bogota', 'Oslo', 'Luna', 'LordFlix', 'Sakura', 'Rio', 'Ativa'];
+const SERVERS = ['Berlin', 'Marseille', 'Backrooms', 'Phoenix', 'Oslo', 'Luna', 'Sakura', 'Rio', 'Ativa', 'Moscow'];
// Matches the reference project's encodeQuote: encodeURIComponent with + encoded as %20
function encodeQuote(str) {
diff --git a/providers/moviesmod.js b/providers/moviesmod.js
deleted file mode 100644
index d3d054e..0000000
--- a/providers/moviesmod.js
+++ /dev/null
@@ -1,1205 +0,0 @@
-/**
- * MoviesMod provider (standalone API)
- * Supports both movies and TV series
- */
-
-const axios = require('axios');
-const cheerio = require('cheerio');
-const FormData = require('form-data');
-const { CookieJar } = require('tough-cookie');
-const { URLSearchParams, URL } = require('url');
-const fs = require('fs').promises;
-const path = require('path');
-const { findBestMatch } = require('string-similarity');
-
-// Dynamic import for axios-cookiejar-support
-let axiosCookieJarSupport = null;
-const getAxiosCookieJarSupport = async () => {
- if (!axiosCookieJarSupport) {
- axiosCookieJarSupport = await import('axios-cookiejar-support');
- }
- return axiosCookieJarSupport;
-};
-
-function escapeRegExp(string) {
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
-}
-
-
-// --- Domain Fetching ---
-let moviesModDomain = 'https://moviesmod.chat'; // Fallback domain
-let domainCacheTimestamp = 0;
-const DOMAIN_CACHE_TTL = 4 * 60 * 60 * 1000; // 4 hours
-
-async function getMoviesModDomain() {
- const now = Date.now();
- if (now - domainCacheTimestamp < DOMAIN_CACHE_TTL) {
- return moviesModDomain;
- }
-
- try {
- console.log('[MoviesMod] Fetching latest domain...');
- const response = await makeRequest('https://raw.githubusercontent.com/phisher98/TVVVV/refs/heads/main/domains.json', { timeout: 10000 });
- if (response.data && response.data.moviesmod) {
- moviesModDomain = response.data.moviesmod;
- domainCacheTimestamp = now;
- console.log(`[MoviesMod] Updated domain to: ${moviesModDomain}`);
- } else {
- console.warn('[MoviesMod] Domain JSON fetched, but "moviesmod" key was not found. Using fallback.');
- }
- } catch (error) {
- console.error(`[MoviesMod] Failed to fetch latest domain, using fallback. Error: ${error.message}`);
- }
- return moviesModDomain;
-}
-
-// --- Caching Configuration ---
-const CACHE_ENABLED = process.env.DISABLE_CACHE !== 'true';
-console.log(`[MoviesMod Cache] Internal cache is ${CACHE_ENABLED ? 'enabled' : 'disabled'}.`);
-const CACHE_DIR = process.env.VERCEL ? path.join('/tmp', '.moviesmod_cache') : path.join(__dirname, '.cache', 'moviesmod');
-
-// Caching layer simplified: placeholders only (extend for FS persistence if needed)
-
-// --- Caching Helper Functions ---
-const ensureCacheDir = async () => {
- if (!CACHE_ENABLED) return;
- try {
- await fs.mkdir(CACHE_DIR, { recursive: true });
- } catch (error) {
- if (error.code !== 'EEXIST') {
- console.error(`[MoviesMod Cache] Error creating cache directory: ${error.message}`);
- }
- }
-};
-
-const getFromCache = async (key) => {
- if (!CACHE_ENABLED) return null;
- return null; // no-op
-};
-
-const saveToCache = async (key, data) => {
- if (!CACHE_ENABLED) return;
- // no-op (placeholder for future FS caching)
-};
-
-// Initialize cache directory on startup
-ensureCacheDir();
-
-// Request helper
-const makeRequest = async (url, options = {}) => {
- console.log(`[MoviesMod] Making direct request to: ${url}`);
- return axios.get(url, options);
-};
-
-// Helper function to create a session for SID resolution
-const createSession = async (jar) => {
- const { wrapper } = await getAxiosCookieJarSupport();
-
- const sessionConfig = {
- jar,
- headers: {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
- '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.5',
- 'Connection': 'keep-alive',
- 'Upgrade-Insecure-Requests': '1'
- }
- };
-
- const session = wrapper(axios.create(sessionConfig));
-
- // Helper function to extract cookies from jar for a given URL
- const getCookiesForUrl = async (url) => {
- try {
- const cookies = await jar.getCookies(url);
- return cookies.map(cookie => `${cookie.key}=${cookie.value}`).join('; ');
- } catch (error) {
- console.error(`[MoviesMod] Error getting cookies for ${url}: ${error.message}`);
- return '';
- }
- };
-
-
- return session;
-};
-
-// Helper function to extract quality from text
-function extractQuality(text) {
- if (!text) return 'Unknown';
-
- const qualityMatch = text.match(/(480p|720p|1080p|2160p|4k)/i);
- if (qualityMatch) {
- return qualityMatch[1];
- }
-
- // Try to extract from full text
- const cleanMatch = text.match(/(480p|720p|1080p|2160p|4k)[^)]*\)/i);
- if (cleanMatch) {
- return cleanMatch[0];
- }
-
- return 'Unknown';
-}
-
-function parseQualityForSort(qualityString) {
- if (!qualityString) return 0;
- const match = qualityString.match(/(\d{3,4})p/i);
- return match ? parseInt(match[1], 10) : 0;
-}
-
-function getTechDetails(qualityString) {
- if (!qualityString) return [];
- const details = [];
- const lowerText = qualityString.toLowerCase();
- if (lowerText.includes('10bit')) details.push('10-bit');
- if (lowerText.includes('hevc') || lowerText.includes('x265')) details.push('HEVC');
- if (lowerText.includes('hdr')) details.push('HDR');
- return details;
-}
-
-// Search for content on MoviesMod
-async function searchMoviesMod(query) {
- try {
- const baseUrl = await getMoviesModDomain();
- const searchUrl = `${baseUrl}/?s=${encodeURIComponent(query)}`;
- const { data } = await makeRequest(searchUrl);
- const $ = cheerio.load(data);
-
- const results = [];
- $('.latestPost').each((i, element) => {
- const linkElement = $(element).find('a');
- const title = linkElement.attr('title');
- const url = linkElement.attr('href');
- if (title && url) {
- results.push({ title, url });
- }
- });
-
- return results;
- } catch (error) {
- console.error(`[MoviesMod] Error searching: ${error.message}`);
- return [];
- }
-}
-
-// Extract download links from a movie/series page
-async function extractDownloadLinks(moviePageUrl) {
- try {
- const { data } = await makeRequest(moviePageUrl);
- const $ = cheerio.load(data);
- const links = [];
- const contentBox = $('.thecontent');
-
- // Get all relevant headers (for movies and TV shows) in document order
- const headers = contentBox.find('h3:contains("Season"), h4');
-
- headers.each((i, el) => {
- const header = $(el);
- const headerText = header.text().trim();
-
- // Define the content block for this header
- const blockContent = header.nextUntil('h3, h4');
-
- if (header.is('h3') && headerText.toLowerCase().includes('season')) {
- // TV Show Logic
- const linkElements = blockContent.find('a.maxbutton-episode-links, a.maxbutton-batch-zip, a.maxbutton-download-links, a[href*="links.modpro.blog"], a[href*="posts.modpro.blog"]');
- linkElements.each((j, linkEl) => {
- const buttonText = $(linkEl).text().trim();
- const linkUrl = $(linkEl).attr('href');
- if (linkUrl && !buttonText.toLowerCase().includes('batch')) {
- links.push({
- quality: `${headerText} - ${buttonText}`,
- url: linkUrl
- });
- }
- });
- } else if (header.is('h4')) {
- // Movie Logic - site changed from modrefer.in to links.modpro.blog
- const linkElement = blockContent.find('a.maxbutton-download-links, a[href*="links.modpro.blog"], a[href*="posts.modpro.blog"], a[href*="modrefer.in"]').first();
- if (linkElement.length > 0) {
- const link = linkElement.attr('href');
- const cleanQuality = extractQuality(headerText);
- if (link && cleanQuality && cleanQuality !== 'Unknown') {
- links.push({
- quality: cleanQuality,
- url: link
- });
- }
- }
- }
- });
-
- return links;
- } catch (error) {
- console.error(`[MoviesMod] Error extracting download links: ${error.message}`);
- return [];
- }
-}
-
-// Resolve intermediate links (dramadrip, episodes.modpro.blog, modrefer.in)
-async function resolveIntermediateLink(initialUrl, refererUrl, quality) {
- try {
- const urlObject = new URL(initialUrl);
-
- if (urlObject.hostname.includes('links.modpro.blog') || urlObject.hostname.includes('posts.modpro.blog')) {
- const { data } = await makeRequest(initialUrl, { headers: { 'Referer': refererUrl } });
- const $ = cheerio.load(data);
- const finalLinks = [];
-
- // Primary: scoped to .entry-content
- $('.entry-content a[href*="driveseed.org"], .entry-content a[href*="cloud.unblockedgames.world"], .entry-content a[href*="tech.creativeexpressionsblog.com"], .entry-content a[href*="tech.examzculture.in"], .entry-content a[href*="tech.unblockedgames.world"], .entry-content a[href*="tech.examdegree.site"]').each((i, el) => {
- const link = $(el).attr('href');
- const text = $(el).text().trim();
- if (link && text && !text.toLowerCase().includes('batch')) {
- finalLinks.push({ server: text.replace(/\s+/g, ' '), url: link });
- }
- });
-
- // Fallback: page-wide search
- if (finalLinks.length === 0) {
- $('a[href*="driveseed.org"], a[href*="cloud.unblockedgames.world"], a[href*="tech.creativeexpressionsblog.com"], a[href*="tech.examzculture.in"], a[href*="tech.unblockedgames.world"], a[href*="tech.examdegree.site"]').each((i, el) => {
- const link = $(el).attr('href');
- const text = $(el).text().trim();
- if (link && text && !text.toLowerCase().includes('batch')) {
- finalLinks.push({ server: text.replace(/\s+/g, ' ') || 'Download Link', url: link });
- }
- });
- }
-
- console.log(`[MoviesMod] Found ${finalLinks.length} links from ${urlObject.hostname}`);
- return finalLinks;
-
- } else if (urlObject.hostname.includes('dramadrip.com')) {
- const { data: dramaData } = await makeRequest(initialUrl, { headers: { 'Referer': refererUrl } });
- const $$ = cheerio.load(dramaData);
-
- let episodePageLink = null;
- const seasonMatch = quality.match(/Season \d+/i);
- // Extract the specific quality details, e.g., "1080p x264"
- const specificQualityMatch = quality.match(/(480p|720p|1080p|2160p|4k)[ \w\d-]*/i);
-
- if (seasonMatch && specificQualityMatch) {
- const seasonIdentifier = seasonMatch[0].toLowerCase();
- // Clean up the identifier to get only the essential parts
- const specificQualityIdentifier = specificQualityMatch[0].toLowerCase().replace(/msubs.*/i, '').replace(/esubs.*/i, '').replace(/\{.*/, '').trim();
- const qualityParts = specificQualityIdentifier.split(/\s+/); // -> ['1080p', 'x264']
-
- $$('a[href*="episodes.modpro.blog"], a[href*="cinematickit.org"]').each((i, el) => {
- const link = $$(el);
- const linkText = link.text().trim().toLowerCase();
- const seasonHeader = link.closest('.wp-block-buttons').prevAll('h2.wp-block-heading').first().text().trim().toLowerCase();
-
- const seasonIsMatch = seasonHeader.includes(seasonIdentifier);
- // Ensure that the link text contains all parts of our specific quality
- const allPartsMatch = qualityParts.every(part => linkText.includes(part));
-
- if (seasonIsMatch && allPartsMatch) {
- episodePageLink = link.attr('href');
- console.log(`[MoviesMod] Found specific match for "${quality}" -> "${link.text().trim()}": ${episodePageLink}`);
- return false; // Break loop, we found our specific link
- }
- });
- }
-
- if (!episodePageLink) {
- console.error(`[MoviesMod] Could not find a specific quality match on dramadrip page for: ${quality}`);
- return [];
- }
-
- // Pass quality to recursive call
- return await resolveIntermediateLink(episodePageLink, initialUrl, quality);
-
- } else if (urlObject.hostname.includes('cinematickit.org')) {
- // Handle cinematickit.org pages
- const { data } = await makeRequest(initialUrl, { headers: { 'Referer': refererUrl } });
- const $ = cheerio.load(data);
- const finalLinks = [];
-
- // Look for episode links on cinematickit.org
- $('a[href*="driveseed.org"]').each((i, el) => {
- const link = $(el).attr('href');
- const text = $(el).text().trim();
- if (link && text && !text.toLowerCase().includes('batch')) {
- finalLinks.push({
- server: text.replace(/\s+/g, ' '),
- url: link,
- });
- }
- });
-
- // If no driveseed links found, try other patterns
- if (finalLinks.length === 0) {
- $('a[href*="modrefer.in"], a[href*="dramadrip.com"]').each((i, el) => {
- const link = $(el).attr('href');
- const text = $(el).text().trim();
- if (link && text) {
- finalLinks.push({
- server: text.replace(/\s+/g, ' '),
- url: link,
- });
- }
- });
- }
-
- return finalLinks;
-
- } else if (urlObject.hostname.includes('episodes.modpro.blog')) {
- const { data } = await makeRequest(initialUrl, { headers: { 'Referer': refererUrl } });
- const $ = cheerio.load(data);
- const finalLinks = [];
-
- $('.entry-content a[href*="driveseed.org"], .entry-content a[href*="tech.unblockedgames.world"], .entry-content a[href*="tech.creativeexpressionsblog.com"], .entry-content a[href*="tech.examzculture.in"]').each((i, el) => {
- const link = $(el).attr('href');
- const text = $(el).text().trim();
- if (link && text && !text.toLowerCase().includes('batch')) {
- finalLinks.push({
- server: text.replace(/\s+/g, ' '),
- url: link,
- });
- }
- });
- return finalLinks;
-
- } else if (urlObject.hostname.includes('modrefer.in')) {
- const encodedUrl = urlObject.searchParams.get('url');
- if (!encodedUrl) {
- console.error('[MoviesMod] Could not find encoded URL in modrefer.in link.');
- return [];
- }
-
- const decodedUrl = Buffer.from(encodedUrl, 'base64').toString('utf8');
- const { data } = await makeRequest(decodedUrl, {
- headers: {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
- 'Referer': refererUrl,
- }
- });
-
- const $ = cheerio.load(data);
- const finalLinks = [];
-
- $('.timed-content-client_show_0_5_0 a').each((i, el) => {
- const link = $(el).attr('href');
- const text = $(el).text().trim();
- if (link) {
- finalLinks.push({
- server: text,
- url: link,
- });
- }
- });
- return finalLinks;
- } else {
- console.warn(`[MoviesMod] Unknown hostname: ${urlObject.hostname}`);
- return [];
- }
- } catch (error) {
- console.error(`[MoviesMod] Error resolving intermediate link: ${error.message}`);
- return [];
- }
-}
-
-// Function to resolve tech.unblockedgames.world links to driveleech URLs (adapted from UHDMovies)
-async function resolveTechUnblockedLink(sidUrl) {
- console.log(`[MoviesMod] Resolving SID link: ${sidUrl}`);
- const { origin } = new URL(sidUrl);
- const jar = new CookieJar();
-
- // Create session
- const session = await createSession(jar);
-
- try {
- // Step 0: Get the _wp_http value
- console.log(" [SID] Step 0: Fetching initial page...");
- const responseStep0 = await session.get(sidUrl);
- let $ = cheerio.load(responseStep0.data);
- const initialForm = $('#landing');
- const wp_http_step1 = initialForm.find('input[name="_wp_http"]').val();
- const action_url_step1 = initialForm.attr('action');
-
- if (!wp_http_step1 || !action_url_step1) {
- console.error(" [SID] Error: Could not find _wp_http in initial form.");
- return null;
- }
-
- // Step 1: POST to the first form's action URL
- console.log(" [SID] Step 1: Submitting initial form...");
- const step1Data = new URLSearchParams({ '_wp_http': wp_http_step1 });
- const responseStep1 = await session.post(action_url_step1, step1Data, {
- headers: { 'Referer': sidUrl, 'Content-Type': 'application/x-www-form-urlencoded' }
- });
-
- // Step 2: Parse verification page for second form
- console.log(" [SID] Step 2: Parsing verification page...");
- $ = cheerio.load(responseStep1.data);
- const verificationForm = $('#landing');
- const action_url_step2 = verificationForm.attr('action');
- const wp_http2 = verificationForm.find('input[name="_wp_http2"]').val();
- const token = verificationForm.find('input[name="token"]').val();
-
- if (!action_url_step2) {
- console.error(" [SID] Error: Could not find verification form.");
- return null;
- }
-
- // Step 3: POST to the verification URL
- console.log(" [SID] Step 3: Submitting verification...");
- const step2Data = new URLSearchParams({ '_wp_http2': wp_http2, 'token': token });
- const responseStep2 = await session.post(action_url_step2, step2Data, {
- headers: { 'Referer': responseStep1.request.res.responseUrl, 'Content-Type': 'application/x-www-form-urlencoded' }
- });
-
- // Step 4: Find dynamic cookie and link from JavaScript
- console.log(" [SID] Step 4: Parsing final page for JS data...");
- let finalLinkPath = null;
- let cookieName = null;
- let cookieValue = null;
-
- const scriptContent = responseStep2.data;
- const cookieMatch = scriptContent.match(/s_343\('([^']+)',\s*'([^']+)'/);
- const linkMatch = scriptContent.match(/c\.setAttribute\("href",\s*"([^"]+)"\)/);
-
- if (cookieMatch) {
- cookieName = cookieMatch[1].trim();
- cookieValue = cookieMatch[2].trim();
- }
- if (linkMatch) {
- finalLinkPath = linkMatch[1].trim();
- }
-
- if (!finalLinkPath || !cookieName || !cookieValue) {
- console.error(" [SID] Error: Could not extract dynamic cookie/link from JS.");
- return null;
- }
-
- const finalUrl = new URL(finalLinkPath, origin).href;
- console.log(` [SID] Dynamic link found: ${finalUrl}`);
- console.log(` [SID] Dynamic cookie found: ${cookieName}`);
-
- // Step 5: Set cookie and make final request
- console.log(" [SID] Step 5: Setting cookie and making final request...");
- await jar.setCookie(`${cookieName}=${cookieValue}`, origin);
-
- const finalResponse = await session.get(finalUrl, {
- headers: { 'Referer': responseStep2.request.res.responseUrl }
- });
-
- // Step 6: Extract driveleech URL from meta refresh tag
- $ = cheerio.load(finalResponse.data);
- const metaRefresh = $('meta[http-equiv="refresh"]');
- if (metaRefresh.length > 0) {
- const content = metaRefresh.attr('content');
- const urlMatch = content.match(/url=(.*)/i);
- if (urlMatch && urlMatch[1]) {
- const driveleechUrl = urlMatch[1].replace(/"/g, "").replace(/'/g, "");
- console.log(` [SID] SUCCESS! Resolved Driveleech URL: ${driveleechUrl}`);
- return driveleechUrl;
- }
- }
-
- console.error(" [SID] Error: Could not find meta refresh tag with Driveleech URL.");
- return null;
-
- } catch (error) {
- console.error(` [SID] Error during SID resolution: ${error.message}`);
- if (error.response) {
- console.error(` [SID] Status: ${error.response.status}`);
- }
- return null;
- }
-}
-
-
-// Resolve Resume Cloud link to final download URL
-async function resolveResumeCloudLink(resumeUrl) {
- try {
- const { data } = await makeRequest(resumeUrl, {
- headers: {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
- 'Referer': 'https://driveseed.org/',
- }
- });
- const $ = cheerio.load(data);
- const downloadLink = $('a:contains("Cloud Resume Download")').attr('href');
- return downloadLink || null;
- } catch (error) {
- console.error(`[MoviesMod] Error resolving Resume Cloud link: ${error.message}`);
- return null;
- }
-}
-
-// Resolve Worker Seed link to final download URL
-async function resolveWorkerSeedLink(workerSeedUrl) {
- try {
- console.log(`[MoviesMod] Resolving Worker-seed link: ${workerSeedUrl}`);
-
- const jar = new CookieJar();
-
- // Get the wrapper function from dynamic import
- const { wrapper } = await getAxiosCookieJarSupport();
- const session = wrapper(axios.create({
- jar,
- headers: {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
- }
- }));
-
- // Step 1: GET the page to get the script content and cookies
- console.log(`[MoviesMod] Step 1: Fetching page to get script content and cookies...`);
- const { data: pageHtml } = await session.get(workerSeedUrl);
-
- // Step 2: Use regex to extract the token and the correct ID from the script
- const scriptTags = pageHtml.match(/