樱花动漫
https://www.yinhuadm.xyz
zpccool (13551) 2天前 下载:715
视频
樱花动漫 - 全 HTTP 解析,chapterContent 纯 HTTP 解密
// @name 樱花动漫
// @uuid yinhuadongman
// @version 2.1.0
// @author AI
// @url https://www.yinhuadm.xyz
// @type video
// @enabled true
// @description 樱花动漫 - 全 HTTP 解析,chapterContent 纯 HTTP 解密
var BASE = "https://www.yinhuadm.xyz";
var PLAYER_BASE = "https://player.mcue.cc";
var MCUE_SALT = "lemon";
// ─── 工具函数 ───────────────────────────────────────────
function absUrl(href) {
if (!href) return "";
if (href.indexOf("http") === 0) return href;
if (href.charAt(0) === "/") return BASE + href;
return BASE + "/" + href;
}
async function httpGetRetry(url, headers) {
var text = "";
var lastErr = null;
for (var i = 0; i < 3; i++) {
try {
text = await legado.http.get(url, headers);
lastErr = null;
break;
} catch (e) {
lastErr = e;
}
}
if (lastErr) throw lastErr;
return text;
}
function cleanTitle(title) {
title = String(title || "").replace(/\s+/g, " ").trim();
if (!title || title.length > 80) return "";
if (/^(更新至第?\d+集|更新至\d+集|已?完结|全\d+集|完结|动漫|HD中字|HD|详情|立即播放|播放)$/.test(title)) return "";
return title;
}
// 提取章节页 player_aaaa JSON(用括号计数避免截断)
function extractPlayerAaaa(html) {
var idx = html.indexOf("player_aaaa");
if (idx === -1) return null;
var start = html.indexOf("{", idx);
if (start === -1) return null;
var depth = 0;
for (var i = start; i < html.length; i++) {
if (html[i] === "{") depth++;
else if (html[i] === "}") {
depth--;
if (depth === 0) {
try {
return JSON.parse(html.substring(start, i + 1));
} catch (e) {
return null;
}
}
}
}
return null;
}
function decodeJsString(value) {
return String(value || "")
.replace(/\\u([0-9a-fA-F]{4})/g, function (_, hex) {
return String.fromCharCode(parseInt(hex, 16));
})
.replace(/\\\//g, "/")
.replace(/\\"/g, '"')
.replace(/\\\\/g, "\\");
}
function extractHtmlAttr(tag, name) {
var quoted = new RegExp("\\s" + name + "\\s*=\\s*([\"'])(.*?)\\1", "i").exec(tag);
if (quoted) return quoted[2];
var plain = new RegExp("\\s" + name + "\\s*=\\s*([^\\s>]+)", "i").exec(tag);
return plain ? plain[1] : "";
}
function extractNowMetaId(html, matchFn) {
var metas = html.match(/<meta\b[^>]*>/gi) || [];
for (var i = 0; i < metas.length; i++) {
if (!matchFn(metas[i])) continue;
var id = extractHtmlAttr(metas[i], "id");
if (id.indexOf("now_") === 0) return id.substring(4);
}
return "";
}
function buildMcueSeed(html) {
var order = extractNowMetaId(html, function (tag) {
return /^utf-8$/i.test(extractHtmlAttr(tag, "charset"));
});
var text = extractNowMetaId(html, function (tag) {
return /^viewport$/i.test(extractHtmlAttr(tag, "name"));
});
if (!order || !text || order.length !== text.length) return "";
var items = [];
for (var i = 0; i < order.length; i++) {
items.push({ id: order.charAt(i), text: text.charAt(i) });
}
items.sort(function (a, b) {
return Number(a.id) - Number(b.id);
});
var seed = "";
for (var j = 0; j < items.length; j++) {
seed += items[j].text;
}
return seed;
}
function extractMcueCipher(html) {
var m = html.match(/\"url\"\s*:\s*\"([^\"]+)\"/);
return m ? decodeJsString(m[1]) : "";
}
async function decryptMcuePlayerHtml(html) {
var cipher = extractMcueCipher(html);
var seed = buildMcueSeed(html);
if (!cipher || !seed) {
legado.log("[chapterContent] player parse failed cipher=" + !!cipher + " seed=" + !!seed);
return "";
}
var md5 = await legado.md5(seed + MCUE_SALT);
var iv = md5.substring(0, 16);
var key = md5.substring(16);
return await legado.aesDecrypt(cipher, key, iv, "CBC");
}
// ─── explore ────────────────────────────────────────────
var CATEGORY_URLS = {
今日更新: "/label/new.html",
热榜: "/label/hot.html",
本周追番: "/label/week.html",
新片上线: "/s/1.html",
};
async function explore(page, category) {
legado.log("[explore] page=" + page + " category=" + category);
if (category === "GETALL") {
return Object.keys(CATEGORY_URLS);
}
var path = CATEGORY_URLS[category] || "/label/new.html";
// 分页
if (page > 1) {
path = path.replace(".html", "/" + page + ".html");
}
var url = BASE + path;
legado.log("[explore] url=" + url);
var html = await httpGetRetry(url, { Referer: BASE });
var doc = legado.dom.parse(html);
var books = [];
var seen = {};
if (category === "热榜") {
var cards = legado.dom.selectAll(doc, ".module-card-item");
for (var c = 0; c < cards.length; c++) {
var detailLink = legado.dom.select(cards[c], ".module-card-item-title a") || legado.dom.select(cards[c], 'a[href*="/v/"]');
if (!detailLink) continue;
var cardHref = legado.dom.attr(detailLink, "href") || "";
if (cardHref.indexOf("/v/") === -1) continue;
var cardUrl = absUrl(cardHref);
if (seen[cardUrl]) continue;
var cardTitle = cleanTitle(legado.dom.selectText(cards[c], ".module-card-item-title strong") || legado.dom.text(detailLink));
if (!cardTitle) {
var cardImg = legado.dom.select(cards[c], "img");
cardTitle = cleanTitle(cardImg ? legado.dom.attr(cardImg, "alt") : "");
}
if (!cardTitle) continue;
var cardImgEl = legado.dom.select(cards[c], "img");
var cardCover = "";
if (cardImgEl) {
cardCover = legado.dom.attr(cardImgEl, "data-original") || legado.dom.attr(cardImgEl, "data-src") || legado.dom.attr(cardImgEl, "src") || "";
}
var cardNoteEl = legado.dom.select(cards[c], ".module-item-note");
var cardLatest = cardNoteEl ? legado.dom.text(cardNoteEl).trim() : "";
seen[cardUrl] = true;
books.push({
name: cardTitle,
bookUrl: cardUrl,
coverUrl: cardCover,
latestChapter: cardLatest,
kind: category,
tocUrl: cardUrl,
});
}
}
if (books.length === 0) {
var links = legado.dom.selectAll(doc, "a.module-poster-item");
if (links.length === 0) {
links = legado.dom.selectAll(doc, 'a[href*="/v/"]');
}
for (var i = 0; i < links.length; i++) {
var href = legado.dom.attr(links[i], "href") || "";
if (href.indexOf("/v/") === -1) continue;
var fullUrl = absUrl(href);
if (seen[fullUrl]) continue;
var title = legado.dom.attr(links[i], "title") || "";
if (!title) {
var titleEl = legado.dom.select(links[i], ".module-poster-item-title");
title = titleEl ? legado.dom.text(titleEl) : legado.dom.text(links[i]);
}
title = cleanTitle(title);
if (!title) {
var altImg = legado.dom.select(links[i], "img");
title = cleanTitle(altImg ? legado.dom.attr(altImg, "alt") : "");
}
if (!title) continue;
seen[fullUrl] = true;
var imgEl = legado.dom.select(links[i], "img");
var cover = "";
if (imgEl) {
cover = legado.dom.attr(imgEl, "data-original") || legado.dom.attr(imgEl, "data-src") || legado.dom.attr(imgEl, "src") || "";
}
var noteEl = legado.dom.select(links[i], ".module-item-note");
var latestChapter = noteEl ? legado.dom.text(noteEl).trim() : "";
books.push({
name: title,
bookUrl: fullUrl,
coverUrl: cover,
latestChapter: latestChapter,
kind: category,
tocUrl: fullUrl,
});
}
}
legado.dom.free(doc);
legado.log("[explore] found=" + books.length);
return books;
}
// ─── search ─────────────────────────────────────────────
async function search(keyword, page) {
legado.log("[search] keyword=" + keyword + " page=" + page);
var url = BASE + "/vch/" + encodeURIComponent(keyword) + ".html";
if (page > 1) {
url = BASE + "/vch/" + encodeURIComponent(keyword) + "/page/" + page + ".html";
}
legado.log("[search] url=" + url);
var html = await httpGetRetry(url, { Referer: BASE });
var doc = legado.dom.parse(html);
var books = [];
var seen = {};
// 搜索结果通常是 .module-poster-item 或包含 /v/ 的链接
var items = legado.dom.selectAll(doc, "a.module-poster-item");
if (items.length === 0) {
// 退化:取全部 /v/ 链接
items = legado.dom.selectAll(doc, 'a[href*="/v/"]');
}
for (var i = 0; i < items.length; i++) {
var href = legado.dom.attr(items[i], "href") || "";
if (href.indexOf("/v/") === -1) continue;
var fullUrl = absUrl(href);
if (seen[fullUrl]) continue;
var title = legado.dom.attr(items[i], "title") || "";
if (!title) {
var tEl = legado.dom.select(items[i], ".module-poster-item-title");
title = tEl ? legado.dom.text(tEl) : "";
}
if (!title) title = legado.dom.attr(items[i], "title") || "";
title = title.trim();
if (!title || title.length > 80) continue;
if (/^(更新至第?\d+集|已?完结|动漫|HD中字|HD|详情|立即播放)$/.test(title)) continue;
seen[fullUrl] = true;
var imgEl = legado.dom.select(items[i], "img");
var cover = "";
if (imgEl) {
cover = legado.dom.attr(imgEl, "data-original") || legado.dom.attr(imgEl, "data-src") || legado.dom.attr(imgEl, "src") || "";
}
var noteEl = legado.dom.select(items[i], ".module-item-note");
var latestChapter = noteEl ? legado.dom.text(noteEl).trim() : "";
books.push({
name: title,
bookUrl: fullUrl,
coverUrl: cover,
latestChapter: latestChapter,
kind: "",
tocUrl: fullUrl,
});
}
// 如果 .module-poster-item 没有找到,尝试另一种模式(多 href 重复但 title 不同)
if (books.length === 0) {
var links = legado.dom.selectAll(doc, 'a[href*="/v/"]');
for (var j = 0; j < links.length; j++) {
var lhref = legado.dom.attr(links[j], "href") || "";
if (lhref.indexOf("/v/") === -1) continue;
var lfull = absUrl(lhref);
var ltitle = (legado.dom.attr(links[j], "title") || legado.dom.text(links[j])).trim();
if (!ltitle || ltitle.length > 80) continue;
if (/^(更新至第?\d+集|已?完结|动漫|HD中字|HD|详情|立即播放)$/.test(ltitle)) continue;
if (seen[lfull + "|" + ltitle]) continue;
seen[lfull + "|" + ltitle] = true;
if (seen[lfull]) continue; // 已有该 URL 的条目
seen[lfull] = true;
var limgEl = legado.dom.select(links[j], "img");
var lcover = "";
if (limgEl) {
lcover = legado.dom.attr(limgEl, "data-original") || legado.dom.attr(limgEl, "data-src") || legado.dom.attr(limgEl, "src") || "";
}
books.push({
name: ltitle,
bookUrl: lfull,
coverUrl: lcover,
latestChapter: "",
kind: "",
tocUrl: lfull,
});
}
}
legado.dom.free(doc);
legado.log("[search] found=" + books.length);
return books;
}
// ─── bookInfo ────────────────────────────────────────────
async function bookInfo(bookUrl) {
legado.log("[bookInfo] url=" + bookUrl);
var html = await httpGetRetry(bookUrl, { Referer: BASE });
var doc = legado.dom.parse(html);
var name = legado.dom.selectText(doc, "h1") || "";
name = name.trim();
// 封面:优先 data-original(懒加载真实地址)
var coverEl = legado.dom.select(doc, ".module-item-cover img") || legado.dom.select(doc, ".module-item-pic img");
var coverUrl = "";
if (coverEl) {
coverUrl = legado.dom.attr(coverEl, "data-original") || legado.dom.attr(coverEl, "data-src") || legado.dom.attr(coverEl, "src") || "";
}
// 简介
var intro = legado.dom.selectText(doc, ".module-info-introduction") || legado.dom.selectText(doc, ".vod_content") || "";
intro = intro.trim();
// 最新话数
var latestChapter = legado.dom.selectText(doc, ".module-item-note") || "";
latestChapter = latestChapter.trim();
// 分类 / 标签
var kindEl = legado.dom.selectAll(doc, ".module-info-tag-link a");
var kinds = [];
for (var i = 0; i < kindEl.length; i++) {
var ktext = legado.dom.text(kindEl[i]).trim();
if (ktext) kinds.push(ktext);
}
legado.dom.free(doc);
legado.log("[bookInfo] name=" + name + " cover=" + coverUrl.substring(0, 40));
return {
name: name,
author: "",
bookUrl: bookUrl,
coverUrl: coverUrl,
intro: intro,
latestChapter: latestChapter,
kind: kinds.join(" "),
tocUrl: bookUrl,
};
}
// ─── chapterList ─────────────────────────────────────────
async function chapterList(tocUrl) {
legado.log("[chapterList] url=" + tocUrl);
var html = await httpGetRetry(tocUrl, { Referer: BASE });
var doc = legado.dom.parse(html);
// 提取所有 /p/ 链接,按 URL pattern 分组
var allLinks = legado.dom.selectAll(doc, 'a[href*="/p/"]');
// 解析 /p/{vid}-{line}-{ep}.html
var episodes = {}; // key: line, value: [{ep, name, url}]
var seen = {};
for (var i = 0; i < allLinks.length; i++) {
var href = legado.dom.attr(allLinks[i], "href") || "";
var m = href.match(/\/p\/(\d+)-(\d+)-(\d+)\.html/);
if (!m) continue;
var fullUrl = absUrl(href);
if (seen[fullUrl]) continue;
var epText = legado.dom.text(allLinks[i]).trim();
// 过滤非集数文字(如"立即播放")
if (!epText || epText.indexOf("第") === -1) {
// 尝试从 title 属性
epText = legado.dom.attr(allLinks[i], "title") || "";
if (epText.indexOf("第") === -1) {
// 自动生成集数名
var epNum = parseInt(m[3]);
epText = "第" + (epNum < 10 ? "0" + epNum : epNum) + "集";
}
}
seen[fullUrl] = true;
var lineNum = parseInt(m[2]);
var epNum2 = parseInt(m[3]);
if (!episodes[lineNum]) episodes[lineNum] = [];
episodes[lineNum].push({ ep: epNum2, name: epText, url: fullUrl });
}
legado.dom.free(doc);
// 取第一条线路(最小line号),排序后返回
var lineNums = Object.keys(episodes).map(function (k) {
return parseInt(k);
});
if (lineNums.length === 0) return [];
lineNums.sort(function (a, b) {
return a - b;
});
var firstLine = lineNums[0];
var lineEps = episodes[firstLine];
lineEps.sort(function (a, b) {
return a.ep - b.ep;
});
var chapters = [];
for (var k = 0; k < lineEps.length; k++) {
var groupLabel = "线路" + firstLine;
// 如果有多条线路,也加入其他线路(作为不同 group)
chapters.push({
name: lineEps[k].name,
url: lineEps[k].url,
group: groupLabel,
});
}
// 如果有多条线路,补充其他线路章节
for (var ln = 1; ln < lineNums.length; ln++) {
var lineN = lineNums[ln];
var lnEps = episodes[lineN];
lnEps.sort(function (a, b) {
return a.ep - b.ep;
});
for (var le = 0; le < lnEps.length; le++) {
chapters.push({
name: lnEps[le].name,
url: lnEps[le].url,
group: "线路" + lineN,
});
}
}
legado.log("[chapterList] total=" + chapters.length + " lines=" + lineNums.length);
return chapters;
}
// ─── chapterContent ──────────────────────────────────────
// 流程(纯 HTTP):
// 1) HTTP 获取章节页 → 提取 player_aaaa.url(MCZY token)
// 2) 若 url 已是 http 开头 → 直接返回
// 3) HTTP 获取 player.mcue.cc 播放器页
// 4) 解析 now_* meta 生成 seed,MD5(seed + lemon) 后 AES-CBC 解密 config.url
async function chapterContent(chapterUrl) {
legado.log("[chapterContent] url=" + chapterUrl);
// Step 1: HTTP 获取章节页
var html = await httpGetRetry(chapterUrl, {
Referer: BASE,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
});
var pd = extractPlayerAaaa(html);
if (!pd) {
legado.log("[chapterContent] player_aaaa not found");
return "";
}
var mczyUrl = String(pd.url || "");
var enc = String(pd.encrypt || "0");
legado.log("[chapterContent] encrypt=" + enc + " from=" + pd.from);
if (enc === "1") {
mczyUrl = decodeURIComponent(mczyUrl);
} else if (enc === "2") {
// base64 解码后 unescape
try {
mczyUrl = decodeURIComponent(atob(mczyUrl));
} catch (e) {}
}
// 已是直接链接
if (mczyUrl.indexOf("http") === 0) {
legado.log("[chapterContent] direct url=" + mczyUrl);
return mczyUrl;
}
if (!mczyUrl) {
legado.log("[chapterContent] empty url");
return "";
}
legado.log("[chapterContent] MCZY=" + mczyUrl.substring(0, 40) + "...");
// Step 2: HTTP 访问播放器页并复刻 setting.js 解密逻辑
var playerPageUrl = PLAYER_BASE + "/yinhua/?url=" + encodeURIComponent(mczyUrl);
try {
var playerHtml = await httpGetRetry(playerPageUrl, {
Referer: BASE,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
});
var m3u8 = await decryptMcuePlayerHtml(playerHtml);
if (m3u8 && String(m3u8).indexOf("http") === 0) {
legado.log("[chapterContent] m3u8=" + m3u8.substring(0, 60));
return String(m3u8);
} else {
legado.log("[chapterContent] decrypt returned: " + String(m3u8 || "").substring(0, 80));
}
} catch (e) {
legado.log("[chapterContent] http decrypt err=" + e.message);
}
return "";
}