八零电子

http://www.80zw.la

zpccool (13551) 16小时前 下载:422

小说 兼容旧书源 自动转换 小说
由 Android JSON 书源自动转换;原分组: 🎉 精选
二维码导入(APP尚未完成该功能)
// @name        八零电子
// @uuid        balingdianzi
// @version     1.0.0
// @author      AI
// @url         http://www.80zw.la
// @type        novel
// @enabled     true
// @tags        兼容旧书源,自动转换,小说
// @description 由 Android JSON 书源自动转换;原分组: 🎉 精选
var OLD_SOURCE = {
  "bookSourceName": "八零电子",
  "bookSourceGroup": "🎉 精选",
  "bookSourceUrl": "http://www.80zw.la",
  "bookSourceType": 0,
  "customOrder": 228,
  "enabled": true,
  "enabledExplore": true,
  "loginUrl": "",
  "lastUpdateTime": 1734372562268,
  "weight": 0,
  "exploreUrl": "最近更新::/top/lastupdate/{{page}}.html\n最新入库::/top/postdate/{{page}}.html\n总排行榜::/top/allvisit/{{page}}.html\n月排行榜::/top/monthvisit/{{page}}.html\n周排行榜::/top/weekvisit/{{page}}.html\n日排行榜::/top/dayvisit/{{page}}.html\n总推荐榜::/top/allvote/{{page}}.html\n月推荐榜::/top/monthvote/{{page}}.html\n周推荐榜::/top/weekvote/{{page}}.html\n日推荐榜::/top/dayvote/{{page}}.html\n总收藏榜::/top/goodnum/{{page}}.html\n字数排行::/top/size/{{page}}.html\n奇幻修真::/sort3/{{page}}.html\n奇幻魔法::/sort13/{{page}}.html\n异术超能::/sort1/{{page}}.html\n东方传奇::/sort12/{{page}}.html\n王朝争霸::/sort14/{{page}}.html\n江湖武侠::/sort15/{{page}}.html\n未来幻想::/sort9/{{page}}.html\n灵异鬼怪::/sort10/{{page}}.html\n探险揭秘::/sort22/{{page}}.html\n历史传记::/sort6/{{page}}.html\n特种军旅::/sort7/{{page}}.html\n虚拟网游::/sort16/{{page}}.html\n竞技体育::/sort8/{{page}}.html\n魔幻女强::/sort2/{{page}}.html\n都市婚姻::/sort4/{{page}}.html\n百合之恋::/sort5/{{page}}.html\n同人美文::/sort11/{{page}}.html\n穿越架空::/sort17/{{page}}.html\n王室贵族::/sort18/{{page}}.html\n魔法校园::/sort19/{{page}}.html\n乡土布衣::/sort20/{{page}}.html\n官职商战::/sort21/{{page}}.html\n间谍暗战::/sort23/{{page}}.html\n唯美言情::/sort24/{{page}}.html\n诗歌文集::/sort25/{{page}}.html",
  "ruleExplore": {
    "bookList": "id.list_art_2013",
    "name": "a.0@text##TXT.*",
    "author": "a.1@text",
    "intro": ".book_jj@text",
    "kind": "span.0@text&&em@text",
    "lastChapter": "b@text##正文卷.|正文.|VIP卷.|默认卷.|卷_|VIP章节.|免费章节.|章节目录.|最新章节.|[\\((【].*?[求更票谢乐发订合补加架字修Kk].*?[】)\\)]",
    "bookUrl": "a.0@href",
    "coverUrl": "img@src",
    "wordCount": ""
  },
  "searchUrl": "/modules/article/search.php,{\n  \"method\": \"post\",\n  \"body\": \"searchkey={{key}}&searchtype=articlename\"\n}",
  "ruleSearch": {
    "checkKeyWord": "",
    "bookList": ".storelistbt5",
    "name": "a.1@text##\\《|\\》.*",
    "author": "a.2@text",
    "intro": "p.1@text",
    "kind": "p.0@textNodes&&span.1@text&&p.2@text##.*更新.|最新.*|.*:|\\s",
    "lastChapter": "p.2@text##.*最新章节.|正文卷.|正文.|VIP卷.|默认卷.|卷_|VIP章节.|免费章节.|章节目录.|最新章节.|[\\((【].*?[求更票谢乐发订合补加架字修Kk].*?[】)\\)]",
    "bookUrl": "a.1@href",
    "coverUrl": "img@src",
    "wordCount": ""
  },
  "ruleBookInfo": {
    "name": "id.soft_info_para@h1@text##TXT.*",
    "author": ".soft_info_r@a.0@text",
    "intro": "id.mainSoftIntro@text##(^|[。!?]+[”」)】]?)##$1<br>@js:result.replace(/.*推荐给你的朋友!|八零电子书.*|【展开】.*|更多.*TXT.*/g,\"\")",
    "kind": ".soft_info_r@li.6@strong@text&&.soft_info_r@li.7@textNodes##\\s..:.*",
    "lastChapter": ".soft_info_r@li.9@textNodes##正文卷.|正文.|VIP卷.|默认卷.|卷_|VIP章节.|免费章节.|章节目录.|最新章节.|[\\((【].*?[求更票谢乐发订合补加架字修Kk].*?[】)\\)]",
    "coverUrl": ".soft_info_r@img@src",
    "tocUrl": ".soft_info_r@a.-1@href"
  },
  "ruleToc": {
    "chapterList": "id.yulan@li@a",
    "chapterName": "text##正文卷.|正文.|VIP卷.|默认卷.|卷_|VIP章节.|免费章节.|章节目录.|最新章节.|[\\((【].*?[求更票谢乐发订合补加架字修Kk].*?[】)\\)]",
    "chapterUrl": "href"
  },
  "ruleContent": {
    "content": "id.content@textNodes",
    "replaceRegex": "##求书网.*",
    "imageStyle": ""
  },
  "bookSourceComment": "//原站:http://www.qiushu.info",
  "respondTime": 10752,
  "enabledCookieJar": true,
  "userid": ""
};
var BASE = normalizeBase(OLD_SOURCE.bookSourceUrl || "");
var DEFAULT_HEADERS = parseHeaders(OLD_SOURCE.header || "");

