feat(v2.3.0): self-healing fallback system — auto-recovery from Cloudflare blocks

pipe.js now tries 3 methods in order with automatic fallback:
1. Direct — mirror rotation (.ru → .to → .bz → .tv) with browser headers
2. ScraperAPI — free proxy (1K req/mo, no CC required)
3. FlareSolverr — self-hosted browser proxy (solves Cloudflare Turnstile)

When one method fails (403/timeout), the next is tried automatically.
Zero-config: works without any env vars. Add SCRAPER_API_KEY or
FLARESOLVERR_URL for Cloudflare bypass.

New endpoint: /api/pipe-health — tests all configured methods,
reports latency and working/failed status, recommends preferred method.

Files changed:
- src/helpers/pipe.js — methodDirect(), methodScraperAPI(), methodFlareSolverr(), pipeHealthCheck()
- src/routes/apiRoutes.js — added /api/pipe-health endpoint
- .env.example — documented SCRAPER_API_KEY, FLARESOLVERR_URL

Version bumped to 2.3.0 across package.json, server.js, /api/health,
/api/openapi, public/openapi.json. CHANGELOG updated.
This commit is contained in:
Shinei Nouzen
2026-07-02 07:12:06 +05:30
parent 92154d7ea2
commit eb0e166310
7 changed files with 297 additions and 320 deletions
+35 -2
View File
@@ -20,6 +20,7 @@
const express = require("express");
const anilist = require("../helpers/anilist");
const pipe = require("../helpers/pipe");
const { pipeHealthCheck } = require("../helpers/pipe");
const { getCached, setCache } = require("../helpers/cache");
const { addCreatorInfo } = require("../middleware/creatorInfo");
@@ -1123,7 +1124,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
success: true,
results: {
status: "healthy",
version: "2.2.0",
version: "2.3.0",
uptime: `${hours}h ${minutes}m ${seconds}s`,
uptimeSeconds: uptime,
timestamp: new Date().toISOString(),
@@ -1143,6 +1144,38 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
});
});
// ---- FEATURE: Pipe health check (tests all fallback methods) ----
router.get("/pipe-health", async (req, res) => {
try {
const results = await pipeHealthCheck();
const working = Object.entries(results).filter(
([, v]) => v.status === "ok"
);
const failed = Object.entries(results).filter(
([, v]) => v.status === "failed"
);
res.json({
success: true,
results: {
methods: results,
working: working.length,
failed: failed.length,
total: Object.keys(results).length,
recommendation:
working.length === 0
? "All methods blocked. Check Cloudflare status or add SCRAPER_API_KEY / FLARESOLVERR_URL."
: working[0]
? `Preferred: ${working[0][0]} (${working[0][1].latency})`
: "No methods available",
timestamp: new Date().toISOString(),
},
});
} catch (err) {
jsonError(res, err.message);
}
});
// ---- FEATURE: Cache & API statistics ----
router.get("/stats", (req, res) => {
const uptime = Math.floor((Date.now() - startTime) / 1000);
@@ -1178,7 +1211,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
info: {
title: "MiruroAPI",
description: "Free REST API for anime streaming data — AniList GraphQL + Miruro streaming providers",
version: "2.2.0",
version: "2.3.0",
contact: { name: "Shineii86", url: "https://github.com/Shineii86/MiruroAPI" },
},
servers: [