From a8223acf92f6dbfd87934f506c488f9479277b98 Mon Sep 17 00:00:00 2001 From: Shinei Nouzen <157171073+Shineii86@users.noreply.github.com> Date: Wed, 12 Aug 2026 22:59:18 +0530 Subject: [PATCH] fix: resolve null values in stream server endpoints - Fix extractServerList JSON parsing: fetchWithMirror returns raw text but code treated it as parsed object, causing Cheerio to parse JSON string as HTML resulting in empty server arrays - Fix extractMapperServers JSON parsing: same raw text issue caused data processing loop to be skipped, returning empty arrays - Fix streamInfo controller to return 404 with error message when stream URL is null instead of silently returning null values - Fix server list controller to use proper TTL and handle empty results - Fix JSON.parse error handling in episodeList and episodeListAjax extractors - Update CHANGELOG.md with v2.2.1 bug fixes --- CHANGELOG.md | 12 ++++++++++++ src/controllers/streamInfo.controller.js | 10 ++++++++-- src/extractors/episodeList.extractor.js | 6 +++++- src/extractors/episodeListAjax.extractor.js | 5 ++++- src/extractors/streamInfo.extractor.js | 15 +++++++++++++-- 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89740e0..31b54b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.2.1] - 2026-08-12 + +### Fixed +- **CRITICAL**: Fixed `streamInfo.extractor.js` `extractServerList` not parsing JSON response — `fetchWithMirror` returns raw text by default, causing Cheerio to parse a JSON string as HTML, resulting in empty server arrays (null values) +- **CRITICAL**: Fixed `streamInfo.extractor.js` `extractMapperServers` not parsing JSON response — same raw text issue caused the data processing loop to be skipped entirely, returning empty arrays +- Fixed `streamInfo.controller.js` `getStreamInfo` to return 404 with error message when stream URL is null instead of silently returning null values +- Fixed `streamInfo.controller.js` `getServerList` to return 404 with error message when server list is empty instead of returning empty array with no indication +- Fixed `streamInfo.controller.js` to use `TTL.stream` instead of `TTL.default` for stream info caching (stream URLs rotate frequently) +- Fixed `streamInfo.controller.js` to use `TTL.servers` instead of `TTL.default` for server list caching +- Fixed `episodeList.extractor.js` JSON.parse error handling — added try/catch around JSON parsing to prevent uncaught exceptions +- Fixed `episodeListAjax.extractor.js` JSON.parse error handling — added try/catch around JSON parsing to prevent uncaught exceptions + ## [2.2.0] - 2026-07-28 ### Added diff --git a/src/controllers/streamInfo.controller.js b/src/controllers/streamInfo.controller.js index ebea6fd..3889e0a 100644 --- a/src/controllers/streamInfo.controller.js +++ b/src/controllers/streamInfo.controller.js @@ -54,7 +54,10 @@ const getStreamInfo = async (req, res, next) => { return res.json({ success: true, results: cached }); } const data = await extractStreamInfo(id); - setCache(cacheKey, data, TTL.default); + if (!data.url) { + return res.status(404).json({ success: false, message: "Stream URL not found for this link ID", results: data }); + } + setCache(cacheKey, data, TTL.stream); res.json({ success: true, results: data }); } catch (error) { next(error); @@ -94,7 +97,10 @@ const getServerList = async (req, res, next) => { return res.json({ success: true, results: cached }); } const data = await extractServerList(ids); - setCache(cacheKey, data, TTL.default); + if (!data || data.length === 0) { + return res.status(404).json({ success: false, message: "No servers found for the given episode IDs", results: [] }); + } + setCache(cacheKey, data, TTL.servers); res.json({ success: true, results: data }); } catch (error) { next(error); diff --git a/src/extractors/episodeList.extractor.js b/src/extractors/episodeList.extractor.js index 5beb251..7f44312 100644 --- a/src/extractors/episodeList.extractor.js +++ b/src/extractors/episodeList.extractor.js @@ -61,7 +61,11 @@ const extractEpisodeList = async (slugOrId) => { headers: { "X-Requested-With": "XMLHttpRequest" } }); - const ajaxParsed = typeof ajaxRaw === "string" ? JSON.parse(ajaxRaw) : ajaxRaw; + // NOTE: Parse JSON response first — fetchWithMirror returns raw text by default + let ajaxParsed = ajaxRaw; + if (typeof ajaxRaw === "string") { + try { ajaxParsed = JSON.parse(ajaxRaw); } catch { ajaxParsed = {}; } + } const ajaxHtml = ajaxParsed?.result || ""; const $ep = cheerio.load(ajaxHtml); diff --git a/src/extractors/episodeListAjax.extractor.js b/src/extractors/episodeListAjax.extractor.js index 0f792d3..db5b5de 100644 --- a/src/extractors/episodeListAjax.extractor.js +++ b/src/extractors/episodeListAjax.extractor.js @@ -55,7 +55,10 @@ const extractEpisodeListAjax = async (animeId, style = "", vrf = "") => { }); // NOTE: Response is JSON {status, result} where result is HTML - const parsed = typeof raw === "string" ? JSON.parse(raw) : raw; + let parsed = raw; + if (typeof raw === "string") { + try { parsed = JSON.parse(raw); } catch { parsed = {}; } + } const html = parsed?.result || ""; const $ = cheerio.load(html); diff --git a/src/extractors/streamInfo.extractor.js b/src/extractors/streamInfo.extractor.js index b892da1..e9f2e62 100644 --- a/src/extractors/streamInfo.extractor.js +++ b/src/extractors/streamInfo.extractor.js @@ -110,7 +110,12 @@ const extractServerList = async (episodeIds, watchSlug = null) => { headers: { "X-Requested-With": "XMLHttpRequest" } }); - const html = typeof raw === "string" ? raw : (raw?.result || ""); + // NOTE: Parse JSON response first — fetchWithMirror returns raw text by default + let parsed = raw; + if (typeof raw === "string") { + try { parsed = JSON.parse(raw); } catch { parsed = {}; } + } + const html = parsed?.result || ""; const $ = cheerio.load(html); const servers = []; @@ -165,7 +170,13 @@ const extractMapperServers = async (malId, slug, timestamp) => { if (!Number.isFinite(Number(timestamp))) throw new Error("Invalid timestamp"); const path = `/ajax/mapper/${encodeURIComponent(malId)}/${encodeURIComponent(slug)}/${encodeURIComponent(timestamp)}`; - const { data } = await fetchWithMirror(path); + const { data: rawData } = await fetchWithMirror(path); + + // NOTE: Parse JSON response first — fetchWithMirror returns raw text by default + let data = rawData; + if (typeof rawData === "string") { + try { data = JSON.parse(rawData); } catch { data = {}; } + } const servers = [];