function normalizeBase(url) {
  url = String(url || "").trim();
  var hash = url.indexOf("#");
  if (hash >= 0) url = url.substring(0, hash);
  while (url.length > 1 && url.charAt(url.length - 1) === "/") url = url.substring(0, url.length - 1);
  return url;
}

function parseHeaders(raw) {
  if (!raw) return {};
  try {
    var obj = JSON.parse(String(raw).replace(/\{\{source\.getKey\(\)\}\}/g, BASE));
    if (!obj) return {};
    return obj;
  } catch (e) {
    return {};
  }
}

function mergeHeaders(extra) {
  var h = {};
  var k;
  for (k in DEFAULT_HEADERS) h[k] = DEFAULT_HEADERS[k];
  if (extra) for (k in extra) h[k] = extra[k];
  return h;
}

function toAbs(href, baseUrl) {
  if (href === null || href === undefined) return "";
  href = String(href).trim();
  if (!href) return "";
  if (href.indexOf("http://") === 0 || href.indexOf("https://") === 0) return href;
  if (href.indexOf("//") === 0) return "https:" + href;
  var base = baseUrl || BASE;
  if (!base) return href;
  if (href.charAt(0) === "/") {
    var m = String(base).match(/^(https?:\/\/[^\/]+)/);
    return (m ? m[1] : base) + href;
  }
  var p = String(base);
  if (p.indexOf("?") !== -1) p = p.substring(0, p.indexOf("?"));
  if (p.charAt(p.length - 1) !== "/") {
    var ix = p.lastIndexOf("/");
    if (ix > 8) p = p.substring(0, ix + 1);
    else p += "/";
  }
  return p + href;
}

function cleanText(s) {
  if (s === null || s === undefined) return "";
  return String(s).replace(/\u00a0/g, " ").replace(/[ \t\r\n]+/g, " ").trim();
}

function splitRule(rule, sep) {
  if (!rule) return [];
  var s = String(rule);
  var arr = [];
  var start = 0;
  var i = 0;
  while (i <= s.length - sep.length) {
    if (s.substring(i, i + sep.length) === sep) {
      arr.push(s.substring(start, i));
      start = i + sep.length;
      i = start;
    } else {
      i++;
    }
  }
  arr.push(s.substring(start));
  return arr;
}

function applyReplace(value, rule) {
  var parts = splitRule(rule, "##");
  if (parts.length < 2) return cleanText(value);
  var text = String(value || "");
  for (var i = 1; i < parts.length; i += 2) {
    var pat = parts[i];
    var rep = i + 1 < parts.length ? parts[i + 1] : "";
    try {
      text = text.replace(new RegExp(pat, "g"), rep);
    } catch (e) {
      text = text.split(pat).join(rep);
    }
  }
  return cleanText(text);
}

function stripReplaceRule(rule) {
  if (!rule) return "";
  return splitRule(String(rule), "##")[0];
}

function renderTemplate(tpl, ctx, baseUrl) {
  if (tpl === null || tpl === undefined) return "";
  var s = String(tpl);
  s = s.replace(/\{\{source\.getKey\(\)\}\}/g, BASE);
  s = s.replace(/\{\{baseUrl\}\}/g, baseUrl || "");
  s = s.replace(/<,([^>]*)>/g, function(all, inner) {
    return ctx && ctx.page && Number(ctx.page) > 1 ? inner : "";
  });
  s = s.replace(/\{\{key\}\}/g, ctx && ctx.key !== undefined ? encodeURIComponent(ctx.key) : "");
  s = s.replace(/\{\{page\}\}/g, ctx && ctx.page !== undefined ? String(ctx.page) : "1");
  s = s.replace(/\{\{\$([^}]*)\}\}/g, function(all, expr) {
    return cleanText(jsonValue(ctx && ctx.json ? ctx.json : ctx, "$" + expr));
  });
  s = s.replace(/\{\{\@\@([^}]*)\}\}/g, function(all, expr) {
    if (!ctx || !ctx.htmlHandle) return "";
    return evalHtmlRule(ctx.htmlHandle, expr, ctx, baseUrl);
  });
  s = s.replace(/\{(\$[^}]+)\}/g, function(all, expr) {
    return cleanText(jsonValue(ctx && ctx.json ? ctx.json : ctx, expr));
  });
  return s;
}

