Compare commits

...

7 Commits

Author SHA1 Message Date
8man cb9cb22c70 Update modflix.json 2026-05-11 21:42:17 +05:30
8man 38fd128eaa Handle blocked responses when final URL resolves elsewhere 2026-05-11 17:51:39 +05:30
8man bfe80ce95a Show URL checker output directly in Actions logs 2026-05-11 17:33:56 +05:30
8man 55d79974db Add verbose logging for URL checks 2026-05-11 17:24:11 +05:30
8man 68dd646b90 Use browser headers and follow redirects for URL updates 2026-05-11 17:03:27 +05:30
8man 1d39a56cc0 Improve URL checker with browser headers and redirect detection 2026-05-11 16:48:14 +05:30
8man 0081034e35 Update modflix.json 2026-05-11 16:25:01 +05:30
3 changed files with 235 additions and 206 deletions
+219 -196
View File
@@ -1,196 +1,219 @@
const fs = require('fs'); const fs = require('fs');
const axios = require('axios'); const axios = require('axios');
const FILE_PATH = 'modflix.json'; const FILE_PATH = 'modflix.json';
const updatedProviders = []; // Track updated providers for Discord notification const updatedProviders = []; // Track updated providers for Discord notification
// Read the modflix.json file const DEFAULT_HEADERS = {
function readModflixJson() { 'User-Agent':
try { 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36',
const data = fs.readFileSync(FILE_PATH, 'utf8'); Accept:
return JSON.parse(data); 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
} catch (error) { 'Accept-Language': 'en-US,en;q=0.9',
console.error(`Error reading ${FILE_PATH}:`, error); 'Accept-Encoding': 'gzip, deflate, br',
process.exit(1); Connection: 'keep-alive',
} 'Upgrade-Insecure-Requests': '1'
} };
// Extract domain (origin) from URL without trailing slash // Read the modflix.json file
function getDomain(url) { function readModflixJson() {
try { try {
const urlObj = new URL(url); const data = fs.readFileSync(FILE_PATH, 'utf8');
return urlObj.origin; return JSON.parse(data);
} catch (error) { } catch (error) {
console.error(`Error parsing URL ${url}:`, error); console.error(`Error reading ${FILE_PATH}:`, error);
return url; process.exit(1);
} }
} }
// Check if original URL has a trailing slash in path // Extract domain (origin) from URL without trailing slash
function hasTrailingSlash(url) { function getDomain(url) {
return url.endsWith('/') && !url.endsWith('://'); try {
} const urlObj = new URL(url);
return urlObj.origin;
// Check URL and return new URL if domain redirected } catch (error) {
async function checkUrl(url) { console.error(`Error parsing URL ${url}:`, error);
try { return url;
// Set timeout to 10 seconds to avoid hanging }
const response = await axios.head(url, { }
maxRedirects: 0,
timeout: 10000, // Check if original URL has a trailing slash in path
validateStatus: status => true function hasTrailingSlash(url) {
}); return url.endsWith('/') && !url.endsWith('://');
}
// If status is 200, no change needed
if (response.status === 200) { function getFinalUrl(response, originalUrl) {
console.log(`${url} is valid (200 OK)`); return (
return null; response?.request?.res?.responseUrl ||
} else if (response.status >= 300 && response.status < 400) { response?.request?._redirectable?._currentUrl ||
// Handle redirects response?.config?.url ||
const newLocation = response.headers.location; originalUrl
if (newLocation) { );
// If it's a relative redirect, construct the full URL }
let fullRedirectUrl = newLocation;
if (!newLocation.startsWith('http')) { function normalizeOrigin(url) {
const baseUrl = new URL(url); try {
fullRedirectUrl = new URL(newLocation, baseUrl.origin).toString(); return new URL(url).origin;
} } catch {
return url;
console.log(`🔄 ${url} redirects to ${fullRedirectUrl}`); }
}
// Get the new domain
const newDomain = getDomain(fullRedirectUrl); async function requestUrl(method, url) {
return axios({
// Check if original URL had a trailing slash method,
const needsTrailingSlash = hasTrailingSlash(url); url,
maxRedirects: 5,
// Create new URL: new domain + trailing slash if the original had one timeout: 10000,
let finalUrl = newDomain; validateStatus: status => true,
if (needsTrailingSlash) { headers: DEFAULT_HEADERS
finalUrl += '/'; });
} }
console.log(`Will update to: ${finalUrl} (preserved trailing slash: ${needsTrailingSlash})`); function logVerboseResult(url, response, finalUrl) {
return finalUrl; const status = response?.status ?? 'unknown';
} const locationHeader = response?.headers?.location;
} else { console.log(
console.log(` ${url} returned status ${response.status}`); ` ${url} -> status=${status} final=${finalUrl}` +
} (locationHeader ? ` location=${locationHeader}` : '')
} catch (error) { );
// Try GET request if HEAD fails }
try {
const response = await axios.get(url, { function shouldUpdateFromFinalUrl(originalUrl, finalUrl) {
maxRedirects: 0, const originalDomain = getDomain(originalUrl);
timeout: 10000, const finalDomain = getDomain(finalUrl);
validateStatus: status => true return finalDomain && finalDomain !== originalDomain;
}); }
if (response.status === 200) { // Check URL and return new URL if domain redirected or resolved elsewhere
console.log(`${url} is valid (200 OK)`); async function checkUrl(url) {
return null; try {
} else if (response.status >= 300 && response.status < 400) { const response = await requestUrl('get', url);
// Handle redirects const finalUrl = getFinalUrl(response, url);
const newLocation = response.headers.location; logVerboseResult(url, response, finalUrl);
if (newLocation) {
console.log(`🔄 ${url} redirects to ${newLocation}`); if (shouldUpdateFromFinalUrl(url, finalUrl)) {
const updatedUrl = normalizeOrigin(finalUrl) + (hasTrailingSlash(url) ? '/' : '');
let fullRedirectUrl = newLocation; console.log(`🔄 ${url} resolved to ${finalUrl}`);
if (!newLocation.startsWith('http')) { console.log(`Will update to: ${updatedUrl} (preserved trailing slash: ${hasTrailingSlash(url)})`);
const baseUrl = new URL(url); return updatedUrl;
fullRedirectUrl = new URL(newLocation, baseUrl.origin).toString(); }
}
if (response.status === 200) {
// Get the new domain console.log(`${url} is valid (200 OK)`);
const newDomain = getDomain(fullRedirectUrl); return null;
}
// Check if original URL had a trailing slash
const needsTrailingSlash = hasTrailingSlash(url); if (response.status >= 300 && response.status < 400) {
const newLocation = response.headers.location;
// Create new URL: new domain + trailing slash if the original had one if (newLocation) {
let finalUrl = newDomain; let fullRedirectUrl = newLocation;
if (needsTrailingSlash) { if (!newLocation.startsWith('http')) {
finalUrl += '/'; const baseUrl = new URL(url);
} fullRedirectUrl = new URL(newLocation, baseUrl.origin).toString();
}
console.log(`Will update to: ${finalUrl} (preserved trailing slash: ${needsTrailingSlash})`);
return finalUrl; if (shouldUpdateFromFinalUrl(url, fullRedirectUrl)) {
} const newDomain = normalizeOrigin(fullRedirectUrl);
} else { const needsTrailingSlash = hasTrailingSlash(url);
console.log(`⚠️ ${url} returned status ${response.status}`); const finalUrlForUpdate = newDomain + (needsTrailingSlash ? '/' : '');
} console.log(`🔄 ${url} redirects to ${fullRedirectUrl}`);
} catch (getError) { console.log(
if (getError.response) { `Will update to: ${finalUrlForUpdate} (preserved trailing slash: ${needsTrailingSlash})`
console.log(`⚠️ ${url} returned status ${getError.response.status}`); );
} else if (getError.code === 'ECONNABORTED') { return finalUrlForUpdate;
console.log(`${url} request timed out`); }
} else if (getError.code === 'ENOTFOUND') { }
console.log(`${url} domain not found`); }
} else {
console.log(`❌ Error checking ${url}: ${getError.message}`); console.log(`⚠️ ${url} returned status ${response.status}`);
} } catch (error) {
} if (error.response) {
} const finalUrl = getFinalUrl(error.response, url);
logVerboseResult(url, error.response, finalUrl);
// Return null if no change or error
return null; // If the request resolves to a different origin even with a non-2xx status,
} // use that as an update signal. This keeps existing behavior intact while
// allowing sites that block HEAD/GET with 403 but still resolve elsewhere.
// Main function if (shouldUpdateFromFinalUrl(url, finalUrl)) {
async function main() { const updatedUrl = normalizeOrigin(finalUrl) + (hasTrailingSlash(url) ? '/' : '');
const providers = readModflixJson(); console.log(`🔄 ${url} resolved to ${finalUrl}`);
let hasChanges = false; console.log(
`Will update to: ${updatedUrl} (preserved trailing slash: ${hasTrailingSlash(url)})`
// Process each provider );
for (const [key, provider] of Object.entries(providers)) { return updatedUrl;
const url = provider.url; }
console.log(`Checking ${provider.name} (${url})...`);
console.log(`⚠️ ${url} returned status ${error.response.status}`);
try { } else if (error.code === 'ECONNABORTED') {
const newUrl = await checkUrl(url); console.log(`${url} request timed out`);
if (newUrl && newUrl !== url) { } else if (error.code === 'ENOTFOUND') {
// Store the old URL before updating console.log(`${url} domain not found`);
const oldUrl = provider.url; } else {
console.log(`❌ Error checking ${url}: ${error.message}`);
// Update the provider URL }
provider.url = newUrl; }
hasChanges = true;
console.log(`Updated ${provider.name} URL from ${oldUrl} to ${newUrl}`); // Return null if no change or error
return null;
// Track updated provider for Discord notification }
updatedProviders.push({
name: provider.name, // Main function
oldUrl: oldUrl, async function main() {
newUrl: newUrl const providers = readModflixJson();
}); let hasChanges = false;
}
} catch (error) { // Process each provider
console.log(`❌ Error processing ${url}: ${error.message}`); for (const [key, provider] of Object.entries(providers)) {
} const url = provider.url;
} console.log(`Checking ${provider.name} (${url})...`);
// Write changes back to file if needed try {
if (hasChanges) { const newUrl = await checkUrl(url);
// Use a space-efficient JSON format but with proper formatting if (newUrl && newUrl !== url) {
const jsonString = JSON.stringify(providers, null, 2); // Store the old URL before updating
fs.writeFileSync(FILE_PATH, jsonString); const oldUrl = provider.url;
console.log(`✅ Updated ${FILE_PATH} with new URLs`);
// Update the provider URL
// Output updated providers for Discord notification in a clean format provider.url = newUrl;
if (updatedProviders.length > 0) { hasChanges = true;
console.log("\n### UPDATED_PROVIDERS_START ###"); console.log(`Updated ${provider.name} URL from ${oldUrl} to ${newUrl}`);
for (const provider of updatedProviders) {
// Format: name|oldUrl|newUrl (pipe-delimited for easy parsing) // Track updated provider for Discord notification
console.log(`${provider.name}|${provider.oldUrl}|${provider.newUrl}`); updatedProviders.push({
} name: provider.name,
console.log("### UPDATED_PROVIDERS_END ###"); oldUrl: oldUrl,
} newUrl: newUrl
} else { });
console.log(`️ No changes needed for ${FILE_PATH}`); }
} } catch (error) {
} console.log(`❌ Error processing ${url}: ${error.message}`);
}
// Execute main function with error handling }
main().catch(error => {
console.error('Unhandled error:', error); // Write changes back to file if needed
process.exit(1); if (hasChanges) {
}); // Use a space-efficient JSON format but with proper formatting
const jsonString = JSON.stringify(providers, null, 2);
fs.writeFileSync(FILE_PATH, jsonString);
console.log(`✅ Updated ${FILE_PATH} with new URLs`);
// Output updated providers for Discord notification in a clean format
if (updatedProviders.length > 0) {
console.log("\n### UPDATED_PROVIDERS_START ###");
for (const provider of updatedProviders) {
// Format: name|oldUrl|newUrl (pipe-delimited for easy parsing)
console.log(`${provider.name}|${provider.oldUrl}|${provider.newUrl}`);
}
console.log("### UPDATED_PROVIDERS_END ###");
}
} else {
console.log(`️ No changes needed for ${FILE_PATH}`);
}
}
// Execute main function with error handling
main().catch(error => {
console.error('Unhandled error:', error);
process.exit(1);
});
+11 -5
View File
@@ -27,20 +27,26 @@ jobs:
- name: Run URL checker - name: Run URL checker
id: url_checker id: url_checker
shell: bash
run: | run: |
# Run the URL checker and save output set -o pipefail
node .github/scripts/url-checker.js > checker_output.log 2>&1 # Run the URL checker and show output in the job logs while also saving it
node .github/scripts/url-checker.js 2>&1 | tee checker_output.log
# Check if there are updated providers # Check if there are updated providers
if grep -q "### UPDATED_PROVIDERS_START ###" checker_output.log; then if grep -q "### UPDATED_PROVIDERS_START ###" checker_output.log; then
echo "CHANGES_DETECTED=true" >> $GITHUB_ENV echo "CHANGES_DETECTED=true" >> "$GITHUB_ENV"
# Extract only the updated provider lines between the markers # Extract only the updated provider lines between the markers
sed -n '/### UPDATED_PROVIDERS_START ###/,/### UPDATED_PROVIDERS_END ###/p' checker_output.log | sed -n '/### UPDATED_PROVIDERS_START ###/,/### UPDATED_PROVIDERS_END ###/p' checker_output.log | \
grep -v "###" > updated_providers.txt grep -v "###" > updated_providers.txt
else else
echo "CHANGES_DETECTED=false" >> $GITHUB_ENV echo "CHANGES_DETECTED=false" >> "$GITHUB_ENV"
fi fi
echo "--- checker_output.log ---"
cat checker_output.log
echo "--- end checker_output.log ---"
- name: Commit changes if any - name: Commit changes if any
run: | run: |
+5 -5
View File
@@ -33,7 +33,7 @@
}, },
"multi": { "multi": {
"name": "multimovies", "name": "multimovies",
"url": "https://multimovies.autos" "url": "https://multimovies.fyi"
}, },
"w4u": { "w4u": {
"name": "world4ufree", "name": "world4ufree",
@@ -49,7 +49,7 @@
}, },
"kat": { "kat": {
"name": "katmovieshd", "name": "katmovieshd",
"url": "https://katmoviehd.pictures" "url": "https://new1.katmoviehd.cymru"
}, },
"dc": { "dc": {
"name": "dramacool", "name": "dramacool",
@@ -141,7 +141,7 @@
}, },
"4khdhub": { "4khdhub": {
"name": "4khdhub", "name": "4khdhub",
"url": "https://4khdhub.dad" "url": "https://4khdhub.link"
}, },
"moviezwap": { "moviezwap": {
"name": "moviezwap", "name": "moviezwap",
@@ -185,6 +185,6 @@
}, },
"1cinevood": { "1cinevood": {
"name": "cinewood", "name": "cinewood",
"url": "https://proxy01.cvproxy.workers.dev" "url": "https://1cinevood.in"
} }
} }