漫画柜
https://m.manhuagui.com/
lonbeaubrunpjl81 (13720) 05/26 11:24 下载:2328
漫画 comic 漫画 漫画柜 manhuagui
使用GPT生成
// @name 漫画柜
// @version 2.0.4
// @uuid manhuagui-js-v204
// @author Ai
// @url https://m.manhuagui.com/
// @logo https://www.manhuagui.com/favicon.ico
// @type comic
// @enabled true
// @tags comic,漫画,漫画柜,manhuagui
// @description 漫画柜 v2.0.4
var BASE = "https://www.manhuagui.com";
var MBASE = "https://m.manhuagui.com";
var IMG_HOST = "https://i.hamreus.com";
var UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36";
var PAGE_HEADERS = {
"User-Agent": UA,
"Referer": MBASE + "/",
"Origin": MBASE,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"X-Requested-With": "XMLHttpRequest",
"Cookie": "country=CN"
};
var DESKTOP_HEADERS = {
"User-Agent": UA,
"Referer": BASE + "/",
"Origin": BASE,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"X-Requested-With": "XMLHttpRequest",
"Cookie": "country=CN"
};
var IMG_HEADERS = {
"User-Agent": UA,
"Referer": BASE + "/",
"Origin": BASE,
"Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"X-Requested-With": "XMLHttpRequest",
"Cookie": "country=CN"
};
var CATS = {
"最新更新": "/update/",
"排行榜": "/rank/",
"漫画大全": "/list/",
"连载漫画": "/list/serial/",
"完结漫画": "/list/finish/",
"日本漫画": "/list/japan/",
"港台漫画": "/list/hongkong/",
"欧美漫画": "/list/europe/",
"韩国漫画": "/list/korea/",
"内地漫画": "/list/china/"
};
var LAST_CHAPTER_URL = BASE + "/";
async function TEST(type) {
if (type === "__list__") return ["search", "explore", "bookInfo", "chapterList", "chapterContent"];
if (type === "search") {
var r = await search("火影", 1);
return {
passed: r && r.length > 0,
message: "search results=" + (r ? r.length : 0)
};
}
if (type === "explore") {
var e = await explore(1, "最新更新");
return {
passed: e && e.length > 0,
message: "explore results=" + (e ? e.length : 0)
};
}
if (type === "bookInfo") {
var b = await bookInfo(BASE + "/comic/19898/");
return {
passed: !!b.name && !!b.tocUrl,
message: "book=" + (b.name || "")
};
}
if (type === "chapterList") {
var cs = await chapterList(BASE + "/comic/7580/");
return {
passed: cs && cs.length > 100,
message: "chapters=" + (cs ? cs.length : 0)
};
}
if (type === "chapterContent") {
var text = await chapterContent(BASE + "/comic/19898/228027.html");
var arr = [];
try {
arr = JSON.parse(text);
} catch (e) {}
return {
passed: arr && arr.length > 0,
message: "images=" + (arr ? arr.length : 0)
};
}
return {
passed: true,
message: "漫画柜 TEST ready"
};
}
function log(msg) {
try {
legado.log("[漫画柜] " + String(msg));
} catch (e) {}
}
function toast(msg) {
try {
legado.toast(String(msg));
} catch (e) {}
}
function isBlockedText(s) {
s = "" + (s || "");
if (!s) return true;
if (s.indexOf("人机验证") >= 0) return true;
if (s.indexOf("无法显示此网页") >= 0) return true;
if (s.indexOf("403") >= 0 && s.indexOf("Forbidden") >= 0) return true;
if (s.indexOf("Forbidden") >= 0) return true;
if (s.indexOf("Cloudflare") >= 0) return true;
if (s.indexOf("Just a moment") >= 0) return true;
if (s.indexOf("Checking your browser") >= 0) return true;
if (s.indexOf("cf-challenge") >= 0) return true;
if (s.indexOf("cf-turnstile") >= 0) return true;
if (s.indexOf("Enable JavaScript") >= 0) return true;
if (s.indexOf("访问过于频繁") >= 0) return true;
if (s.indexOf("challenge-platform") >= 0) return true;
if (s.indexOf("cf-browser-verification") >= 0) return true;
return false;
}
async function getText(url, headers) {
try {
var text = await legado.http.get(url, headers || PAGE_HEADERS);
text = "" + text;
if (!isBlockedText(text)) return text;
log("blocked: " + url);
return "";
} catch (e) {
log("GET failed: " + url + " | " + e);
return "";
}
}
function abs(href) {
if (!href) return "";
href = "" + href;
if (href.indexOf("//") === 0) return "https:" + href;
if (href.indexOf("http://") === 0 || href.indexOf("https://") === 0) return href;
if (href.charAt(0) === "/") return BASE + href;
return BASE + "/" + href;
}
function absMobile(href) {
if (!href) return "";
href = "" + href;
if (href.indexOf("//") === 0) return "https:" + href;
if (href.indexOf("http://") === 0 || href.indexOf("https://") === 0) return href;
if (href.charAt(0) === "/") return MBASE + href;
return MBASE + "/" + href;
}
function toDesktop(url) {
url = abs(url);
url = url.replace("https://m.manhuagui.com", BASE);
url = url.replace("http://m.manhuagui.com", BASE);
return url;
}
function toMobile(url) {
url = absMobile(url);
url = url.replace("https://www.manhuagui.com", MBASE);
url = url.replace("http://www.manhuagui.com", MBASE);
return url;
}
function htmlDecode(s) {
s = "" + (s || "");
s = s.replace(/ /g, " ");
s = s.replace(/&/g, "&");
s = s.replace(/</g, "<");
s = s.replace(/>/g, ">");
s = s.replace(/"/g, '"');
s = s.replace(/'/g, "'");
s = s.replace(/ /g, " ");
s = s.replace(/\u00a0/g, " ");
return s;
}
function clean(s) {
s = "" + (s || "");
s = s.replace(/<script[\s\S]*?<\/script>/ig, "");
s = s.replace(/<style[\s\S]*?<\/style>/ig, "");
s = s.replace(/<[^>]+>/g, "");
s = htmlDecode(s);
s = s.replace(/\s+/g, " ");
return s.trim();
}
function plainTextFromHtml(s) {
s = "" + (s || "");
s = s.replace(/<script[\s\S]*?<\/script>/ig, "");
s = s.replace(/<style[\s\S]*?<\/style>/ig, "");
s = s.replace(/<[^>]+>/g, " ");
s = htmlDecode(s);
return clean(s);
}
function pickMeta(text, key) {
text = clean(text);
var p = text.indexOf(key);
if (p < 0) return "";
p = p + key.length;
while (p < text.length) {
var c = text.charAt(p);
if (c === ":" || c === ":" || c === " " || c === " ") p++;
else break;
}
var q = text.length;
var stops = ["作 者", "作者", "类 别", "类别", "更新至", "更新于", "状态", "年份", "地区", "简介"];
for (var i = 0; i < stops.length; i++) {
var k = text.indexOf(stops[i], p + 1);
if (k >= 0 && k < q) q = k;
}
return clean(text.substring(p, q));
}
function getAttrFromTag(tag, name) {
tag = "" + (tag || "");
var re1 = new RegExp(name + "\\s*=\\s*\"([^\"]*)\"", "i");
var re2 = new RegExp(name + "\\s*=\\s*'([^']*)'", "i");
var m = re1.exec(tag);
if (!m) m = re2.exec(tag);
if (m) return htmlDecode(m[1]);
return "";
}
function seenBook(out, url) {
for (var i = 0; i < out.length; i++) {
if (out[i].bookUrl === url) return true;
}
return false;
}
function seenChapter(out, url) {
for (var i = 0; i < out.length; i++) {
if (out[i].url === url) return true;
}
return false;
}
function parseMobileName(text) {
text = clean(text);
text = text.replace(/^\d+\s*/, "");
text = text.replace(/^(连载|完结)\s*/, "");
var p = text.indexOf("作 者");
if (p < 0) p = text.indexOf("作者");
if (p > 0) text = text.substring(0, p);
text = clean(text);
text = text.replace(/\s+/g, "");
return text;
}
function parseMobileCardList(html) {
html = "" + html;
if (!html) return [];
var doc = legado.dom.parse(html);
var links = legado.dom.selectAll(doc, 'a[href*="/comic/"]');
var out = [];
var seen = {};
for (var i = 0; i < links.length; i++) {
var href = legado.dom.attr(links[i], "href") || "";
if (!href) continue;
if (!/\/comic\/[0-9]+\/?$/.test(href)) continue;
var url = toDesktop(absMobile(href));
if (seen[url]) continue;
var text = "";
try {
text = legado.dom.selectText(links[i], "h3") || "";
if (!text) text = legado.dom.selectText(links[i], ".ell") || "";
if (!text) text = legado.dom.attr(links[i], "title") || "";
if (!text) text = legado.dom.text(links[i]) || "";
} catch (e) {
text = legado.dom.text(links[i]) || "";
}
text = clean(text);
if (!text) continue;
var name = parseMobileName(text);
if (!name || name.length > 80) continue;
if (name.indexOf("更多") >= 0) continue;
if (name.indexOf("排行") >= 0) continue;
if (name.indexOf("更新") === 0 && name.length < 8) continue;
var cover = "";
try {
cover = legado.dom.selectAttr(links[i], "img", "data-src") || "";
if (!cover) cover = legado.dom.selectAttr(links[i], "img", "data-original") || "";
if (!cover) cover = legado.dom.selectAttr(links[i], "img", "src") || "";
} catch (e2) {}
var all = clean(legado.dom.text(links[i]) || "");
var author = pickMeta(all, "作 者");
if (!author) author = pickMeta(all, "作者");
var kind = pickMeta(all, "类 别");
if (!kind) kind = pickMeta(all, "类别");
var latest = pickMeta(all, "更新至");
var updateTime = pickMeta(all, "更新于");
var status = "";
if (all.indexOf("完结") >= 0) status = "完结";
if (all.indexOf("连载") >= 0 && !status) status = "连载";
seen[url] = true;
out.push({
name: name,
author: author,
bookUrl: url,
tocUrl: url,
coverUrl: absMobile(cover),
kind: kind || "漫画",
intro: "",
latestChapter: latest,
lastChapter: latest,
updateTime: updateTime,
status: status
});
}
legado.dom.free(doc);
if (out.length === 0) {
out = parseCardListByRegex(html, MBASE);
}
return out;
}
function parseCardListByRegex(html, baseUrl) {
var out = [];
html = "" + html;
var re = /<a[^>]+href=["']([^"']*\/comic\/[0-9]+\/?)["'][^>]*>([\s\S]*?)<\/a>/ig;
var m;
while ((m = re.exec(html)) !== null) {
var href = m[1];
var text = clean(m[2]);
if (!href || !text) continue;
if (text.length > 80) continue;
if (text.indexOf("开始阅读") >= 0) continue;
if (text.indexOf("详情") >= 0 && text.length < 8) continue;
if (text.indexOf("更新至") >= 0) continue;
if (text.indexOf("共") === 0 && text.length < 20) continue;
var url = toDesktop(baseUrl === MBASE ? absMobile(href) : abs(href));
if (seenBook(out, url)) continue;
out.push({
name: text,
author: "",
bookUrl: url,
tocUrl: url,
coverUrl: "",
kind: "漫画",
intro: "",
latestChapter: "",
lastChapter: ""
});
}
return out;
}
async function search(keyword, page) {
if (!keyword) return [];
page = Number(page || 1);
if (page > 1) return [];
var url = MBASE + "/s/" + encodeURIComponent(keyword) + ".html";
log("search: " + url);
var html = await getText(url, PAGE_HEADERS);
if (!html) {
return [{
name: "[调试] 搜索页请求失败",
author: "",
bookUrl: MBASE + "/",
tocUrl: MBASE + "/",
coverUrl: "",
kind: "调试",
intro: "移动端首页能打开,但搜索页在 Legado-Tauri 请求失败。请用浏览器打开 /s/火影.html 测试;如果浏览器能开而书源不能开,说明 Tauri HTTP 请求与浏览器网络环境不一致。",
latestChapter: "",
lastChapter: ""
}];
}
var list = parseMobileCardList(html);
log("search count=" + list.length);
if (list && list.length > 0) return list;
return [{
name: "[调试] 搜索页已返回但解析为空",
author: "",
bookUrl: MBASE + "/",
tocUrl: MBASE + "/",
coverUrl: "",
kind: "调试",
intro: "关键词:" + keyword + "\nHTML长度:" + html.length + "\n说明:请求成功,但页面结构未匹配。请复制搜索页源码中漫画条目的片段继续调试。",
latestChapter: "",
lastChapter: ""
}];
}
async function explore(page, category) {
if (!category || category === "GETALL") {
return Object.keys(CATS);
}
page = Number(page || 1);
var path = CATS[category] || "/update/";
var url;
if (page <= 1) {
url = MBASE + path;
} else {
if (path.charAt(path.length - 1) !== "/") path = path + "/";
url = MBASE + path + "index_p" + page + ".html";
}
log("explore: " + url);
var html = await getText(url, PAGE_HEADERS);
if (!html) return [];
var list = parseMobileCardList(html);
log("explore count=" + list.length);
return list;
}
function extractAuthor(html) {
html = "" + html;
var m = html.match(/漫画作者:([\s\S]*?)<\/li>/i);
if (m) return clean(m[1]);
m = html.match(/"@type":"Person","name":"([^"]+)"/);
if (m) return clean(m[1]);
return "";
}
function extractKind(html) {
html = "" + html;
var m = html.match(/漫画剧情:([\s\S]*?)漫画作者/i);
if (m) return clean(m[1]);
return "";
}
async function bookInfo(bookUrl) {
bookUrl = toDesktop(bookUrl);
var html = await getText(bookUrl, DESKTOP_HEADERS);
if (!html) return {};
var doc = legado.dom.parse(html);
var name = "";
try {
name = legado.dom.selectText(doc, "h1") || "";
} catch (e) {}
var sub = "";
try {
sub = legado.dom.selectText(doc, "h2") || "";
} catch (e2) {}
var cover = "";
try {
cover = legado.dom.selectAttr(doc, ".hcover img", "src") || "";
if (!cover) cover = legado.dom.selectAttr(doc, ".hcover img", "data-src") || "";
if (!cover) cover = legado.dom.selectAttr(doc, 'meta[property="og:image"]', "content") || "";
} catch (e3) {}
var intro = "";
try {
intro = legado.dom.selectText(doc, "#intro-all") || "";
if (!intro) intro = legado.dom.selectText(doc, "#intro-cut") || "";
if (!intro) intro = legado.dom.selectAttr(doc, 'meta[property="og:description"]', "content") || "";
} catch (e4) {}
var text = clean(legado.dom.text(doc) || "");
legado.dom.free(doc);
var author = extractAuthor(html);
if (!author) author = pickMeta(text, "漫画作者");
if (!author) author = pickMeta(text, "作者");
var kind = extractKind(html);
if (!kind) kind = pickMeta(text, "漫画剧情");
if (!kind) kind = pickMeta(text, "类别");
var latest = pickMeta(text, "更新至");
var status = "";
if (text.indexOf("连载中") >= 0) status = "连载";
if (text.indexOf("已完结") >= 0 || text.indexOf("完结") >= 0) status = "完结";
if (sub && intro.indexOf(sub) < 0) {
intro = sub + "\n" + intro;
}
return {
name: clean(name),
author: clean(author),
bookUrl: bookUrl,
tocUrl: bookUrl,
coverUrl: abs(cover),
intro: clean(intro),
kind: clean(kind) || "漫画",
latestChapter: clean(latest),
lastChapter: clean(latest),
status: status
};
}
function extractChapterSection(html) {
html = "" + (html || "");
var p = html.indexOf("章节全集");
if (p < 0) p = html.indexOf("章节列表");
if (p < 0) p = html.indexOf("chapter-list");
if (p < 0) p = html.indexOf("chapter-list-");
if (p < 0) p = 0;
var s = html.substring(p);
var end = s.indexOf("看过《");
if (end < 0) end = s.indexOf("用户还喜欢");
if (end < 0) end = s.indexOf("相关推荐");
if (end < 0) end = s.indexOf("热门漫画");
if (end > 0) s = s.substring(0, end);
return s;
}
function betterChapterName(oldName, newName) {
oldName = clean(oldName);
newName = clean(newName);
if (!oldName) return newName;
if (!newName) return oldName;
if (oldName === "章节") return newName;
if (oldName.indexOf("开始阅读") >= 0) return newName;
if (newName.length > oldName.length) return newName;
return oldName;
}
function upsertChapter(out, item) {
for (var i = 0; i < out.length; i++) {
if (out[i].url === item.url) {
out[i].name = betterChapterName(out[i].name, item.name);
return;
}
}
out.push(item);
}
function normalizeChapterName(name, groupName) {
name = clean(htmlDecode(name));
name = name.replace(/^章节列表\s*/g, "");
name = name.replace(/^开始阅读\s*/g, "");
name = name.replace(/\s+/g, " ");
if (!name) name = "章节";
if (groupName) {
if (name.indexOf(groupName) !== 0 && groupName !== "单话") {
name = groupName + " " + name;
}
}
return name;
}
function chapterOrderFromUrl(url) {
var m = ("" + url).match(/\/comic\/[0-9]+\/([0-9]+)\.html/);
if (m) return parseInt(m[1], 10);
return 0;
}
function parseChapterListFull(html, bookUrl) {
var out = [];
html = extractChapterSection(html);
var id = "";
var mId = bookUrl.match(/\/comic\/([0-9]+)\//);
if (mId) id = mId[1];
var groupName = "";
var tokenRe = /<(h2|h3|h4|h5|strong|dt)[^>]*>([\s\S]*?)<\/\1>|<a\b([^>]*)>([\s\S]*?)<\/a>/ig;
var m;
while ((m = tokenRe.exec(html)) !== null) {
if (m[1]) {
var head = plainTextFromHtml(m[2]);
if (head.indexOf("单话") >= 0) groupName = "单话";
else if (head.indexOf("单行本") >= 0) groupName = "单行本";
else if (head.indexOf("番外") >= 0) groupName = "番外篇";
else if (head.indexOf("短篇") >= 0) groupName = "短篇";
else if (head.indexOf("特别") >= 0) groupName = "特别篇";
else if (head.indexOf("外传") >= 0) groupName = "外传";
continue;
}
var tag = m[3] || "";
var inner = m[4] || "";
var href = getAttrFromTag(tag, "href");
if (!href) continue;
if (href.indexOf("/comic/") < 0) continue;
if (href.indexOf(".html") < 0) continue;
if (id && href.indexOf("/comic/" + id + "/") < 0) continue;
var innerText = plainTextFromHtml(inner);
if (innerText.indexOf("开始阅读") >= 0) continue;
if (innerText.indexOf("加入收藏") >= 0) continue;
if (innerText.indexOf("我要吐槽") >= 0) continue;
if (innerText.indexOf("上一章") >= 0) continue;
if (innerText.indexOf("下一章") >= 0) continue;
var url = toDesktop(abs(href));
var title = getAttrFromTag(tag, "title");
var name = "";
if (title) name = title;
else name = innerText;
name = normalizeChapterName(name, groupName);
upsertChapter(out, {
name: name,
url: url,
vip: false,
order: chapterOrderFromUrl(url)
});
}
return out;
}
async function chapterList(tocUrl) {
var bookUrl = toDesktop(tocUrl);
log("chapterList: " + bookUrl);
var html = await getText(bookUrl, DESKTOP_HEADERS);
if (!html) return [];
var out = parseChapterListFull(html, bookUrl);
if (out.length === 0) {
var mobileUrl = toMobile(bookUrl);
var html2 = await getText(mobileUrl, PAGE_HEADERS);
if (html2) out = parseChapterListFull(html2, bookUrl);
}
log("chapters full=" + out.length);
return out;
}
function getChapterIdFromUrl(url) {
var m = ("" + url).match(/\/comic\/[0-9]+\/([0-9]+)\.html/);
if (m) return m[1];
return "";
}
async function chapterContent(chapterUrl) {
chapterUrl = toDesktop(chapterUrl);
LAST_CHAPTER_URL = chapterUrl;
var expectCid = getChapterIdFromUrl(chapterUrl);
log("chapterContent: " + chapterUrl + " cid=" + expectCid);
var html = await getText(chapterUrl, DESKTOP_HEADERS);
if (!html) return "[]";
var imgs = parseImagesFromScript(html, expectCid);
if (!imgs || imgs.length === 0) {
log("正文图片为空:未解析到当前章节 cInfo。为避免图文不匹配,已禁用 img 标签兜底。");
return "[]";
}
log("images=" + imgs.length);
return JSON.stringify(imgs);
}
function isImageUrl(u) {
u = ("" + (u || "")).toLowerCase();
if (u.indexOf(".jpg") >= 0) return true;
if (u.indexOf(".jpeg") >= 0) return true;
if (u.indexOf(".png") >= 0) return true;
if (u.indexOf(".webp") >= 0) return true;
if (u.indexOf(".gif") >= 0) return true;
return false;
}
function parseLooseJson(j) {
try {
return JSON.parse(j);
} catch (e1) {
try {
j = j.replace(/'/g, '"');
j = j.replace(/([{,])\s*([A-Za-z0-9_]+)\s*:/g, '$1"$2":');
return JSON.parse(j);
} catch (e2) {}
}
return null;
}
function dataMatchesCid(data, cid) {
if (!data || !cid) return true;
var dcid = "";
if (data.cid) dcid = "" + data.cid;
if (!dcid && data.id) dcid = "" + data.id;
if (!dcid && data.chapterId) dcid = "" + data.chapterId;
if (!dcid && data.chapter_id) dcid = "" + data.chapter_id;
if (dcid && dcid === cid) return true;
var path = "" + (data.path || "");
path = path.replace(/\\/g, "/");
if (path.indexOf("/" + cid + "/") >= 0) return true;
if (path.indexOf("/" + cid + ".") >= 0) return true;
if (path.indexOf("/" + cid) >= 0) return true;
if (!dcid && !path) return true;
return false;
}
function parseImagesFromScript(html, expectCid) {
var data = findJsonObjectWithFiles(html, expectCid);
if (!data) {
data = findPackedObject(html, expectCid);
}
if (!data) {
log("未找到当前章节图片数据 cid=" + expectCid);
return [];
}
return buildImageUrls(data, expectCid);
}
function findJsonObjectWithFiles(text, expectCid) {
var s = "" + (text || "");
var pos = 0;
var first = null;
var matched = null;
while (true) {
var p1 = s.indexOf('"files"', pos);
var p2 = s.indexOf("'files'", pos);
var p = -1;
if (p1 < 0) p = p2;
else if (p2 < 0) p = p1;
else p = p1 < p2 ? p1 : p2;
if (p < 0) break;
var start = s.lastIndexOf("{", p);
var end = findBraceEnd(s, start);
if (start >= 0 && end > start) {
var j = s.substring(start, end + 1);
var data = parseLooseJson(j);
if (data && data.files && data.files.length) {
if (!first) first = data;
if (dataMatchesCid(data, expectCid)) {
matched = data;
break;
}
}
}
pos = p + 7;
}
if (matched) return matched;
if (first && !expectCid) return first;
if (first && dataMatchesCid(first, expectCid)) return first;
return null;
}
function findBraceEnd(s, start) {
if (start < 0) return -1;
var deep = 0;
var quote = "";
var esc = false;
for (var i = start; i < s.length; i++) {
var c = s.charAt(i);
if (quote) {
if (esc) esc = false;
else if (c === "\\") esc = true;
else if (c === quote) quote = "";
} else {
if (c === '"' || c === "'") quote = c;
else if (c === "{") deep++;
else if (c === "}") {
deep--;
if (deep === 0) return i;
}
}
}
return -1;
}
function findPackedObject(html, expectCid) {
var s = "" + (html || "");
var p = s.indexOf("}('");
if (p < 0) p = s.indexOf("}(\"");
var first = null;
var matched = null;
while (p >= 0) {
var data = readAndUnpack(s, p + 2, expectCid);
if (data && data.files && data.files.length) {
if (!first) first = data;
if (dataMatchesCid(data, expectCid)) {
matched = data;
break;
}
}
var p1 = s.indexOf("}('", p + 2);
var p2 = s.indexOf("}(\"", p + 2);
if (p1 < 0) p = p2;
else if (p2 < 0) p = p1;
else if (p1 < p2) p = p1;
else p = p2;
}
if (matched) return matched;
if (first && !expectCid) return first;
if (first && dataMatchesCid(first, expectCid)) return first;
return null;
}
function readAndUnpack(s, start, expectCid) {
var q1 = readQuoted(s, start);
if (!q1) return null;
var i = skipComma(s, q1[1] + 1);
var n1 = readNum(s, i);
if (!n1) return null;
i = skipComma(s, n1[1]);
var n2 = readNum(s, i);
if (!n2) return null;
i = skipComma(s, n2[1]);
var q2 = readQuoted(s, i);
if (!q2) return null;
var packed = q1[0];
var radix = parseInt(n1[0], 10);
var count = parseInt(n2[0], 10);
var dictText = q2[0];
var dict;
if (dictText.indexOf("|") >= 0) dict = dictText.split("|");
else dict = lzDecodeBase64(dictText).split("|");
var code = unpackCode(packed, radix, count, dict);
var data = findJsonObjectWithFiles(code, expectCid);
if (!data) {
data = firstObject(code);
}
return data;
}
function skipComma(s, i) {
while (i < s.length) {
var c = s.charAt(i);
if (c === " " || c === "\n" || c === "\r" || c === "\t" || c === ",") i++;
else break;
}
return i;
}
function readNum(s, i) {
var start = i;
while (i < s.length) {
var c = s.charAt(i);
if (c >= "0" && c <= "9") i++;
else break;
}
if (i === start) return null;
return [s.substring(start, i), i];
}
function readQuoted(s, i) {
var q = s.charAt(i);
if (q !== "'" && q !== '"') return null;
var out = "";
var esc = false;
i++;
while (i < s.length) {
var c = s.charAt(i);
if (esc) {
if (c === "x" && i + 2 < s.length) {
out += String.fromCharCode(parseInt(s.substring(i + 1, i + 3), 16));
i = i + 3;
} else if (c === "u" && i + 4 < s.length) {
out += String.fromCharCode(parseInt(s.substring(i + 1, i + 5), 16));
i = i + 5;
} else {
if (c === "n") out += "\n";
else if (c === "r") out += "\r";
else if (c === "t") out += "\t";
else out += c;
i++;
}
esc = false;
} else {
if (c === "\\") {
esc = true;
i++;
} else if (c === q) {
return [out, i];
} else {
out += c;
i++;
}
}
}
return null;
}
function firstObject(code) {
code = "" + (code || "");
var p = code.indexOf("{");
var e = findBraceEnd(code, p);
if (p < 0 || e <= p) return null;
var j = code.substring(p, e + 1);
return parseLooseJson(j);
}
function unpackCode(p, a, c, k) {
var dict = {};
for (var i = c - 1; i >= 0; i--) {
var key = baseEncode(i, a);
if (k[i]) dict[key] = k[i];
else dict[key] = key;
}
var out = "" + (p || "");
out = out.replace(/\b\w+\b/g, function (w) {
if (typeof dict[w] !== "undefined") return dict[w];
return w;
});
out = out.split("\\'").join("'");
return out;
}
function baseEncode(num, base) {
var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
num = Number(num || 0);
base = Number(base || 36);
if (num === 0) return "0";
var out = "";
while (num > 0) {
out = chars.charAt(num % base) + out;
num = Math.floor(num / base);
}
return out;
}
function getPageNoFromFile(f) {
f = "" + (f || "");
f = f.replace(/\\/g, "/");
var name = f;
var p = name.lastIndexOf("/");
if (p >= 0) name = name.substring(p + 1);
var m = name.match(/^0*([0-9]+)\.(jpg|jpeg|png|webp|gif)/i);
if (m) return parseInt(m[1], 10);
m = name.match(/(?:^|[_\-])0*([0-9]{1,4})(?:[_\-\.])/i);
if (m) return parseInt(m[1], 10);
return -1;
}
function shouldSortFiles(files) {
if (!files || files.length < 2) return false;
var ok = 0;
var nums = {};
var i;
for (i = 0; i < files.length; i++) {
var n = getPageNoFromFile(files[i]);
if (n >= 0) {
ok++;
nums["n" + n] = true;
}
}
if (ok !== files.length) return false;
var unique = 0;
for (var k in nums) unique++;
if (unique !== files.length) return false;
return true;
}
function sortFilesIfNeeded(files) {
var arr = [];
var i;
for (i = 0; i < files.length; i++) {
arr.push(files[i]);
}
if (!shouldSortFiles(arr)) {
return arr;
}
arr.sort(function (a, b) {
var na = getPageNoFromFile(a);
var nb = getPageNoFromFile(b);
return na - nb;
});
log("图片文件已按自然页码排序 count=" + arr.length);
return arr;
}
function joinUrl(host, path, file) {
host = "" + (host || "");
path = "" + (path || "");
file = "" + (file || "");
path = path.replace(/\\/g, "/");
file = file.replace(/\\/g, "/");
if (file.indexOf("http://") === 0 || file.indexOf("https://") === 0) {
return file;
}
if (file.indexOf("//") === 0) {
return "https:" + file;
}
var raw = "";
if (path) {
if (path.charAt(path.length - 1) !== "/" && file.charAt(0) !== "/") {
raw = path + "/" + file;
} else {
raw = path + file;
}
} else {
raw = file;
}
if (raw.indexOf("http://") === 0 || raw.indexOf("https://") === 0) {
return raw;
}
if (raw.indexOf("//") === 0) {
return "https:" + raw;
}
if (raw.charAt(0) !== "/" && host.charAt(host.length - 1) !== "/") {
raw = "/" + raw;
}
return host + raw;
}
function buildImageUrls(data, expectCid) {
var arr = [];
if (!data) return arr;
var files = data.files || data.imgs || data.images || [];
if (!files || !files.length) return arr;
files = sortFilesIfNeeded(files);
var host = data.host || data.domain || data.server || IMG_HOST;
host = normalizeHost(host);
var path = data.path || "";
log("cInfo cid=" + (data.cid || data.id || "") + " expectCid=" + expectCid + " files=" + files.length + " path=" + path);
for (var i = 0; i < files.length; i++) {
var f = "" + (files[i] || "");
if (!f) continue;
var u = joinUrl(host, path, f);
u = encodeURI(u);
var qs = [];
if (data.sl) {
if (data.sl.e) qs.push("e=" + encodeURIComponent(data.sl.e));
if (data.sl.m) qs.push("m=" + encodeURIComponent(data.sl.m));
}
if (qs.length === 0 && data.cid && data.md5) {
qs.push("cid=" + encodeURIComponent(data.cid));
qs.push("md5=" + encodeURIComponent(data.md5));
}
if (qs.length > 0) {
u = u + (u.indexOf("?") >= 0 ? "&" : "?") + qs.join("&");
}
arr.push(u);
}
return arr;
}
function normalizeHost(host) {
host = "" + (host || "");
if (!host) return IMG_HOST;
if (host.indexOf("http://") === 0 || host.indexOf("https://") === 0) return host;
if (host.indexOf("//") === 0) return "https:" + host;
if (host.charAt(0) === "/") return BASE + host;
if (host.indexOf(".") >= 0) return "https://" + host;
return IMG_HOST;
}
function prepareImage(url, pageIndex) {
IMG_HEADERS.Referer = LAST_CHAPTER_URL || BASE + "/";
return {
url: url,
headers: IMG_HEADERS
};
}
function lzDecodeBase64(input) {
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
if (input == null) return "";
input = ("" + input).replace(/\s/g, "");
if (input === "") return "";
return lzDecode(input.length, 32, function (index) {
return keyStr.indexOf(input.charAt(index));
});
}
function lzBits(data, numBits, resetValue, getNextValue) {
var bits = 0;
var maxpower = Math.pow(2, numBits);
var power = 1;
var resb;
while (power !== maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position === 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits = bits | ((resb > 0 ? 1 : 0) * power);
power <<= 1;
}
return bits;
}
function lzDecode(length, resetValue, getNextValue) {
var dictionary = [];
var enlargeIn = 4;
var dictSize = 4;
var numBits = 3;
var result = [];
var i;
var w;
var c;
var entry;
var data = {
val: getNextValue(0),
position: resetValue,
index: 1
};
for (i = 0; i < 3; i++) dictionary[i] = i;
var next = lzBits(data, 2, resetValue, getNextValue);
if (next === 0) c = String.fromCharCode(lzBits(data, 8, resetValue, getNextValue));
else if (next === 1) c = String.fromCharCode(lzBits(data, 16, resetValue, getNextValue));
else return "";
dictionary[3] = c;
w = c;
result.push(c);
while (true) {
if (data.index > length) return "";
c = lzBits(data, numBits, resetValue, getNextValue);
if (c === 0) {
dictionary[dictSize++] = String.fromCharCode(lzBits(data, 8, resetValue, getNextValue));
c = dictSize - 1;
enlargeIn--;
} else if (c === 1) {
dictionary[dictSize++] = String.fromCharCode(lzBits(data, 16, resetValue, getNextValue));
c = dictSize - 1;
enlargeIn--;
} else if (c === 2) {
return result.join("");
}
if (enlargeIn === 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
if (dictionary[c]) {
entry = dictionary[c];
} else {
if (c === dictSize) entry = w + w.charAt(0);
else return "";
}
result.push(entry);
dictionary[dictSize++] = w + entry.charAt(0);
enlargeIn--;
w = entry;
if (enlargeIn === 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
}
}