function stripSourceAnchor(url) {
  url = String(url || "");
  var markers = ["#♤yc", "#yc"];
  for (var i = 0; i < markers.length; i++) {
    var ix = url.indexOf(markers[i]);
    if (ix >= 0) {
      var slash = url.indexOf("/", ix + markers[i].length);
      if (slash >= 0) return url.substring(0, ix) + url.substring(slash);
      return url.substring(0, ix);
    }
  }
  return url;
}

function parseRequestSpec(raw, ctx) {
  var s = String(raw || "").trim();
  s = s.replace(/\{\{(?!key\}\}|page\}\}|\$|@@)[\s\S]*?\}\}/g, "");
  s = s.trim();
  var spec = { url: s, method: "GET", body: "", headers: {} };
  var ix = s.lastIndexOf(",{");
  if (ix >= 0) {
    spec.url = s.substring(0, ix).trim();
    var optText = s.substring(ix + 1).trim();
    try {
      var opt = JSON.parse(optText);
      if (opt.method) spec.method = String(opt.method).toUpperCase();
      if (opt.body) spec.body = opt.body;
      if (opt.headers) spec.headers = opt.headers;
      if (opt.header) spec.headers = opt.header;
    } catch (e) {}
  }
  spec.url = stripSourceAnchor(renderTemplate(spec.url, ctx, ""));
  spec.url = toAbs(spec.url, BASE);
  spec.body = renderTemplate(spec.body, ctx, spec.url);
  spec.headers = mergeHeaders(spec.headers);
  return spec;
}

async function requestBySpec(raw, ctx) {
  var spec = parseRequestSpec(raw, ctx || {});
  legado.log("[compat] " + spec.method + " " + spec.url);
  if (spec.method === "POST") {
    return await legado.http.post(spec.url, spec.body || "", spec.headers);
  }
  return await legado.http.get(spec.url, spec.headers);
}

function looksJson(s) {
  s = String(s || "").trim();
  return s.charAt(0) === "{" || s.charAt(0) === "[";
}

function parseMaybeJson(s) {
  try { return JSON.parse(s); } catch (e) { return null; }
}

function parseLenientJson(s) {
  try { return JSON.parse(s); } catch (e1) {}
  try {
    var cleaned = String(s).replace(/,\s*([\]}])/g, "$1");
    return JSON.parse(cleaned);
  } catch (e2) {
    return null;
  }
}

function tokenParts(path) {
  var s = String(path || "");
  if (s.indexOf("$.") === 0) s = s.substring(2);
  if (s.indexOf("$") === 0) s = s.substring(1);
  if (s.charAt(0) === ".") s = s.substring(1);
  return s ? s.split(".") : [];
}

function deepFind(obj, key, out) {
  if (obj === null || obj === undefined) return;
  if (typeof obj !== "object") return;
  if (Object.prototype.hasOwnProperty.call(obj, key)) out.push(obj[key]);
  if (Object.prototype.toString.call(obj) === "[object Array]") {
    for (var i = 0; i < obj.length; i++) deepFind(obj[i], key, out);
  } else {
    for (var k in obj) deepFind(obj[k], key, out);
  }
}

function applyJsonToken(nodes, tok) {
  var m = String(tok).match(/^([^\[]*)(?:\[([^\]]+)\])?$/);
  var key = m ? m[1] : tok;
  var idx = m ? m[2] : null;
  var out = [];
  for (var i = 0; i < nodes.length; i++) {
    var v = key ? nodes[i][key] : nodes[i];
    if (v === undefined || v === null) continue;
    if (idx === "*") {
      if (Object.prototype.toString.call(v) === "[object Array]") {
        for (var j = 0; j < v.length; j++) out.push(v[j]);
      }
    } else if (idx !== null && idx !== undefined) {
      if (Object.prototype.toString.call(v) === "[object Array]") {
        var n = parseInt(idx, 10);
        if (n < 0) n = v.length + n;
        if (n >= 0 && n < v.length) out.push(v[n]);
      }
    } else {
      out.push(v);
    }
  }
  return out;
}

