]*class="item\\s+server-item"[^>]*data-type="${esc(wantTrack)}"[^>]*data-id="(\\d+)"[^>]*>[\\s\\S]*?
]*>[\\s\\S]*?${esc(serverName)}[\\s\\S]*?<\\/a>`,
"i"
);
- const mmExact = reExact.exec(html);
- let serverId = mmExact ? String(mmExact[1] || "").trim() : "";
+ let mm = strictRe.exec(serverHtml);
+ let serverId = mm ? String(mm[1] || "").trim() : "";
if (!serverId) {
- const reFirst = new RegExp(
- `class="item server-item"[^>]*data-type="${subOrDub}"[^>]*data-id="([^"]+)"`,
+ const trackAnyRe = new RegExp(
+ `]*class="item\\s+server-item"[^>]*data-type="${esc(wantTrack)}"[^>]*data-id="(\\d+)"`,
"i"
);
- const mmFirst = reFirst.exec(html);
- if (mmFirst) serverId = String(mmFirst[1] || "").trim();
+ mm = trackAnyRe.exec(serverHtml);
+ serverId = mm ? String(mm[1] || "").trim() : "";
}
- if (!serverId) throw new Error(`No server id found for track=${subOrDub}`);
+ if (!serverId) throw new Error(`No server id found for track=${wantTrack}`);
const sourcesJson = this._getJson(
`${this.baseUrl}/ajax/v2/episode/sources?id=${encodeURIComponent(serverId)}`,
- this._headersJson()
+ { "X-Requested-With": "XMLHttpRequest" }
);
const embed = (sourcesJson && sourcesJson.link) ? String(sourcesJson.link) : "";
@@ -481,7 +332,7 @@ class HiAnime {
const sources = Array.isArray(decryptData.sources) ? decryptData.sources : [];
const tracks = Array.isArray(decryptData.tracks) ? decryptData.tracks : [];
- const streamSource =
+ let streamSource =
sources.find((s) => s && s.type === "hls" && s.file) ||
sources.find((s) => s && s.type === "mp4" && s.file) ||
sources.find((s) => s && s.file);
@@ -490,27 +341,23 @@ class HiAnime {
const subtitles = tracks
.filter((t) => t && String(t.kind || "").toLowerCase() === "captions" && t.file)
- .map((t, index) => ({
- id: `sub-${index}`,
- language: String(t.label || "Unknown"),
+ .map((t, idx) => ({
+ id: `sub-${idx}`,
+ language: this._decodeHtml(t.label || "Unknown"),
url: String(t.file),
isDefault: !!t.default
}));
- const out = {
- server: server,
+ return JSON.stringify({
+ server: serverName,
headers: requiredHeaders,
- videoSources: [
- {
- url: String(streamSource.file),
- type: String(streamSource.type || "").toLowerCase() === "hls" ? "m3u8" : "mp4",
- quality: "auto",
- subtitles: subtitles
- }
- ]
- };
-
- return JSON.stringify(out);
+ videoSources: [{
+ url: String(streamSource.file),
+ type: (String(streamSource.type || "").toLowerCase() === "hls" || String(streamSource.file).includes(".m3u8")) ? "m3u8" : "mp4",
+ quality: "auto",
+ subtitles
+ }]
+ });
}
extractMegaCloudSync(embedUrl) {
@@ -522,10 +369,10 @@ class HiAnime {
"X-Requested-With": "XMLHttpRequest",
"Referer": baseDomain,
"Origin": `${url.protocol}//${url.host}`,
- "User-Agent": "Mozilla/5.0"
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
};
- const html = this._getText(String(embedUrl), headers);
+ const html = this._getText(embedUrl, headers);
const fileIdMatch = html.match(/
\s*File\s+#([a-zA-Z0-9]+)\s*-/i);
if (!fileIdMatch) throw new Error("file_id not found in embed page");
@@ -534,14 +381,12 @@ class HiAnime {
let nonce = null;
const match48 = html.match(/\b[a-zA-Z0-9]{48}\b/);
if (match48) nonce = match48[0];
-
- if (!nonce) {
- const match3x16 = [...html.matchAll(/["']([A-Za-z0-9]{16})["']/g)];
+ else {
+ const match3x16 = Array.from(html.matchAll(/["']([A-Za-z0-9]{16})["']/g));
if (match3x16.length >= 3) {
nonce = match3x16[0][1] + match3x16[1][1] + match3x16[2][1];
}
}
-
if (!nonce) throw new Error("nonce not found");
const sourcesJson = this._getJson(
@@ -550,7 +395,7 @@ class HiAnime {
);
return {
- sources: sourcesJson.sources || [],
+ sources: sourcesJson.sources,
tracks: sourcesJson.tracks || [],
intro: sourcesJson.intro || null,
outro: sourcesJson.outro || null,
@@ -560,4 +405,4 @@ class HiAnime {
}
}
-module.exports = HiAnime;
+module.exports = new HiAnime();