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
This commit is contained in:
Shinei Nouzen
2026-08-12 22:59:18 +05:30
parent 8f423bf39c
commit a8223acf92
5 changed files with 42 additions and 6 deletions
+12
View File
@@ -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
+8 -2
View File
@@ -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);
+5 -1
View File
@@ -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);
+4 -1
View File
@@ -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);
+13 -2
View File
@@ -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 = [];