function jsonSelectAll(obj, path) {
  if (!path) return [];
  path = stripReplaceRule(path);
  if (path.indexOf("$..") === 0) {
    var rest = path.substring(3);
    var first = rest.split(/[.\[]/)[0];
    var found = [];
    deepFind(obj, first, found);
    var tail = rest.substring(first.length);
    if (tail.indexOf(".") === 0) tail = "$" + tail;
    else if (tail.indexOf("[") === 0) tail = "$" + tail;
    else tail = "$";
    if (tail === "$") return found;
    var all = [];
    for (var f = 0; f < found.length; f++) {
      var sub = jsonSelectAll(found[f], tail);
      for (var si = 0; si < sub.length; si++) all.push(sub[si]);
    }
    return all;
  }
  var nodes = [obj];
  var parts = tokenParts(path);
  for (var i = 0; i < parts.length; i++) nodes = applyJsonToken(nodes, parts[i]);
  return nodes;
}

function jsonValue(obj, rule) {
  if (!rule) return "";
  var r = stripReplaceRule(String(rule));
  var nodes = jsonSelectAll(obj, r);
  if (!nodes.length) return "";
  var v = nodes[0];
  if (v === null || v === undefined) return "";
  if (typeof v === "object") return JSON.stringify(v);
  return v;
}

function cssToken(token) {
  var idx = null;
  var slice = null;
  var exclude = null;
  var sel = String(token || "").trim();
  if (sel.indexOf("@@") === 0) sel = sel.substring(2);
  if (sel.indexOf("@css:") === 0) sel = sel.substring(5);
  if (sel.indexOf("//") === 0) sel = xpathToCss(sel);
  if (!sel) return { selector: "", index: null, action: "" };
  if (sel === "text" || sel === "ownText" || sel === "html" || sel === "all" || sel === "textNodes" || sel === "href" || sel === "src" || sel === "_src" || sel === "content" || sel === "data-src" || sel === "data-original" || sel === "value") {
    return { selector: "", index: null, action: sel };
  }
  var bang = sel.indexOf("!");
  if (bang >= 0) {
    var excl = sel.substring(bang + 1);
    sel = sel.substring(0, bang);
    exclude = {};
    var eparts = excl.split(":");
    for (var ei = 0; ei < eparts.length; ei++) {
      if (eparts[ei] !== "") exclude[parseInt(eparts[ei], 10)] = 1;
    }
  }
  if (sel.indexOf("class.") === 0) {
    sel = "." + sel.substring(6).replace(/\s+/g, ".");
  } else if (sel.indexOf("tag.") === 0) {
    sel = sel.substring(4);
  } else if (sel.indexOf("id.") === 0) {
    sel = "#" + sel.substring(3);
  } else if (sel.indexOf("text.") === 0) {
    return { selector: "", index: null, slice: null, exclude: null, action: "selectText:" + sel.substring(5) };
  }
  var bm = sel.match(/^(.*)\[(-?\d+)(?::(-?\d+))?(?::(-?\d+))?\]$/);
  if (bm) {
    sel = bm[1];
    if (bm[2] !== undefined && bm[3] !== undefined) {
      slice = { start: parseInt(bm[2], 10), end: parseInt(bm[3], 10), step: bm[4] !== undefined ? parseInt(bm[4], 10) : 1 };
    } else {
      idx = parseInt(bm[2], 10);
    }
  }
  var rm = sel.match(/^(.*)\.(-?\d+):(-?\d+)(?::(-?\d+))?$/);
  if (rm) {
    sel = rm[1];
    slice = { start: parseInt(rm[2], 10), end: parseInt(rm[3], 10), step: rm[4] !== undefined ? parseInt(rm[4], 10) : 1 };
  }
  var m = sel.match(/^(.*)\.(-?\d+)$/);
  if (m) {
    sel = m[1];
    idx = parseInt(m[2], 10);
  }
  return { selector: sel, index: idx, slice: slice, exclude: exclude, action: "" };
}

function xpathToCss(rule) {
  var r = String(rule || "");
  r = r.replace(/\/text\(\)\s*$/, "").replace(/\/@([A-Za-z0-9_-]+)\s*$/, "");
  var mm = r.match(/^\/\/([A-Za-z0-9_-]+)\[@([A-Za-z0-9_-]+)=['"]([^'"]+)['"]\](?:\/\/([A-Za-z0-9_-]+))?(?:\/([A-Za-z0-9_-]+))?(?:\/([A-Za-z0-9_-]+))?$/);
  if (mm) {
    return mm[1] + "[" + mm[2] + "=\"" + mm[3] + "\"]" + (mm[4] ? " " + mm[4] : "") + (mm[5] ? " " + mm[5] : "") + (mm[6] ? " " + mm[6] : "");
  }
  var idm = r.match(/^\/\/([A-Za-z0-9_-]+)\[@id=['"]([^'"]+)['"]\](?:\/\/([A-Za-z0-9_-]+))?(?:\/([A-Za-z0-9_-]+))?(?:\/([A-Za-z0-9_-]+))?$/);
  if (idm) {
    return idm[1] + "#" + idm[2] + (idm[3] ? " " + idm[3] : "") + (idm[4] ? " " + idm[4] : "") + (idm[5] ? " " + idm[5] : "");
  }
  var clsm = r.match(/^\/\/([A-Za-z0-9_-]+)\[@class=['"]([^'"]+)['"]\](?:\/\/([A-Za-z0-9_-]+))?(?:\/([A-Za-z0-9_-]+))?(?:\/([A-Za-z0-9_-]+))?$/);
  if (clsm) {
    return clsm[1] + "." + clsm[2].replace(/\s+/g, ".") + (clsm[3] ? " " + clsm[3] : "") + (clsm[4] ? " " + clsm[4] : "") + (clsm[5] ? " " + clsm[5] : "");
  }
  var simple = r.match(/^\/\/([A-Za-z0-9_-]+)(?:\/([A-Za-z0-9_-]+))?(?:\/([A-Za-z0-9_-]+))?$/);
  if (simple) return simple[1] + (simple[2] ? " " + simple[2] : "") + (simple[3] ? " " + simple[3] : "");
  return "";
}

function normalizeIndex(n, len) {
  if (n < 0) n = len + n;
  return n;
}

function applyIndexing(arr, t) {
  var out = [];
  if (!arr) return out;
  if (t.exclude) {
    for (var ei = 0; ei < arr.length; ei++) {
      var key = ei;
      if (!t.exclude[key]) out.push(arr[ei]);
    }
    arr = out;
    out = [];
  }
  if (t.index !== null && t.index !== undefined) {
    var n = normalizeIndex(t.index, arr.length);
    if (n >= 0 && n < arr.length) out.push(arr[n]);
    return out;
  }
  if (t.slice) {
    var start = normalizeIndex(t.slice.start, arr.length);
    var end = normalizeIndex(t.slice.end, arr.length);
    var step = t.slice.step || 1;
    if (step === 0) step = 1;
    if (step > 0) {
      if (start < 0) start = 0;
      if (end > arr.length) end = arr.length;
      for (var si = start; si < end; si += step) out.push(arr[si]);
    } else {
      if (start >= arr.length) start = arr.length - 1;
      if (end < -1) end = -1;
      for (var sj = start; sj > end; sj += step) out.push(arr[sj]);
    }
    return out;
  }
  for (var i = 0; i < arr.length; i++) out.push(arr[i]);
  return out;
}

function selectMany(handles, token) {
  var t = cssToken(token);
  if (t.action && t.action.indexOf("selectText:") === 0) {
    var outText = [];
    var needle = t.action.substring(11);
    for (var st = 0; st < handles.length; st++) {
      var h = null;
      if (!h) {
        var links = legado.dom.selectAll(handles[st], "a");
        for (var li = 0; li < links.length; li++) {
          var ltx = legado.dom.text(links[li]) || "";
          if (ltx.indexOf(needle) !== -1) {
            h = links[li];
            outText.push(h);
            break;
          }
        }
      }
      if (!h) {
        var scan = legado.dom.selectAll(handles[st], "p,span,div,li");
        for (var si = 0; si < scan.length; si++) {
          var tx = legado.dom.text(scan[si]) || "";
          if (tx.indexOf(needle) !== -1) {
            h = scan[si];
            outText.push(h);
            break;
          }
        }
      }
    }
    return outText;
  }
  if (!t.selector) return handles;
  var out = [];
  for (var i = 0; i < handles.length; i++) {
    var arr = [];
    try {
      arr = legado.dom.selectAll(handles[i], t.selector);
    } catch (e) {
      arr = [];
    }
    var picked = applyIndexing(arr, t);
    for (var j = 0; j < picked.length; j++) out.push(picked[j]);
  }
  return out;
}

function selectListHtml(root, rule) {
  var r = stripReplaceRule(rule);
  var alts = splitRule(r, "||");
  for (var ai = 0; ai < alts.length; ai++) {
    var got = selectListHtmlSingle(root, alts[ai]);
    if (got.length) return got;
  }
  return [];
}

function selectListHtmlSingle(root, rule) {
  var parts = splitRule(rule, "@");
  var handles = [root];
  for (var i = 0; i < parts.length; i++) {
    if (String(parts[i]).indexOf("put:{") === 0 || String(parts[i]).indexOf("@put:{") === 0) continue;
    var t = cssToken(parts[i]);
    if (t.action && t.action.indexOf("selectText:") === 0) {
      handles = selectMany(handles, parts[i]);
      continue;
    }
    if (t.action && t.action !== "") break;
    handles = selectMany(handles, parts[i]);
  }
  return handles;
}

function evalHtmlBase(root, rule, ctx, baseUrl) {
  if (!rule) return "";
  var r = stripReplaceRule(rule);
  if (r.indexOf("@get:{") === 0) {
    var gm = r.match(/^@get:\{([^}]+)\}/);
    if (gm && ctx && ctx.vars) return ctx.vars[gm[1]] || "";
    return "";
  }
  if (r.indexOf("@put:{") === 0) {
    applyPutRule(root, r, ctx || {}, baseUrl);
    return "";
  }
  var alts = splitRule(r, "||");
  if (alts.length > 1) {
    for (var ai = 0; ai < alts.length; ai++) {
      var av = evalHtmlBase(root, alts[ai], ctx, baseUrl);
      if (cleanText(av)) return av;
    }
    return "";
  }
  if (r === "baseUrl") return baseUrl || "";
  if (r.indexOf("{{") >= 0) return renderTemplate(r, ctx, baseUrl);
  if (r.indexOf("//") === 0 || r.indexOf("//*") === 0) return evalXpathLite(root, r, baseUrl);
  var parts = splitRule(r, "@");
  var handles = [root];
  var action = "text";
  for (var i = 0; i < parts.length; i++) {
    var t = cssToken(parts[i]);
    if (t.action && t.action.indexOf("selectText:") === 0) {
      handles = selectMany(handles, parts[i]);
      continue;
    }
    if (t.action) {
      action = t.action;
      continue;
    }
    handles = selectMany(handles, parts[i]);
  }
  if (!handles.length) return "";
  var h = handles[0];
  try {
    if (action === "href" || action === "src" || action === "_src") return toAbs(legado.dom.attr(h, action) || "", baseUrl);
    if (action === "content" || action === "data-src" || action === "data-original" || action === "value") return legado.dom.attr(h, action) || "";
    if (action === "html" || action === "all") return legado.dom.html(h) || "";
    return legado.dom.text(h) || "";
  } catch (e) {
    return "";
  }
}

