feat: add direct embed providers (megavid, anixo) to bypass Cloudflare

- Added GET /api/embed/:anilistId/:episode — embed URLs for all providers
- Added GET /api/embed/:anilistId/:episode/:provider — specific provider embed
- Added GET /api/embed-providers — list all available embed providers
- Embed providers return iframe-ready URLs, no scraping required
- Supports sub/dub language selection and optional malId param
- Updated OpenAPI spec with new embed endpoints
- Updated health/stats to show 49 endpoints and embed providers
- Added embed provider tests to test.js

Cloudflare-protected pipe endpoints now have working alternatives:
use /api/embed to get direct video URLs from megavid.buzz and anixo.buzz
This commit is contained in:
Shinei Nouzen
2026-08-25 06:56:29 +05:30
parent a5fbace9c1
commit a16a3be096
4 changed files with 152 additions and 6 deletions
+17 -4
View File
@@ -1,7 +1,16 @@
# Changelog # Changelog
## v2.3.3 ## v2.3.3
### Issue #3 Fix — API Stability, Error Handling & New Endpoints ### Issue #3 Fix — API Stability, Direct Embed Providers & Error Handling
#### New: Direct Embed Providers (bypasses Cloudflare entirely)
- **`megavid.buzz`** — Direct video embed, no scraping needed
- **`anixo.buzz`** — Direct video embed, CORS-friendly
- Added `GET /api/embed/:anilistId/:episode` — Returns embed URLs for all providers
- Added `GET /api/embed/:anilistId/:episode/:provider` — Returns embed URL for specific provider
- Added `GET /api/embed-providers` — Lists all available embed providers
- These endpoints return URLs that can be embedded directly in an iframe — **no Cloudflare blocking**
- Supports `lang=sub|dub` and optional `malId` query params
#### Fixed: Streaming Endpoint Error Handling #### Fixed: Streaming Endpoint Error Handling
- **Cloudflare detection** — Streaming endpoints (`/episodes`, `/stream`, `/watch`, `/download`, `/sources`) now return proper `503` status codes with descriptive error messages when Cloudflare blocks requests - **Cloudflare detection** — Streaming endpoints (`/episodes`, `/stream`, `/watch`, `/download`, `/sources`) now return proper `503` status codes with descriptive error messages when Cloudflare blocks requests
@@ -25,9 +34,11 @@
#### Changed: Version Bump #### Changed: Version Bump
- All version references updated from `2.3.2` to `2.3.3` - All version references updated from `2.3.2` to `2.3.3`
- Endpoint count updated from 46 to 49 (3 new embed routes)
#### Files Changed #### Files Changed
- `src/routes/apiRoutes.js` — Added `/sources/:id` route, improved error handling for all streaming endpoints, updated OpenAPI spec - `src/helpers/pipe.js` — Added EMBED_PROVIDERS config, getEmbedUrl(), getEmbedUrls()
- `src/routes/apiRoutes.js` — Added /embed, /embed-providers routes, improved error handling, updated OpenAPI spec
- `src/helpers/pipe.js` — Better timeout handling, Cloudflare detection, informative error messages - `src/helpers/pipe.js` — Better timeout handling, Cloudflare detection, informative error messages
- `server.js` — Updated CORS headers, version bump - `server.js` — Updated CORS headers, version bump
- `package.json` — Version bump to 2.3.3 - `package.json` — Version bump to 2.3.3
@@ -42,6 +53,7 @@
- [x] Fix `/api/episodes/:id` — Improved error handling, proper 503 on Cloudflare blocks - [x] Fix `/api/episodes/:id` — Improved error handling, proper 503 on Cloudflare blocks
- [x] Fix `/api/sources` — Added `/api/sources/:id` path-based endpoint - [x] Fix `/api/sources` — Added `/api/sources/:id` path-based endpoint
- [x] Fix `/api/watch` — Improved error handling with Cloudflare detection - [x] Fix `/api/watch` — Improved error handling with Cloudflare detection
- [x] **NEW: Direct embed providers** — megavid.buzz + anixo.buzz bypass Cloudflare entirely
- [x] Consistent JSON responses — All endpoints return `{ success, results/message }` - [x] Consistent JSON responses — All endpoints return `{ success, results/message }`
- [x] Proper 400/404 responses — Input validation for invalid IDs and missing params - [x] Proper 400/404 responses — Input validation for invalid IDs and missing params
- [x] Error handling — Timeouts, Cloudflare blocks, upstream failures handled gracefully - [x] Error handling — Timeouts, Cloudflare blocks, upstream failures handled gracefully
@@ -49,8 +61,9 @@
#### Notes #### Notes
- AniList-based endpoints (search, info, trending, filter, etc.) are fully functional - AniList-based endpoints (search, info, trending, filter, etc.) are fully functional
- Streaming endpoints depend on miruro.to pipe availability — Cloudflare may still block from datacenters - **Direct embed providers (`/api/embed`) work from anywhere** — no Cloudflare blocking
- For reliable streaming, deploy on Cloudflare Workers (worker.js) or configure SCRAPER_API_KEY/FLARESOLVERR_URL - Pipe-based endpoints may still be blocked from datacenters — use `/api/embed` instead
- For Cloudflare-protected pipe, configure SCRAPER_API_KEY/FLARESOLVERR_URL as fallback
--- ---
+43
View File
@@ -592,6 +592,46 @@ const getBatchSources = async (provider, anilistId, category, slugs) => {
}; };
}; };
// ══════════════════════════════════════════════════════════════
// DIRECT EMBED PROVIDERS (bypass Cloudflare — no scraping needed)
// ══════════════════════════════════════════════════════════════
const EMBED_PROVIDERS = [
{
id: "megavid",
name: "Megavid",
cors: false,
makeUrl(anilistId, episode, lang = "sub", malId = null) {
const idType = malId ? "mal" : "ani";
const id = malId || anilistId;
return `https://megavid.buzz/${idType}/${id}/${episode}/${lang}?autoplay=true`;
},
},
{
id: "anixo",
name: "AniXo",
cors: true,
makeUrl(anilistId, episode, lang = "sub") {
return `https://anixo.buzz/embed/ani/${anilistId}/${episode}/${lang}?autoplay=true`;
},
},
];
const getEmbedUrl = (providerId, anilistId, episode, lang = "sub", malId = null) => {
const provider = EMBED_PROVIDERS.find((p) => p.id === providerId);
if (!provider) return null;
return provider.makeUrl(anilistId, episode, lang, malId);
};
const getEmbedUrls = (anilistId, episode, lang = "sub", malId = null) => {
return EMBED_PROVIDERS.map((p) => ({
id: p.id,
name: p.name,
cors: p.cors,
url: p.makeUrl(anilistId, episode, lang, malId),
}));
};
module.exports = { module.exports = {
getEpisodes, getEpisodes,
getSources, getSources,
@@ -607,6 +647,9 @@ module.exports = {
deepTranslate, deepTranslate,
injectSourceSlugs, injectSourceSlugs,
pipeHealthCheck, pipeHealthCheck,
EMBED_PROVIDERS,
getEmbedUrl,
getEmbedUrls,
}; };
// ══════════════════════════════════════════════════════════════ END: pipe.js // ══════════════════════════════════════════════════════════════ END: pipe.js
+86 -2
View File
@@ -812,6 +812,85 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
} }
}); });
// ══════════════════════════════════════════════════════════════
// DIRECT EMBED PROVIDERS (no scraping — bypasses Cloudflare)
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Direct embed URLs for all providers ----
router.get("/embed/:anilistId/:episode", (req, res) => {
try {
const anilistId = parseInt(req.params.anilistId);
const episode = parseInt(req.params.episode);
if (isNaN(anilistId) || anilistId <= 0) return jsonError(res, "Invalid AniList ID", 400);
if (isNaN(episode) || episode <= 0) return jsonError(res, "Invalid episode number", 400);
const { lang = "sub", malId } = req.query;
const validLangs = ["sub", "dub"];
if (!validLangs.includes(lang)) return jsonError(res, "lang must be 'sub' or 'dub'", 400);
const embeds = pipe.getEmbedUrls(anilistId, episode, lang, malId ? parseInt(malId) : null);
jsonResponse(res, {
anilistId,
episode,
lang,
providers: embeds,
total: embeds.length,
description: "Direct embed URLs — embed in an iframe to watch. No scraping required.",
});
} catch (err) {
jsonError(res, err.message, 500);
}
});
// ---- FEATURE: Direct embed URL for specific provider ----
router.get("/embed/:anilistId/:episode/:provider", (req, res) => {
try {
const anilistId = parseInt(req.params.anilistId);
const episode = parseInt(req.params.episode);
const providerId = req.params.provider;
if (isNaN(anilistId) || anilistId <= 0) return jsonError(res, "Invalid AniList ID", 400);
if (isNaN(episode) || episode <= 0) return jsonError(res, "Invalid episode number", 400);
const { lang = "sub", malId } = req.query;
const validLangs = ["sub", "dub"];
if (!validLangs.includes(lang)) return jsonError(res, "lang must be 'sub' or 'dub'", 400);
const url = pipe.getEmbedUrl(providerId, anilistId, episode, lang, malId ? parseInt(malId) : null);
if (!url) return jsonError(res, `Unknown provider: ${providerId}. Available: megavid, anixo`, 404);
const provider = pipe.EMBED_PROVIDERS.find((p) => p.id === providerId);
jsonResponse(res, {
anilistId,
episode,
lang,
provider: providerId,
name: provider.name,
url,
cors: provider.cors,
description: "Embed this URL in an iframe to watch. No scraping required.",
});
} catch (err) {
jsonError(res, err.message, 500);
}
});
// ---- FEATURE: List all available embed providers ----
router.get("/embed-providers", (req, res) => {
jsonResponse(res, {
providers: pipe.EMBED_PROVIDERS.map((p) => ({
id: p.id,
name: p.name,
cors: p.cors,
example: p.makeUrl(20, 1, "sub"),
})),
total: pipe.EMBED_PROVIDERS.length,
usage: "GET /api/embed/:anilistId/:episode?lang=sub",
description: "Direct embed providers — bypass Cloudflare by embedding video URLs directly in an iframe.",
});
});
// ══════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════
// UTILITY FEATURES // UTILITY FEATURES
// ══════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════
@@ -1178,8 +1257,9 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
size: require("../helpers/cache").getCacheSize(), size: require("../helpers/cache").getCacheSize(),
maxSize: 100, maxSize: 100,
}, },
endpoints: 46, endpoints: 49,
providers: ["kiwi", "pewe", "bee", "bonk", "bun", "ally", "nun", "twin", "cog", "moo", "hop", "telli"], providers: ["kiwi", "pewe", "bee", "bonk", "bun", "ally", "nun", "twin", "cog", "moo", "hop", "telli"],
embedProviders: ["megavid", "anixo"],
}, },
}); });
}); });
@@ -1242,7 +1322,8 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
currentSize: require("../helpers/cache").getCacheSize(), currentSize: require("../helpers/cache").getCacheSize(),
description: "Map-based cache with TTL expiration and FIFO eviction", description: "Map-based cache with TTL expiration and FIFO eviction",
}, },
endpoints: 46, endpoints: 49,
embedProviders: 2,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}, },
}); });
@@ -1304,6 +1385,9 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
"/calendar": { get: { summary: "Monthly calendar", tags: ["Utility"] } }, "/calendar": { get: { summary: "Monthly calendar", tags: ["Utility"] } },
"/timeline/{id}": { get: { summary: "Anime timeline", tags: ["Utility"] } }, "/timeline/{id}": { get: { summary: "Anime timeline", tags: ["Utility"] } },
"/providers": { get: { summary: "Provider capabilities", tags: ["System"] } }, "/providers": { get: { summary: "Provider capabilities", tags: ["System"] } },
"/embed/{anilistId}/{episode}": { get: { summary: "Direct embed URLs (all providers)", tags: ["Streaming & Downloading"] } },
"/embed/{anilistId}/{episode}/{provider}": { get: { summary: "Direct embed URL (specific provider)", tags: ["Streaming & Downloading"] } },
"/embed-providers": { get: { summary: "List embed providers", tags: ["System"] } },
"/health": { get: { summary: "Health check", tags: ["System"] } }, "/health": { get: { summary: "Health check", tags: ["System"] } },
"/stats": { get: { summary: "API statistics", tags: ["System"] } }, "/stats": { get: { summary: "API statistics", tags: ["System"] } },
"/openapi": { get: { summary: "OpenAPI spec", tags: ["System"] } }, "/openapi": { get: { summary: "OpenAPI spec", tags: ["System"] } },
+6
View File
@@ -93,6 +93,12 @@ const tests = [
{ name: "Genre Stats", url: "/stats/genre" }, { name: "Genre Stats", url: "/stats/genre" },
{ name: "Calendar", url: "/calendar" }, { name: "Calendar", url: "/calendar" },
{ name: "Timeline", url: "/timeline/20" }, { name: "Timeline", url: "/timeline/20" },
// ---- FEATURE: Direct embed endpoints (no scraping) ----
{ name: "Embed All Providers", url: "/embed/20/1?lang=sub" },
{ name: "Embed Megavid", url: "/embed/20/1/megavid?lang=sub" },
{ name: "Embed AniXo", url: "/embed/20/1/anixo?lang=sub" },
{ name: "Embed Providers List", url: "/embed-providers" },
]; ];
// ══════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════