From a16a3be096ee65cdb9ecf686cb69d066c8240ef1 Mon Sep 17 00:00:00 2001 From: Shinei Nouzen <157171073+Shineii86@users.noreply.github.com> Date: Tue, 25 Aug 2026 06:56:29 +0530 Subject: [PATCH] feat: add direct embed providers (megavid, anixo) to bypass Cloudflare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- CHANGELOG.md | 21 ++++++++-- src/helpers/pipe.js | 43 ++++++++++++++++++++ src/routes/apiRoutes.js | 88 ++++++++++++++++++++++++++++++++++++++++- test.js | 6 +++ 4 files changed, 152 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8304bdd..ab251b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ # Changelog ## 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 - **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 - 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 -- `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 - `server.js` — Updated CORS headers, version bump - `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/sources` — Added `/api/sources/:id` path-based endpoint - [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] Proper 400/404 responses — Input validation for invalid IDs and missing params - [x] Error handling — Timeouts, Cloudflare blocks, upstream failures handled gracefully @@ -49,8 +61,9 @@ #### Notes - 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 -- For reliable streaming, deploy on Cloudflare Workers (worker.js) or configure SCRAPER_API_KEY/FLARESOLVERR_URL +- **Direct embed providers (`/api/embed`) work from anywhere** — no Cloudflare blocking +- 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 --- diff --git a/src/helpers/pipe.js b/src/helpers/pipe.js index 442a2c7..db654e8 100644 --- a/src/helpers/pipe.js +++ b/src/helpers/pipe.js @@ -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 = { getEpisodes, getSources, @@ -607,6 +647,9 @@ module.exports = { deepTranslate, injectSourceSlugs, pipeHealthCheck, + EMBED_PROVIDERS, + getEmbedUrl, + getEmbedUrls, }; // ══════════════════════════════════════════════════════════════ END: pipe.js diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 08b9e73..8a95843 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -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 // ══════════════════════════════════════════════════════════════ @@ -1178,8 +1257,9 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => { size: require("../helpers/cache").getCacheSize(), maxSize: 100, }, - endpoints: 46, + endpoints: 49, 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(), description: "Map-based cache with TTL expiration and FIFO eviction", }, - endpoints: 46, + endpoints: 49, + embedProviders: 2, timestamp: new Date().toISOString(), }, }); @@ -1304,6 +1385,9 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => { "/calendar": { get: { summary: "Monthly calendar", tags: ["Utility"] } }, "/timeline/{id}": { get: { summary: "Anime timeline", tags: ["Utility"] } }, "/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"] } }, "/stats": { get: { summary: "API statistics", tags: ["System"] } }, "/openapi": { get: { summary: "OpenAPI spec", tags: ["System"] } }, diff --git a/test.js b/test.js index 0c51829..025fb19 100644 --- a/test.js +++ b/test.js @@ -93,6 +93,12 @@ const tests = [ { name: "Genre Stats", url: "/stats/genre" }, { name: "Calendar", url: "/calendar" }, { 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" }, ]; // ══════════════════════════════════════════════════════════════