function evalXpathLite(root, rule, baseUrl) {
  var r = stripReplaceRule(rule);
  var attr = null;
  var am = r.match(/\/@([A-Za-z0-9_-]+)\s*$/);
  if (am) {
    attr = am[1];
    r = r.substring(0, am.index);
  } else if (/\/text\(\)\s*$/.test(r)) {
    r = r.replace(/\/text\(\)\s*$/, "");
  }
  var selector = "";
  var mm = r.match(/^\/\/([A-Za-z0-9_-]+)\[@([A-Za-z0-9_-]+)=['"]([^'"]+)['"]\](?:\/([A-Za-z0-9_-]+))?$/);
  if (mm) {
    selector = mm[1] + "[" + mm[2] + "=\"" + mm[3] + "\"]" + (mm[4] ? " " + mm[4] : "");
  } else {
    var idm = r.match(/^\/\/([A-Za-z0-9_-]+)\[@id=['"]([^'"]+)['"]\](?:\/([A-Za-z0-9_-]+))?(?:\/([A-Za-z0-9_-]+))?$/);
    if (idm) selector = idm[1] + "#" + idm[2] + (idm[3] ? " " + idm[3] : "") + (idm[4] ? " " + idm[4] : "");
    else {
      var clsm = r.match(/^\/\/([A-Za-z0-9_-]+)\[@class=['"]([^'"]+)['"]\](?:\/\/([A-Za-z0-9_-]+))?(?:\/([A-Za-z0-9_-]+))?$/);
      if (clsm) selector = clsm[1] + "." + clsm[2].replace(/\s+/g, ".") + (clsm[3] ? " " + clsm[3] : "") + (clsm[4] ? " " + clsm[4] : "");
    }
  }
  if (!selector) return "";
  try {
    var el = legado.dom.select(root, selector);
    if (!el) return "";
    if (attr) {
      var val = legado.dom.attr(el, attr) || "";
      if (attr === "href" || attr === "src") return toAbs(val, baseUrl);
      return val;
    }
    return legado.dom.text(el) || "";
  } catch (e) {
    return "";
  }
}

