八零电子
http://www.80zw.la
zpccool (13551) 16小时前 下载:422
小说 兼容旧书源 自动转换 小说
由 Android JSON 书源自动转换;原分组: 🎉 精选
// @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(/ /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 };
}