Update anime/anicrush/source.js
This commit is contained in:
@@ -43,21 +43,42 @@ class AniCrush {
|
||||
}
|
||||
}
|
||||
|
||||
_postJson(url, headers, obj) {
|
||||
const res = this._nativeFetch(url, "POST", headers, JSON.stringify(obj || {}));
|
||||
try {
|
||||
return JSON.parse(String(res.body || "{}"));
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
_safeStr(v) {
|
||||
return typeof v === "string" ? v : (v == null ? "" : String(v));
|
||||
}
|
||||
|
||||
_normalizeTitle(t) {
|
||||
return this._safeStr(t)
|
||||
_headers() {
|
||||
return {
|
||||
"User-Agent": "Mozilla/5.0",
|
||||
"Accept": "application/json",
|
||||
"Referer": this.baseUrl + "/",
|
||||
"Origin": this.baseUrl,
|
||||
"X-Site": "anicrush"
|
||||
};
|
||||
}
|
||||
|
||||
_parseQuery(q) {
|
||||
if (typeof q === "string") {
|
||||
const s = q.trim();
|
||||
if (s.startsWith("{") || s.startsWith("[")) {
|
||||
try { return JSON.parse(s); } catch (e) { return { query: s }; }
|
||||
}
|
||||
return { query: s };
|
||||
}
|
||||
return q || {};
|
||||
}
|
||||
|
||||
_normalize(title) {
|
||||
return (this._safeStr(title))
|
||||
.toLowerCase()
|
||||
.replace(/(season|cour|part)/g, "")
|
||||
.replace(/\d+(st|nd|rd|th)/g, (m) => m.replace(/st|nd|rd|th/, ""))
|
||||
.replace(/[^a-z0-9]+/g, "")
|
||||
.replace(/(?<!i)ii(?!i)/g, "2");
|
||||
}
|
||||
|
||||
_normalizeTitle(title) {
|
||||
return (this._safeStr(title))
|
||||
.toLowerCase()
|
||||
.replace(/(season|cour|part|uncensored)/g, "")
|
||||
.replace(/\d+(st|nd|rd|th)/g, (m) => m.replace(/st|nd|rd|th/, ""))
|
||||
@@ -101,30 +122,9 @@ class AniCrush {
|
||||
}
|
||||
}
|
||||
|
||||
const dist = dp[lenA][lenB];
|
||||
const distance = dp[lenA][lenB];
|
||||
const maxLen = Math.max(lenA, lenB);
|
||||
return 1 - dist / maxLen;
|
||||
}
|
||||
|
||||
_headers() {
|
||||
return {
|
||||
"User-Agent": "Mozilla/5.0",
|
||||
"Accept": "application/json",
|
||||
"Referer": this.baseUrl + "/",
|
||||
"Origin": this.baseUrl,
|
||||
"X-Site": "anicrush"
|
||||
};
|
||||
}
|
||||
|
||||
_parseQuery(q) {
|
||||
if (typeof q === "string") {
|
||||
const s = q.trim();
|
||||
if (s.startsWith("{") || s.startsWith("[")) {
|
||||
try { return JSON.parse(s); } catch (e) { return { query: s }; }
|
||||
}
|
||||
return { query: s };
|
||||
}
|
||||
return q || {};
|
||||
return 1 - distance / maxLen;
|
||||
}
|
||||
|
||||
search(query) {
|
||||
@@ -135,18 +135,16 @@ class AniCrush {
|
||||
|
||||
const media = query.media || {};
|
||||
const start = (media.startDate || {});
|
||||
const wantYear = start.year || 0;
|
||||
const wantMonth = start.month || 0;
|
||||
|
||||
const targetNormJP = this._normalizeTitle(media.romajiTitle);
|
||||
const targetNorm = media.englishTitle ? this._normalizeTitle(media.englishTitle) : targetNormJP;
|
||||
const targetNormJP = this._normalize(media.romajiTitle);
|
||||
const targetNorm = media.englishTitle ? this._normalize(media.englishTitle) : targetNormJP;
|
||||
|
||||
const url = `${this.apiBase}/shared/v2/movie/list?keyword=${encodeURIComponent(q)}&limit=48&page=1`;
|
||||
const json = this._getJson(url, this._headers());
|
||||
const list = (((json || {}).result || {}).movies) || [];
|
||||
if (!Array.isArray(list) || !list.length) return [];
|
||||
|
||||
let matches = list.map((movie) => {
|
||||
const movies = (((json || {}).result || {}).movies) || [];
|
||||
if (!Array.isArray(movies) || !movies.length) return [];
|
||||
|
||||
let matches = movies.map((movie) => {
|
||||
const id = movie && movie.id != null ? String(movie.id) : "";
|
||||
const slug = movie && movie.slug ? String(movie.slug) : "";
|
||||
if (!id || !slug) return null;
|
||||
@@ -157,11 +155,11 @@ class AniCrush {
|
||||
|
||||
return {
|
||||
id,
|
||||
slug,
|
||||
pageUrl: slug,
|
||||
title,
|
||||
titleJP,
|
||||
normTitle: this._normalizeTitle(title),
|
||||
normTitleJP: this._normalizeTitle(titleJP),
|
||||
normTitleJP: this._normalize(titleJP),
|
||||
normTitle: this._normalize(title),
|
||||
dub: !!(movie && movie.has_dub),
|
||||
startDate: this._normalizeDate(movie && movie.aired_from ? String(movie.aired_from) : "")
|
||||
};
|
||||
@@ -169,55 +167,90 @@ class AniCrush {
|
||||
|
||||
if (query.dub) matches = matches.filter(m => m.dub);
|
||||
|
||||
let filtered = matches;
|
||||
|
||||
if (wantYear) {
|
||||
filtered = matches.filter(m => {
|
||||
const tMatch = (m.normTitle === targetNorm) || (m.normTitleJP === targetNormJP);
|
||||
const d = m.startDate || {};
|
||||
const dMatch = wantMonth ? ((d.year === wantYear) && (d.month === wantMonth)) : (d.year === wantYear);
|
||||
return tMatch && dMatch;
|
||||
let filtered = matches.filter(m => {
|
||||
const titleMatch = (m.normTitle === targetNorm) || (m.normTitleJP === targetNormJP);
|
||||
const dateMatch =
|
||||
(m.startDate && m.startDate.year === start.year) &&
|
||||
(m.startDate && m.startDate.month === start.month);
|
||||
return titleMatch && dateMatch;
|
||||
});
|
||||
|
||||
if (!filtered.length) {
|
||||
filtered = matches.filter(m => {
|
||||
const a = m.normTitle;
|
||||
const b = targetNorm;
|
||||
const aj = m.normTitleJP;
|
||||
const bj = targetNormJP;
|
||||
|
||||
const fuzzy =
|
||||
(a && b && (a.includes(b) || b.includes(a) || this._levSim(a, b) > 0.72)) ||
|
||||
(aj && bj && (aj.includes(bj) || bj.includes(aj) || this._levSim(aj, bj) > 0.72));
|
||||
|
||||
const d = m.startDate || {};
|
||||
const dMatch = wantMonth ? ((d.year === wantYear) && (d.month === wantMonth)) : (d.year === wantYear);
|
||||
return fuzzy && dMatch;
|
||||
const titleMatch = (m.normTitle === targetNorm) || (m.normTitleJP === targetNormJP);
|
||||
const dateMatch = (m.startDate && m.startDate.year === start.year);
|
||||
return titleMatch && dateMatch;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
if (!filtered.length) {
|
||||
filtered = matches.filter(m => {
|
||||
const titleMatch =
|
||||
m.normTitle.includes(targetNorm) ||
|
||||
m.normTitleJP.includes(targetNormJP) ||
|
||||
targetNorm.includes(m.normTitle) ||
|
||||
targetNormJP.includes(m.normTitleJP) ||
|
||||
this._levSim(m.normTitle, targetNorm) > 0.7 ||
|
||||
this._levSim(m.normTitleJP, targetNormJP) > 0.7;
|
||||
|
||||
const dateMatch =
|
||||
(m.startDate && m.startDate.year === start.year) &&
|
||||
(m.startDate && m.startDate.month === start.month);
|
||||
|
||||
return titleMatch && dateMatch;
|
||||
});
|
||||
}
|
||||
|
||||
if (!filtered.length) {
|
||||
filtered = matches.filter(m => {
|
||||
const titleMatch =
|
||||
m.normTitle.includes(targetNorm) ||
|
||||
m.normTitleJP.includes(targetNormJP) ||
|
||||
targetNorm.includes(m.normTitle) ||
|
||||
targetNormJP.includes(m.normTitleJP) ||
|
||||
this._levSim(m.normTitle, targetNorm) > 0.7 ||
|
||||
this._levSim(m.normTitleJP, targetNormJP) > 0.7;
|
||||
|
||||
const dateMatch = (m.startDate && m.startDate.year === start.year);
|
||||
return titleMatch && dateMatch;
|
||||
});
|
||||
}
|
||||
|
||||
let results = filtered.map(m => ({
|
||||
id: `${m.id}/${query.dub ? "dub" : "sub"}`,
|
||||
title: m.title,
|
||||
url: `${this.baseUrl}/detail/${m.pageUrl}.${m.id}`,
|
||||
subOrDub: query.dub ? "dub" : "sub"
|
||||
}));
|
||||
|
||||
if (!media.startDate || !media.startDate.year) {
|
||||
const qn = this._normalizeTitle(q);
|
||||
filtered = matches.filter(m => {
|
||||
const a = this._normalizeTitle(m.title);
|
||||
const aj = this._normalizeTitle(m.titleJP);
|
||||
return (a === qn) || (aj === qn) || a.includes(qn) || aj.includes(qn) || qn.includes(a) || qn.includes(aj);
|
||||
return (
|
||||
a === qn || aj === qn ||
|
||||
a.includes(qn) || aj.includes(qn) ||
|
||||
qn.includes(a) || qn.includes(aj)
|
||||
);
|
||||
});
|
||||
|
||||
filtered.sort((x, y) => {
|
||||
const A = this._normalizeTitle(x.title);
|
||||
const B = this._normalizeTitle(y.title);
|
||||
filtered.sort((a, b) => {
|
||||
const A = this._normalizeTitle(a.title);
|
||||
const B = this._normalizeTitle(b.title);
|
||||
if (A.length !== B.length) return A.length - B.length;
|
||||
return A.localeCompare(B);
|
||||
});
|
||||
|
||||
results = filtered.map(m => ({
|
||||
id: `${m.id}/${query.dub ? "dub" : "sub"}`,
|
||||
title: m.title,
|
||||
url: `${this.baseUrl}/detail/${m.pageUrl}.${m.id}`,
|
||||
subOrDub: query.dub ? "dub" : "sub"
|
||||
}));
|
||||
}
|
||||
|
||||
const subOrDub = query.dub ? "dub" : "sub";
|
||||
return filtered.map(m => ({
|
||||
id: `${m.id}/${subOrDub}`,
|
||||
title: m.title,
|
||||
url: `${this.baseUrl}/detail/${m.slug}.${m.id}`,
|
||||
subOrDub
|
||||
}));
|
||||
return results;
|
||||
}
|
||||
|
||||
findEpisodes(Id) {
|
||||
@@ -228,19 +261,17 @@ class AniCrush {
|
||||
|
||||
const url = `${this.apiBase}/shared/v2/episode/list?_movieId=${encodeURIComponent(id)}`;
|
||||
const epJson = this._getJson(url, this._headers());
|
||||
const groups = (epJson && epJson.result) ? epJson.result : {};
|
||||
const episodeGroups = (epJson && epJson.result) ? epJson.result : {};
|
||||
|
||||
const episodes = [];
|
||||
|
||||
const keys = Object.keys(groups || {});
|
||||
const keys = Object.keys(episodeGroups || {});
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const group = groups[keys[i]];
|
||||
const group = episodeGroups[keys[i]];
|
||||
if (!Array.isArray(group)) continue;
|
||||
|
||||
for (let j = 0; j < group.length; j++) {
|
||||
const ep = group[j] || {};
|
||||
const num = Number(ep.number);
|
||||
if (!Number.isFinite(num)) continue;
|
||||
|
||||
episodes.push({
|
||||
id: `${id}/${subOrDub}`,
|
||||
number: num,
|
||||
@@ -254,33 +285,23 @@ class AniCrush {
|
||||
return episodes;
|
||||
}
|
||||
|
||||
_getEpisodeSourcesLink(movieId, epNumber, sv, sc) {
|
||||
const linkUrl =
|
||||
`${this.apiBase}/shared/v2/episode/sources?_movieId=${encodeURIComponent(movieId)}` +
|
||||
`&ep=${encodeURIComponent(String(epNumber))}` +
|
||||
`&sv=${encodeURIComponent(String(sv))}` +
|
||||
`&sc=${encodeURIComponent(String(sc))}`;
|
||||
|
||||
const json = this._getJson(linkUrl, this._headers());
|
||||
const link = (((json || {}).result || {}).link) ? String(json.result.link) : "";
|
||||
return { json, link, linkUrl };
|
||||
findEpisodeServer(episode, _server) {
|
||||
if (typeof episode === "string") {
|
||||
try { episode = JSON.parse(episode); } catch (e) {}
|
||||
}
|
||||
episode = episode || {};
|
||||
|
||||
findEpisodeServer(episodeOrId, _server) {
|
||||
let ep = episodeOrId;
|
||||
if (typeof ep === "string") { try { ep = JSON.parse(ep); } catch (e) {} }
|
||||
ep = ep || {};
|
||||
|
||||
const parts = String(ep.id || "").split("/");
|
||||
const parts = String(episode.id || "").split("/");
|
||||
const id = parts[0];
|
||||
const subOrDub = parts[1] || "sub";
|
||||
if (!id) throw new Error("Missing id");
|
||||
|
||||
const num = Number(ep.number);
|
||||
if (!Number.isFinite(num)) throw new Error("Missing episode number");
|
||||
const epNum = Number(episode.number);
|
||||
if (!Number.isFinite(epNum)) throw new Error("Missing episode number");
|
||||
|
||||
let server = String(_server || "").trim();
|
||||
if (!server || server === "default") server = "Southcloud-1";
|
||||
|
||||
if (server === "HD-1") server = "Southcloud-1";
|
||||
if (server === "HD-2") server = "Southcloud-2";
|
||||
if (server === "HD-3") server = "Southcloud-3";
|
||||
@@ -288,26 +309,16 @@ class AniCrush {
|
||||
const serverMap = { "Southcloud-1": 4, "Southcloud-2": 1, "Southcloud-3": 6 };
|
||||
const sv = serverMap[server] != null ? serverMap[server] : 4;
|
||||
|
||||
const scCandidates =
|
||||
subOrDub === "dub"
|
||||
? ["dub", "dubbed", "en", "eng", "2", "1"]
|
||||
: ["sub", "jp", "0", "1"];
|
||||
|
||||
let encryptedIframe = "";
|
||||
let usedSc = null;
|
||||
|
||||
for (let i = 0; i < scCandidates.length; i++) {
|
||||
const sc = scCandidates[i];
|
||||
const r = this._getEpisodeSourcesLink(id, num, sv, sc);
|
||||
if (r.link) {
|
||||
encryptedIframe = r.link;
|
||||
usedSc = sc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const encryptedLinkUrl =
|
||||
`${this.apiBase}/shared/v2/episode/sources?_movieId=${encodeURIComponent(id)}` +
|
||||
`&ep=${encodeURIComponent(String(epNum))}` +
|
||||
`&sv=${encodeURIComponent(String(sv))}` +
|
||||
`&sc=${encodeURIComponent(String(subOrDub))}`;
|
||||
|
||||
const json = this._getJson(encryptedLinkUrl, this._headers());
|
||||
const encryptedIframe = (((json || {}).result || {}).link) ? String(json.result.link) : "";
|
||||
if (!encryptedIframe) {
|
||||
throw new Error(`Missing encrypted iframe link (movieId=${id} ep=${num} server=${server} sv=${sv} scTried=${scCandidates.join(",")})`);
|
||||
throw new Error(`Missing encrypted iframe link (server=${server} sv=${sv} sc=${subOrDub})`);
|
||||
}
|
||||
|
||||
let decryptData = null;
|
||||
@@ -326,24 +337,22 @@ class AniCrush {
|
||||
requiredHeaders = {
|
||||
Referer: "https://megacloud.club/",
|
||||
Origin: "https://megacloud.club",
|
||||
"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",
|
||||
"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",
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
};
|
||||
}
|
||||
|
||||
if (!decryptData) throw new Error("No video sources");
|
||||
if (!decryptData || !decryptData.sources) throw new Error("No video sources from any decrypter");
|
||||
|
||||
const sources = decryptData.sources || [];
|
||||
const streamSource =
|
||||
sources.find((s) => s && s.type === "hls") ||
|
||||
sources.find((s) => s && s.type === "mp4");
|
||||
|
||||
if (!streamSource || !streamSource.file) {
|
||||
throw new Error(`No valid stream file found (scUsed=${usedSc || "none"})`);
|
||||
}
|
||||
if (!streamSource || !streamSource.file) throw new Error("No valid stream file found");
|
||||
|
||||
const tracks = decryptData.tracks || [];
|
||||
const subtitles = tracks
|
||||
const subtitles = (decryptData.tracks || [])
|
||||
.filter((t) => t && t.kind === "captions" && t.file)
|
||||
.map((track, index) => ({
|
||||
id: `sub-${index}`,
|
||||
@@ -352,19 +361,19 @@ class AniCrush {
|
||||
isDefault: !!track.default
|
||||
}));
|
||||
|
||||
const st = String(streamSource.type || "");
|
||||
const outType = (st === "hls" || st === "m3u8") ? "m3u8" : "mp4";
|
||||
const outType = (String(streamSource.type || "") === "hls") ? "m3u8" : "mp4";
|
||||
|
||||
return {
|
||||
server: server,
|
||||
headers: requiredHeaders || {},
|
||||
videoSources: [{
|
||||
videoSources: [
|
||||
{
|
||||
url: streamSource.file,
|
||||
type: outType,
|
||||
quality: "auto",
|
||||
subtitles: subtitles
|
||||
}],
|
||||
_debug: { scUsed: usedSc, sv: sv }
|
||||
subtitles
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -382,13 +391,14 @@ class AniCrush {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
Referer: baseDomain,
|
||||
Origin: `${protocol}://${host}`,
|
||||
"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"
|
||||
"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(embedUrl, headers);
|
||||
|
||||
const fileIdMatch = html.match(/<title>\s*File\s+#([a-zA-Z0-9]+)\s*-/i);
|
||||
if (!fileIdMatch) throw new Error("file_id not found");
|
||||
if (!fileIdMatch) throw new Error("file_id not found in embed page");
|
||||
const fileId = fileIdMatch[1];
|
||||
|
||||
let nonce = null;
|
||||
@@ -403,7 +413,10 @@ class AniCrush {
|
||||
}
|
||||
if (!nonce) throw new Error("nonce not found");
|
||||
|
||||
const sourcesJson = this._getJson(`${baseDomain}embed-2/v3/e-1/getSources?id=${fileId}&_k=${nonce}`, headers);
|
||||
const sourcesJson = this._getJson(
|
||||
`${baseDomain}embed-2/v3/e-1/getSources?id=${fileId}&_k=${nonce}`,
|
||||
headers
|
||||
);
|
||||
|
||||
return {
|
||||
sources: sourcesJson.sources || [],
|
||||
|
||||
Reference in New Issue
Block a user