function evalHtmlRule(root, rule, ctx, baseUrl) {
  if (!rule) return "";
  var r = stripReplaceRule(rule);
  var jsInject = null;
  if (r.indexOf("@js:") === 0) {
    try { return String(eval(r.substring(4))) || ""; } catch (e) { return ""; }
  }
  var jm = r.match(/^(.*)<js>([\s\S]*?)<\/js>$/);
  if (jm) { r = jm[1]; jsInject = jm[2]; }
  var pieces = splitRule(r, "&&");
  var out = "";
  for (var i = 0; i < pieces.length; i++) {
    var part = pieces[i];
    var val;
    if (part.indexOf("{{") >= 0 && stripReplaceRule(part).indexOf("@") < 0) val = renderTemplate(part, ctx, baseUrl);
    else val = evalHtmlBase(root, part, ctx, baseUrl);
    val = applyReplace(val, part);
    if (val) out += (out ? " " : "") + val;
  }
  out = cleanText(out);
  if (jsInject) {
    try { var result = out; out = String(eval(jsInject)) || ""; } catch (e) {}
  }
  return out;
}

function applyPutRule(root, rule, ctx, baseUrl) {
  if (!ctx.vars) ctx.vars = {};
  var m = String(rule || "").match(/^@put:\{([\s\S]*)\}$/);
  if (!m) return;
  var body = m[1];
  var re = /([A-Za-z0-9_]+)\s*:\s*(['"])([\s\S]*?)\2/g;
  var hit;
  while ((hit = re.exec(body)) !== null) {
    var expr = hit[3];
    if (expr.indexOf("@@") === 0) expr = expr.substring(2);
    ctx.vars[hit[1]] = evalHtmlRule(root, expr, ctx, baseUrl);
  }
}

function isBadBookName(name) {
  name = cleanText(name);
  return !name || name === "首页" || name === "返回" || name === "电脑版" || name === "书架";
}

function fallbackBookName(doc) {
  var name = cleanText(legado.dom.selectText(doc, "h1") || legado.dom.selectText(doc, "h2") || legado.dom.selectText(doc, ".name") || "");
  if (name) return name;
  var title = cleanText(legado.dom.selectText(doc, "title") || "");
  title = title.replace(/最新章节[\s\S]*$/, "").replace(/全文阅读[\s\S]*$/, "").replace(/_.*$/, "").replace(/-.*/, "").trim();
  return title;
}

function evalJsonRule(obj, rule, ctx) {
  if (!rule) return "";
  if (String(rule).indexOf("{{") >= 0 || String(rule).indexOf("{$") >= 0) {
    return cleanText(renderTemplate(rule, { json: obj, key: ctx ? ctx.key : "", page: ctx ? ctx.page : 1 }, ""));
  }
  var pieces = splitRule(rule, "&&");
  var out = "";
  for (var i = 0; i < pieces.length; i++) {
    var part = pieces[i];
    var val = String(stripReplaceRule(part)).indexOf("$") === 0 ? jsonValue(obj, part) : part;
    val = applyReplace(val, part);
    if (val) out += (out ? " " : "") + val;
  }
  return cleanText(out);
}

function mapJsonBook(obj, rules, ctx) {
  return {
    name: evalJsonRule(obj, rules.name || "", ctx),
    author: evalJsonRule(obj, rules.author || "", ctx),
    bookUrl: toAbs(evalJsonRule(obj, rules.bookUrl || "", ctx), BASE),
    coverUrl: toAbs(evalJsonRule(obj, rules.coverUrl || "", ctx), BASE),
    intro: evalJsonRule(obj, rules.intro || "", ctx),
    kind: evalJsonRule(obj, rules.kind || "", ctx),
    lastChapter: evalJsonRule(obj, rules.lastChapter || "", ctx),
    latestChapter: evalJsonRule(obj, rules.lastChapter || "", ctx),
    wordCount: evalJsonRule(obj, rules.wordCount || "", ctx)
  };
}

function mapHtmlBook(el, rules, ctx, baseUrl) {
  ctx = ctx || {};
  ctx.htmlHandle = el;
  return {
    name: evalHtmlRule(el, rules.name || "", ctx, baseUrl),
    author: evalHtmlRule(el, rules.author || "", ctx, baseUrl),
    bookUrl: toAbs(evalHtmlRule(el, rules.bookUrl || "", ctx, baseUrl), baseUrl),
    coverUrl: toAbs(evalHtmlRule(el, rules.coverUrl || "", ctx, baseUrl), baseUrl),
    intro: evalHtmlRule(el, rules.intro || "", ctx, baseUrl),
    kind: evalHtmlRule(el, rules.kind || "", ctx, baseUrl),
    lastChapter: evalHtmlRule(el, rules.lastChapter || "", ctx, baseUrl),
    latestChapter: evalHtmlRule(el, rules.lastChapter || "", ctx, baseUrl),
    wordCount: evalHtmlRule(el, rules.wordCount || "", ctx, baseUrl)
  };
}

function parseBooks(dataText, rules, ctx, url) {
  var books = [];
  if (!rules) return books;
  if (looksJson(dataText) || String(rules.bookList || "").indexOf("$") === 0) {
    var obj = parseMaybeJson(dataText);
    if (!obj) return books;
    var list = jsonSelectAll(obj, rules.bookList || "$");
    for (var i = 0; i < list.length; i++) {
      var b = mapJsonBook(list[i], rules, ctx || {});
      if (b.name && b.bookUrl) books.push(b);
    }
    return books;
  }
  var doc = legado.dom.parse(dataText);
  var items = selectListHtml(doc, rules.bookList || "a");
  for (var j = 0; j < items.length; j++) {
    var hb = mapHtmlBook(items[j], rules, ctx || {}, url || BASE);
    if (hb.name && hb.bookUrl) books.push(hb);
  }
  legado.dom.free(doc);
  return books;
}

function parseExploreCategories() {
  var raw = OLD_SOURCE.exploreUrl || "";
  var out = [];
  if (!raw) return out;
  var s = String(raw).trim();
  if (s.charAt(0) === "[") {
    try {
      var arr = parseLenientJson(s);
      if (!arr) arr = [];
      for (var i = 0; i < arr.length; i++) {
        if (arr[i] && arr[i].title && arr[i].url) out.push({ name: arr[i].title, url: arr[i].url });
      }
      return out;
    } catch (e) {}
  }
  var lines = s.split(/\r?\n/);
  for (var j = 0; j < lines.length; j++) {
    var line = cleanText(lines[j]);
    if (!line) continue;
    var ix = line.indexOf("::");
    if (ix > 0) {
      var name = cleanText(line.substring(0, ix));
      var url = cleanText(line.substring(ix + 2));
      if (name && url) out.push({ name: name, url: url });
    }
  }
  return out;
}

async function search(keyword, page) {
  page = page || 1;
  var rules = OLD_SOURCE.ruleSearch || {};
  if (!OLD_SOURCE.searchUrl) {
    legado.log("[search] " + OLD_SOURCE.bookSourceName + " has no searchUrl");
    return [];
  }
  var html = await requestBySpec(OLD_SOURCE.searchUrl || "", { key: keyword, page: page });
  var books = parseBooks(html, rules, { key: keyword, page: page }, parseRequestSpec(OLD_SOURCE.searchUrl || "", { key: keyword, page: page }).url);
  legado.log("[search] " + OLD_SOURCE.bookSourceName + " found=" + books.length);
  return books;
}

async function explore(page, category) {
  page = page || 1;
  var cats = parseExploreCategories();
  if (category === "GETALL" || !category) {
    var names = [];
    for (var i = 0; i < cats.length; i++) names.push(cats[i].name);
    return names;
  }
  var cat = null;
  for (var j = 0; j < cats.length; j++) {
    if (cats[j].name === category) { cat = cats[j]; break; }
  }
  if (!cat) return [];
  var urlTpl = cat.url;
  var url = toAbs(stripSourceAnchor(renderTemplate(urlTpl, { page: page }, "")), BASE);
  var html = await legado.http.get(url, DEFAULT_HEADERS);
  var rules = OLD_SOURCE.ruleExplore && OLD_SOURCE.ruleExplore.bookList ? OLD_SOURCE.ruleExplore : OLD_SOURCE.ruleSearch;
  var books = parseBooks(html, rules || {}, { page: page }, url);
  legado.log("[explore] " + OLD_SOURCE.bookSourceName + " found=" + books.length);
  return books;
}

async function bookInfo(bookUrl) {
  var html = await legado.http.get(bookUrl, DEFAULT_HEADERS);
  var rules = OLD_SOURCE.ruleBookInfo || {};
  var result = { tocUrl: bookUrl };
  if (looksJson(html)) {
    var obj = parseMaybeJson(html);
    result.name = evalJsonRule(obj, rules.name || "", {});
    result.author = evalJsonRule(obj, rules.author || "", {});
    result.coverUrl = toAbs(evalJsonRule(obj, rules.coverUrl || "", {}), bookUrl);
    result.intro = evalJsonRule(obj, rules.intro || "", {});
    result.kind = evalJsonRule(obj, rules.kind || "", {});
    result.lastChapter = evalJsonRule(obj, rules.lastChapter || "", {});
    result.latestChapter = result.lastChapter;
    result.wordCount = evalJsonRule(obj, rules.wordCount || "", {});
    result.tocUrl = toAbs(evalJsonRule(obj, rules.tocUrl || "", {}), bookUrl) || bookUrl;
    return result;
  }
  var doc = legado.dom.parse(html);
  var ctx = { htmlHandle: doc, vars: {} };
  if (rules.init) evalHtmlBase(doc, rules.init, ctx, bookUrl);
  result.name = evalHtmlRule(doc, rules.name || "", ctx, bookUrl);
  if (isBadBookName(result.name)) result.name = fallbackBookName(doc);
  result.author = evalHtmlRule(doc, rules.author || "", ctx, bookUrl);
  result.coverUrl = toAbs(evalHtmlRule(doc, rules.coverUrl || "", ctx, bookUrl), bookUrl);
  result.intro = evalHtmlRule(doc, rules.intro || "", ctx, bookUrl);
  result.kind = evalHtmlRule(doc, rules.kind || "", ctx, bookUrl);
  result.lastChapter = evalHtmlRule(doc, rules.lastChapter || "", ctx, bookUrl);
  result.latestChapter = result.lastChapter;
  result.wordCount = evalHtmlRule(doc, rules.wordCount || "", ctx, bookUrl);
  result.tocUrl = toAbs(evalHtmlRule(doc, rules.tocUrl || "", ctx, bookUrl), bookUrl) || bookUrl;
  legado.dom.free(doc);
  return result;
}

async function chapterList(tocUrl) {
  var html = await legado.http.get(tocUrl, DEFAULT_HEADERS);
  var rules = OLD_SOURCE.ruleToc || {};
  var chapters = [];
  if (looksJson(html) || String(rules.chapterList || "").indexOf("$") === 0) {
    var obj = parseMaybeJson(html);
    var list = jsonSelectAll(obj, rules.chapterList || "$");
    for (var i = 0; i < list.length; i++) {
      var name = evalJsonRule(list[i], rules.chapterName || "name", {});
      var url = toAbs(evalJsonRule(list[i], rules.chapterUrl || "url", {}), tocUrl);
      if (name && url) chapters.push({ name: name, url: url });
    }
    return chapters;
  }
  var doc = legado.dom.parse(html);
  var items = selectListHtml(doc, rules.chapterList || "a");
  for (var j = 0; j < items.length; j++) {
    var ctx = { htmlHandle: items[j] };
    var chName = evalHtmlRule(items[j], rules.chapterName || "text", ctx, tocUrl);
    var chUrl = toAbs(evalHtmlRule(items[j], rules.chapterUrl || "href", ctx, tocUrl), tocUrl);
    if (chName && chUrl) chapters.push({ name: chName, url: chUrl });
  }
  legado.dom.free(doc);
  return chapters;
}

async function chapterContent(chapterUrl) {
  var html = await legado.http.get(chapterUrl, DEFAULT_HEADERS);
  var rules = OLD_SOURCE.ruleContent || {};
  var value = "";
  if (looksJson(html) || String(rules.content || "").indexOf("$") === 0) {
    var obj = parseMaybeJson(html);
    value = evalJsonRule(obj, rules.content || "$", {});
  } else {
    var doc = legado.dom.parse(html);
    value = evalHtmlRule(doc, rules.content || "body@text", { htmlHandle: doc }, chapterUrl);
    legado.dom.free(doc);
  }
  value = applyReplace(value, rules.replaceRegex || "");
  return formatContent(value);
}

function formatContent(text) {
  if (!text) return "";
  text = text.replace(/&nbsp;/g, " ");
  text = text.replace(/<br\s*\/?>/gi, "\n");
  text = text.replace(/<\/p>/gi, "\n\n");
  text = text.replace(/<p[^>]*>/gi, "");
  text = text.replace(/<[^>]+>/g, "");
  text = text.replace(/[ \t]+/g, " ");
  text = text.replace(/\n{3,}/g, "\n\n");
  text = text.replace(/^\s+|\s+$/gm, "");
  return text;
}

async function TEST(type) {
  if (type === "__list__") return ["search", "explore"];
  if (type === "search") {
    var key = (OLD_SOURCE.ruleSearch && OLD_SOURCE.ruleSearch.checkKeyWord) || "剑来";
    var r = await search(key, 1);
    return { passed: !!(r && r.length), message: "搜索返回 " + (r ? r.length : 0) + " 条" };
  }
  if (type === "explore") {
    var cats = await explore(1, "GETALL");
    if (!cats || !cats.length) return { passed: false, message: "无发现分类" };
    var b = await explore(1, cats[0]);
    return { passed: !!(b && b.length), message: "发现[" + cats[0] + "]返回 " + (b ? b.length : 0) + " 条" };
  }
  return { passed: false, message: "未知测试类型: " + type };
}

广告