漫画柜

https://m.manhuagui.com/

lonbeaubrunpjl81 (13720) 05/26 11:24 下载:2328

漫画 comic 漫画 漫画柜 manhuagui
使用GPT生成
二维码导入(APP尚未完成该功能)
// @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(/&lt;/g, "<");
  s = s.replace(/&gt;/g, ">");
  s = s.replace(/&quot;/g, '"');
  s = s.replace(/&#39;/g, "'");
  s = s.replace(/&#12288;/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++;
    }
  }
}
广告