得奇小说网
https://www.deqixs.co
ethereal-essence (13554) 16小时前 下载:309
小说 小说 免费 连载
得奇小说网,免费在线阅读小说
// @uuid 019e120f-2f99-79cb-b0b2-cfe87b90adf0
// @name 得奇小说网
// @version 1.1.0
// @author Ethereal
// @url https://www.deqixs.co
// @logo https://www.deqixs.co/favicon.ico
// @type novel
// @enabled true
// @tags 小说,免费,连载
// @description 得奇小说网,免费在线阅读小说
var BASE = 'https://www.deqixs.co';
var UA_CHROME = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
var BOOK_PAGE_HEADERS = {
'User-Agent': UA_CHROME,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Accept-Encoding': 'gzip, deflate',
'Referer': BASE + '/',
};
var LONG_GET_SECS = 90;
var LONG_POST_SECS = 90;
var BOOK_META_MEM_MS = 300000;
var bookMetaMem = {};
var BOOK_META_DISK_SCOPE = 'deqixs.co_meta';
var BOOK_META_DISK_MS = 86400000;
function cloneBookMeta(m) {
if (!m) return null;
return {
name: m.name,
author: m.author,
coverUrl: m.coverUrl,
intro: m.intro,
kind: m.kind,
lastChapter: m.lastChapter,
tocUrl: m.tocUrl
};
}
function bookMetaMemGet(aid) {
if (!aid) return null;
var key = 'k' + aid;
var row = bookMetaMem[key];
if (!row || !row.meta) return null;
if (Date.now() - row.t > BOOK_META_MEM_MS) {
delete bookMetaMem[key];
return null;
}
legado.log('bookInfo meta RAM hit aid=' + aid);
return cloneBookMeta(row.meta);
}
function bookMetaMemPut(aid, meta) {
if (!aid || !meta || !meta.name) return;
bookMetaMem['k' + aid] = { t: Date.now(), meta: cloneBookMeta(meta) };
}
function bookMetaDiskGet(aid) {
if (!aid || typeof legado.config.read !== 'function') return null;
var raw = legado.config.read(BOOK_META_DISK_SCOPE, 'm' + aid);
if (!raw) return null;
try {
var row = JSON.parse(raw);
if (!row || !row.name || typeof row.t !== 'number') return null;
if (Date.now() - row.t > BOOK_META_DISK_MS) return null;
legado.log('bookInfo meta disk hit aid=' + aid);
return {
name: row.name,
author: row.author || '',
coverUrl: row.coverUrl || '',
intro: row.intro || '',
kind: row.kind || '',
lastChapter: row.lastChapter || ''
};
} catch (e) {
return null;
}
}
function bookMetaDiskPut(aid, meta) {
if (!aid || !meta || !meta.name || typeof legado.config.write !== 'function') return;
var row = {
t: Date.now(),
name: meta.name,
author: meta.author || '',
coverUrl: meta.coverUrl || '',
intro: meta.intro || '',
kind: meta.kind || '',
lastChapter: meta.lastChapter || ''
};
try {
legado.config.write(BOOK_META_DISK_SCOPE, 'm' + aid, JSON.stringify(row));
} catch (e) {}
}
function sleepMs(ms) {
if (typeof setTimeout !== 'function') {
return Promise.resolve();
}
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
function isBookTocIndexUrl(url) {
var p = String(url).replace(/^https?:\/\/[^/?#]+/i, '').split(/[?#]/)[0];
return /\/books\/\d+\/$/.test(p);
}
async function httpGetBookPage(url, timeoutSecs, optHeaders) {
var secs = timeoutSecs || LONG_GET_SECS;
var hdr = optHeaders && typeof optHeaders === 'object' ? optHeaders : BOOK_PAGE_HEADERS;
var lastErr = null;
function noteErr(tag, e) {
lastErr = e;
legado.log(tag + ' ' + e);
}
// Harmony HttpClient often hard-caps ~10s; timeoutSecs may be ignored. Repeated legado.http.get
// frequently succeeds on a later try (~8s) while the first hits the wall.
var nativeGetAttempts = isBookTocIndexUrl(url) ? 5 : 3;
var skipRequestFirst = isBookTocIndexUrl(url);
if (!skipRequestFirst && typeof legado.http.request === 'function') {
try {
var r1 = await legado.http.request(url, {
method: 'GET',
headers: hdr,
timeoutSecs: secs,
});
if (typeof r1 === 'string' && r1.length) {
return r1;
}
} catch (e) {
noteErr('http.request GET', e);
}
}
var gi;
for (gi = 0; gi < nativeGetAttempts; gi++) {
try {
var g1 = await legado.http.get(url, hdr);
if (typeof g1 === 'string' && g1.length) {
return g1;
}
} catch (eg) {
noteErr('http.get try=' + (gi + 1), eg);
}
if (gi < nativeGetAttempts - 1) {
await sleepMs(450);
}
}
if (typeof fetch === 'function') {
try {
var resp = await fetch(url, {
method: 'GET',
headers: hdr,
timeoutSecs: secs,
});
if (resp && typeof resp.text === 'function') {
var ft = await resp.text();
if (typeof ft === 'string' && ft.length) {
return ft;
}
}
} catch (ef) {
noteErr('fetch GET', ef);
}
}
if (skipRequestFirst && typeof legado.http.request === 'function') {
try {
var r2 = await legado.http.request(url, {
method: 'GET',
headers: hdr,
timeoutSecs: secs,
});
if (typeof r2 === 'string' && r2.length) {
return r2;
}
} catch (e2) {
noteErr('http.request GET(after toc retries)', e2);
}
}
try {
return await legado.http.get(url, hdr);
} catch (e3) {
noteErr('http.get final', e3);
if (lastErr) {
throw lastErr;
}
throw e3;
}
}
async function httpPostBookPage(url, body, headers, timeoutSecs) {
var secs = timeoutSecs || LONG_POST_SECS;
if (typeof legado.http.request === 'function') {
try {
var p1 = await legado.http.request(url, {
method: 'POST',
body: body,
headers: headers,
timeoutSecs: secs,
});
if (typeof p1 === 'string' && p1.length) {
return p1;
}
} catch (e) {
legado.log('http.request POST ' + e);
}
}
try {
var po = await legado.http.post(url, body, headers);
if (typeof po === 'string' && po.length) {
return po;
}
} catch (ep) {
legado.log('http.post ' + ep);
}
if (typeof fetch === 'function') {
try {
var hdr = {};
if (headers && typeof headers === 'object') {
for (var k in headers) {
if (Object.prototype.hasOwnProperty.call(headers, k) && k.toLowerCase() !== 'content-length') {
hdr[k] = headers[k];
}
}
}
var resp = await fetch(url, {
method: 'POST',
headers: hdr,
body: body,
timeoutSecs: secs,
});
if (resp && typeof resp.text === 'function') {
var t = await resp.text();
if (typeof t === 'string') {
return t;
}
}
} catch (ef) {
legado.log('fetch POST ' + ef);
}
}
return await legado.http.post(url, body, headers);
}
var AUTHOR_PREFIX = '\u4f5c\u8005\uff1a';
var SKIP_START_READ = '\u5f00\u59cb\u9605\u8bfb';
var SKIP_ADD_SHELF = '\u52a0\u5165\u4e66\u67b6';
var SKIP_RECOMMEND = '\u63a8\u8350\u672c\u4e66';
var SKIP_TXT_DL = 'TXT\u4e0b\u8f7d';
function _trim(s) {
if (!s) return '';
return s.replace(/^[\s\u3000\u00A0]+|[\s\u3000\u00A0]+$/g, '');
}
function coverUrlFromArticleId(articleId) {
var n = parseInt(articleId, 10);
if (!n || n !== n) return '';
var seg = String(Math.floor(n / 1000));
return BASE + '/files/article/image/' + seg + '/' + articleId + '/' + articleId + 's.jpg';
}
function articleIdFromBookUrl(url) {
var m = url.match(/\/books\/(\d+)/);
return m ? m[1] : '';
}
function canonicalTocUrl(bookUrl) {
var aid = articleIdFromBookUrl(bookUrl);
return aid ? (BASE + '/books/' + aid + '/') : bookUrl;
}
function isArticleinfoErrorPage(html) {
return html && html.indexOf('\u51fa\u73b0\u9519\u8bef') >= 0;
}
function extractChapterLinksRegex(html, aid) {
var aidNum = String(parseInt(aid, 10));
if (!aidNum || aidNum === 'NaN') return [];
var re = new RegExp('<a\\s+href="([^"]*\\/books\\/' + aidNum + '\\/(\\d+)\\.html)"[^>]*>([^<]*)<\\/a>', 'gi');
var chapters = [];
var seen = {};
var m;
while ((m = re.exec(html)) !== null) {
var href = m[1];
var url = href.indexOf('http') === 0 ? href : (BASE + href);
var plain = url.split('?')[0].split('#')[0];
if (!/\/books\/\d+\/\d+\.html$/i.test(plain)) {
continue;
}
if (seen[url]) {
continue;
}
var nameTrim = _trim(m[3]);
if (!nameTrim) {
continue;
}
if (nameTrim === SKIP_START_READ || nameTrim === SKIP_ADD_SHELF || nameTrim === SKIP_RECOMMEND || nameTrim === SKIP_TXT_DL) {
continue;
}
seen[url] = true;
chapters.push({ name: nameTrim, url: url });
}
chapters.sort(function(a, b) {
var idA = parseInt(a.url.replace(/.*\/(\d+)\.html.*/, '$1'), 10);
var idB = parseInt(b.url.replace(/.*\/(\d+)\.html.*/, '$1'), 10);
return idA - idB;
});
return chapters;
}
function chapterListFromDom(html) {
var doc = legado.dom.parse(html);
var chapters = [];
var seen = {};
var links = legado.dom.selectAll(doc, 'dl.book.chapterlist a');
for (var i = 0; i < links.length; i++) {
var link = links[i];
var url = legado.dom.attr(link, 'href');
var name = legado.dom.text(link);
if (!url || url.indexOf('javascript') === 0) {
continue;
}
if (url.indexOf('http') !== 0) {
url = BASE + url;
}
if (!/\/books\/\d+\/\d+\.html$/i.test(url.split('?')[0].split('#')[0])) {
continue;
}
if (seen[url]) {
continue;
}
var nameTrim = _trim(name);
if (nameTrim === SKIP_START_READ || nameTrim === SKIP_ADD_SHELF || nameTrim === SKIP_RECOMMEND || nameTrim === SKIP_TXT_DL) {
continue;
}
seen[url] = true;
chapters.push({ name: nameTrim, url: url });
}
chapters.sort(function(a, b) {
var idA = parseInt(a.url.replace(/.*\/(\d+)\.html.*/, '$1'), 10);
var idB = parseInt(b.url.replace(/.*\/(\d+)\.html.*/, '$1'), 10);
return idA - idB;
});
return chapters;
}
function parseBookInfoFromDoc(doc) {
var title = legado.dom.selectAttr(doc, 'meta[property="og:title"]', 'content');
if (!_trim(title)) {
title = legado.dom.selectText(doc, 'h1.booktitle');
}
var author = legado.dom.selectAttr(doc, 'meta[property="og:novel:author"]', 'content');
if (!_trim(author)) {
var authorEl = legado.dom.select(doc, 'p.booktag a.red');
var authorTitle = legado.dom.attr(authorEl, 'title');
if (authorTitle && authorTitle.indexOf(AUTHOR_PREFIX) === 0) {
author = authorTitle.substring(AUTHOR_PREFIX.length);
} else {
author = legado.dom.text(authorEl);
}
}
var coverUrl = legado.dom.selectAttr(doc, 'meta[property="og:image"]', 'content');
if (!_trim(coverUrl)) {
coverUrl = legado.dom.attr(legado.dom.select(doc, 'img.thumbnail'), 'src');
}
var kind = legado.dom.selectAttr(doc, 'meta[property="og:novel:category"]', 'content');
if (!_trim(kind)) {
kind = legado.dom.selectText(doc, 'ol.breadcrumb li:nth-child(2) a');
}
var lastChapter = legado.dom.selectAttr(doc, 'meta[property="og:novel:latest_chapter_name"]', 'content');
if (!_trim(lastChapter)) {
lastChapter = legado.dom.text(legado.dom.select(doc, 'a.bookchapter'));
}
var introDoc = legado.dom.select(doc, 'p.bookintro');
legado.dom.remove(introDoc, 'img');
var intro = legado.dom.text(introDoc);
return {
name: _trim(title),
author: _trim(author),
coverUrl: _trim(coverUrl),
intro: _trim(intro),
kind: _trim(kind),
lastChapter: _trim(lastChapter)
};
}
function escapeRegExpMeta(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function metaPropertyContent(html, prop) {
if (!html || !prop) return '';
var esc = escapeRegExpMeta(prop);
var re1 = new RegExp('<meta[^>]*property="' + esc + '"[^>]*content="([^"]*)"', 'i');
var m = html.match(re1);
if (m) return _trim(m[1]);
var re2 = new RegExp('<meta[^>]*content="([^"]*)"[^>]*property="' + esc + '"', 'i');
m = html.match(re2);
return m ? _trim(m[1]) : '';
}
function decodeBasicEntities(s) {
if (!s) return '';
return s
.replace(/ /gi, ' ')
.replace(/ /gi, ' ')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, "'");
}
function parseBookInfoFast(html) {
if (!html) return null;
var title = metaPropertyContent(html, 'og:title');
if (!_trim(title)) {
var h1m = html.match(/<h1[^>]*class="[^"]*booktitle[^"]*"[^>]*>([^<]*)</i);
title = h1m ? h1m[1] : '';
}
var author = metaPropertyContent(html, 'og:novel:author');
if (!_trim(author)) {
var atm = html.match(/<a[^>]*class="[^"]*red[^"]*"[^>]*title="\u4f5c\u8005\uff1a([^"]*)"[^>]*>/i);
author = atm ? atm[1] : '';
}
var coverUrl = metaPropertyContent(html, 'og:image');
if (!_trim(coverUrl)) {
var imgm = html.match(/<img[^>]*class="[^"]*thumbnail[^"]*"[^>]*src="([^"]+)"/i);
if (!imgm) {
imgm = html.match(/<img[^>]*src="([^"]+)"[^>]*class="[^"]*thumbnail[^"]*"/i);
}
if (imgm) {
coverUrl = imgm[1];
}
}
var kind = metaPropertyContent(html, 'og:novel:category');
if (!_trim(kind)) {
var bcm = html.match(/<ol[^>]*class="[^"]*breadcrumb[^"]*"[^>]*>([\s\S]*?)<\/ol>/i);
if (bcm) {
var inner = bcm[1];
var la = [];
var reA = /<a[^>]*>([^<]+)<\/a>/gi;
var mm;
while ((mm = reA.exec(inner)) !== null) {
la.push(_trim(mm[1]));
}
if (la.length >= 2) {
kind = la[1];
}
}
}
var lastChapter = metaPropertyContent(html, 'og:novel:latest_chapter_name');
if (!_trim(lastChapter)) {
var bcm2 = html.match(/<a[^>]*class="[^"]*bookchapter[^"]*"[^>]*title="([^"]*)"/i);
if (bcm2) {
lastChapter = bcm2[1];
} else {
var bcm3 = html.match(/<a[^>]*class="[^"]*bookchapter[^"]*"[^>]*>([^<]+)<\/a>/i);
if (bcm3) {
lastChapter = bcm3[1];
}
}
}
var intro = '';
var introMatch = html.match(/<p[^>]*class="[^"]*bookintro[^"]*"[^>]*>([\s\S]*?)<\/p>/i);
if (introMatch) {
intro = introMatch[1].replace(/<img\b[^>]*>/gi, '').replace(/<[^>]+>/g, '');
intro = decodeBasicEntities(intro);
}
return {
name: _trim(title),
author: _trim(author),
coverUrl: _trim(coverUrl),
intro: _trim(intro),
kind: _trim(kind),
lastChapter: _trim(lastChapter)
};
}
async function search(keyword, page) {
legado.log('searching: ' + keyword);
var p = parseInt(page, 10);
if (!p || p < 1) {
p = 1;
}
var q =
'searchkey=' +
encodeURIComponent(keyword) +
'&action=search&searchtype=articlename&page=' +
encodeURIComponent(String(p));
var searchUrl = BASE + '/modules/article/search.php?' + q;
legado.log('search GET: ' + searchUrl);
var html = '';
try {
html = await httpGetBookPage(searchUrl, LONG_GET_SECS);
} catch (e) {
legado.log('search GET err: ' + e);
html = '';
}
if (!html || html.length < 80 || (html.indexOf('bookbox') < 0 && html.indexOf('og:novel:book_name') < 0)) {
legado.log('search POST fallback');
var body = 'searchkey=' + encodeURIComponent(keyword) + '&action=search&searchtype=articlename';
if (p > 1) {
body += '&page=' + encodeURIComponent(String(p));
}
var headers = {
'User-Agent': UA_CHROME,
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Content-Length': String(body.length),
'Referer': BASE + '/',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
};
try {
html = await httpPostBookPage(BASE + '/modules/article/search.php', body, headers, LONG_POST_SECS);
} catch (e2) {
legado.log('search POST err: ' + e2);
html = '';
}
}
if (!html) {
legado.log('search returned empty response');
return [];
}
legado.log('search response length: ' + html.length);
var doc = legado.dom.parse(html);
var ogTitle = legado.dom.selectAttr(doc, 'meta[property="og:novel:book_name"]', 'content');
var ogUrl = legado.dom.selectAttr(doc, 'meta[property="og:novel:read_url"]', 'content');
var ogAuthor = legado.dom.selectAttr(doc, 'meta[property="og:novel:author"]', 'content');
var ogCover = legado.dom.selectAttr(doc, 'meta[property="og:image"]', 'content');
var ogLatest = legado.dom.selectAttr(doc, 'meta[property="og:novel:latest_chapter_name"]', 'content');
var ogKind = legado.dom.selectAttr(doc, 'meta[property="og:novel:category"]', 'content');
if (ogTitle && ogUrl) {
legado.log('single result: ' + ogTitle);
return [{
name: _trim(ogTitle),
author: _trim(ogAuthor),
bookUrl: ogUrl,
coverUrl: _trim(ogCover),
kind: _trim(ogKind),
lastChapter: _trim(ogLatest)
}];
}
var items = legado.dom.selectAll(doc, 'div.bookbox');
if (!items || items.length === 0) {
legado.log('no results found');
return [];
}
legado.log('found ' + items.length + ' results');
var results = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
var titleEl = legado.dom.select(item, 'h4.bookname a');
var title = legado.dom.text(titleEl);
var url = legado.dom.attr(titleEl, 'href');
if (!title || !url) continue;
if (url.indexOf('http') !== 0) {
url = BASE + url;
}
var authorText = legado.dom.text(legado.dom.select(item, 'div.author'));
var author = authorText.indexOf(AUTHOR_PREFIX) === 0 ? authorText.substring(AUTHOR_PREFIX.length) : authorText;
var lastChapterEl = legado.dom.select(item, 'div.cat a');
var lastChapter = legado.dom.text(lastChapterEl);
var articleIdMatch = url.match(/\/books\/(\d+)\//);
var articleId = articleIdMatch ? articleIdMatch[1] : '';
var coverUrl = coverUrlFromArticleId(articleId);
results.push({
name: _trim(title),
author: _trim(author),
bookUrl: url,
coverUrl: coverUrl,
kind: '',
lastChapter: _trim(lastChapter)
});
}
return results;
}
async function bookInfo(bookUrl) {
legado.log('bookInfo: ' + bookUrl);
var aid = articleIdFromBookUrl(bookUrl);
var tocUrlCanonical = canonicalTocUrl(bookUrl);
var html = '';
var meta = null;
if (aid) {
var cached = bookMetaMemGet(aid);
if (cached) {
cached.tocUrl = tocUrlCanonical;
return cached;
}
var diskMeta = bookMetaDiskGet(aid);
if (diskMeta) {
diskMeta.tocUrl = tocUrlCanonical;
bookMetaMemPut(aid, diskMeta);
return diskMeta;
}
var liteUrl = BASE + '/modules/article/articleinfo.php?id=' + encodeURIComponent(aid);
legado.log('bookInfo lite: ' + liteUrl);
try {
html = await httpGetBookPage(liteUrl, 45);
} catch (e) {
legado.log('bookInfo lite err: ' + e);
html = '';
}
if (html && isArticleinfoErrorPage(html)) {
html = '';
}
if (html && html.length > 200) {
meta = parseBookInfoFast(html);
if (!meta || !meta.name) {
legado.log('bookInfo lite dom fallback');
try {
var docLite = legado.dom.parse(html);
meta = parseBookInfoFromDoc(docLite);
} catch (e2) {
legado.log('bookInfo lite dom err: ' + e2);
}
}
if (meta && meta.name) {
meta.tocUrl = tocUrlCanonical;
bookMetaMemPut(aid, meta);
bookMetaDiskPut(aid, meta);
legado.log('bookInfo from lite ok');
return meta;
}
}
}
legado.log('bookInfo full toc: ' + tocUrlCanonical);
try {
html = await httpGetBookPage(tocUrlCanonical, LONG_GET_SECS);
} catch (e3) {
legado.log('bookInfo full toc err: ' + e3);
html = '';
}
if (!aid) {
try {
html = await httpGetBookPage(bookUrl, LONG_GET_SECS);
} catch (e4) {
legado.log('bookInfo bookUrl err: ' + e4);
html = '';
}
}
if (!html) {
legado.log('bookInfo returned empty');
return null;
}
meta = parseBookInfoFast(html);
if (!meta || !meta.name) {
legado.log('bookInfo full regex fallback dom');
var doc = legado.dom.parse(html);
meta = parseBookInfoFromDoc(doc);
}
if (!meta || !meta.name) {
legado.log('bookInfo missing title');
return null;
}
meta.tocUrl = tocUrlCanonical;
if (aid) {
bookMetaMemPut(aid, meta);
bookMetaDiskPut(aid, meta);
}
return meta;
}
async function chapterList(tocUrl) {
legado.log('chapterList: ' + tocUrl);
var aid = articleIdFromBookUrl(tocUrl);
var html = '';
var chapters = [];
var maxAttempts = 3;
for (var attempt = 1; attempt <= maxAttempts; attempt++) {
try {
html = await httpGetBookPage(tocUrl, LONG_GET_SECS);
} catch (e) {
legado.log('chapterList GET err try=' + attempt + ' ' + e);
html = '';
}
if (html && html.length > 800) {
chapters = aid ? extractChapterLinksRegex(html, aid) : [];
if (!chapters.length) {
legado.log('chapterList regex empty, dom fallback');
chapters = chapterListFromDom(html);
}
if (chapters.length) {
legado.log('chapterList: ' + chapters.length + ' chapters (try ' + attempt + ')');
return chapters;
}
}
if (attempt < maxAttempts) {
legado.log('chapterList retry after empty/slow toc (try ' + attempt + '/' + maxAttempts + ')');
await sleepMs(400);
}
}
legado.log('chapterList returned empty after ' + maxAttempts + ' tries');
return [];
}
function extractChapterHeadingsFromPage(pageHtml) {
var list = [];
if (!pageHtml) return list;
var m = pageHtml.match(/<li[^>]*class="[^"]*active[^"]*"[^>]*>([^<]+)<\/li>/i);
if (m && m[1]) {
list.push(_trim(m[1]));
}
m = pageHtml.match(/<h1[^>]*class="[^"]*pt10[^"]*"[^>]*>\s*([^<]+)/i);
if (m && m[1]) {
var h1t = _trim(m[1]);
h1t = h1t.replace(/\s*\(\u7b2c\/\u9875\)\s*$/, '');
list.push(h1t);
}
m = pageHtml.match(/<title>([^<]+)<\/title>/i);
if (m && m[1]) {
var parts = m[1].split('_');
if (parts.length >= 3) {
list.push(_trim(parts[parts.length - 2]));
}
}
var seen = {};
var out = [];
for (var i = 0; i < list.length; i++) {
var s = list[i];
if (!s || s.length < 2 || seen[s]) {
continue;
}
seen[s] = true;
out.push(s);
}
return out;
}
function stripDuplicateChapterHeadings(plain, headings) {
if (!plain || !headings || !headings.length) {
return plain;
}
var c = plain;
var guard = 0;
while (guard < 12) {
guard++;
var t = _trim(c);
if (!t) {
break;
}
var changed = false;
for (var h = 0; h < headings.length; h++) {
var ph = headings[h];
if (!ph) {
continue;
}
if (t.indexOf(ph) === 0) {
c = c.substring(c.indexOf(ph) + ph.length);
c = c.replace(/^[\s\u3000\u00A0\n\r]+/, '');
changed = true;
break;
}
var nl = t.indexOf('\n');
var line0 = nl < 0 ? t : t.substring(0, nl);
line0 = _trim(line0);
if (line0 === ph) {
c = nl < 0 ? '' : t.substring(nl + 1);
changed = true;
break;
}
}
if (!changed) {
break;
}
}
return c;
}
async function chapterContent(chapterUrl) {
legado.log('chapterContent: ' + chapterUrl);
var articleId = '';
var chapterId = '';
var parts = chapterUrl.split('/');
for (var i = 0; i < parts.length; i++) {
if (parts[i] === 'books' && i + 1 < parts.length) {
articleId = parts[i + 1];
}
}
var cidMatch = chapterUrl.match(/\/(\d+)\.html/);
if (cidMatch) {
chapterId = cidMatch[1];
}
if (!articleId || !chapterId) {
legado.log('Cannot extract IDs from URL: ' + chapterUrl);
return '';
}
legado.log('articleId=' + articleId + ' chapterId=' + chapterId);
var headers = {
'User-Agent': UA_CHROME,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Referer': BASE + '/',
};
var pageHtml = await httpGetBookPage(chapterUrl, LONG_GET_SECS, headers);
if (!pageHtml) {
legado.log('chapter page returned empty');
return '';
}
legado.log('chapter page loaded, length: ' + pageHtml.length);
var tokenUrl = BASE + '/scripts/chapter.js.php?aid=' + articleId + '&cid=' + chapterId + '&referrer=' + encodeURIComponent(chapterUrl);
var tokenHeaders = {
'User-Agent': UA_CHROME,
'Referer': chapterUrl,
'Accept': '*/*',
};
var tokenHtml = await httpGetBookPage(tokenUrl, 45, tokenHeaders);
if (!tokenHtml) {
legado.log('Token request returned empty');
return '';
}
legado.log('token response: ' + tokenHtml.substring(0, 150));
var tokenMatch = tokenHtml.match(/var chapterToken = '([^']+)'/);
var tsMatch = tokenHtml.match(/var timestamp = (\d+)/);
var nonceMatch = tokenHtml.match(/var nonce = '([^']+)'/);
if (!tokenMatch || !tsMatch || !nonceMatch) {
legado.log('Token parse failed');
return '';
}
var token = tokenMatch[1];
var timestamp = tsMatch[1];
var nonce = nonceMatch[1];
legado.log('token=' + token + ' ts=' + timestamp + ' nonce=' + nonce);
var apiUrl = BASE + '/modules/article/ajax2.php?aid=' + articleId + '&cid=' + chapterId + '&token=' + token + '×tamp=' + timestamp + '&nonce=' + nonce;
var apiHeaders = {
'User-Agent': UA_CHROME,
'Referer': chapterUrl,
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json, text/javascript, */*; q=0.01',
};
var apiResponse = await httpGetBookPage(apiUrl, 45, apiHeaders);
legado.log('API response: ' + apiResponse.substring(0, 200));
var data;
try {
data = JSON.parse(apiResponse);
} catch (e) {
legado.log('JSON parse error: ' + e);
return '';
}
if (!data || data.status !== 1 || !data.data || !data.data.content) {
legado.log('chapter API status not success');
return '';
}
var content = data.data.content;
content = content.replace(/^\s*<h[1-4][^>]*>[^<]*<\/h[1-4]>\s*(<br\s*\/?>\s*)*/i, '');
content = content.replace(/^\s*<p[^>]*>\s*<strong>[^<]{1,200}<\/strong>\s*<\/p>\s*(<br\s*\/?>\s*)*/i, '');
content = content.replace(/<br\s*\/?>/gi, '\n');
content = content.replace(/<[^>]+>/g, '');
content = content.replace(/ /g, ' ');
content = content.replace(/ /g, ' ');
content = content.replace(/&/g, '&');
content = content.replace(/</g, '<');
content = content.replace(/>/g, '>');
content = content.replace(/"/g, '"');
content = content.replace(/'/g, "'");
content = content.replace(/\n{3,}/g, '\n\n');
content = content.replace(/[\u200B\u200C\u200D\uFEFF]/g, '');
content = _trim(content);
var heads = extractChapterHeadingsFromPage(pageHtml);
content = stripDuplicateChapterHeadings(content, heads);
content = _trim(content);
legado.log('content length: ' + content.length);
return content;
}
async function explore(page, category) {
legado.log('explore: page=' + page + ' category=' + category);
if (category === 'GETALL' || !category) {
return [
'\u5168\u90e8',
'\u7384\u5e7b',
'\u90fd\u5e02',
'\u4ed9\u4fa0',
'\u5386\u53f2',
'\u79d1\u5e7b',
'\u8bf8\u5929',
'\u60ac\u7591',
'\u4f53\u80b2',
'\u6e38\u620f',
'\u7efc\u5408'
];
}
var categoryMap = {
'\u5168\u90e8': '0',
'\u7384\u5e7b': '1',
'\u90fd\u5e02': '2',
'\u4ed9\u4fa0': '3',
'\u5386\u53f2': '4',
'\u79d1\u5e7b': '5',
'\u8bf8\u5929': '6',
'\u60ac\u7591': '7',
'\u4f53\u80b2': '8',
'\u6e38\u620f': '9',
'\u7efc\u5408': '10'
};
var sortId = categoryMap[category];
if (!sortId) {
legado.log('unknown category: ' + category);
return [];
}
var url = BASE + '/sort/' + sortId + '/' + page + '.html';
legado.log('explore url: ' + url);
var html = await httpGetBookPage(url, LONG_GET_SECS);
if (!html) {
legado.log('explore returned empty');
return [];
}
var doc = legado.dom.parse(html);
var items = legado.dom.selectAll(doc, 'div.bookbox');
if (!items || items.length === 0) {
legado.log('no books found');
return [];
}
var results = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
var titleEl = legado.dom.select(item, 'h4.bookname a');
var title = legado.dom.text(titleEl);
var bUrl = legado.dom.attr(titleEl, 'href');
if (!title || !bUrl) continue;
if (bUrl.indexOf('http') !== 0) {
bUrl = BASE + bUrl;
}
var author = '';
var authorEls = legado.dom.selectAll(item, 'div.author');
for (var j = 0; j < authorEls.length; j++) {
var at = legado.dom.text(authorEls[j]);
if (at.indexOf(AUTHOR_PREFIX) === 0) {
author = at.substring(AUTHOR_PREFIX.length);
break;
}
}
var lastChapterEl = legado.dom.select(item, 'div.cat a');
var lastChapter = legado.dom.text(lastChapterEl);
var articleIdMatch = bUrl.match(/\/books\/(\d+)\//);
var articleId = articleIdMatch ? articleIdMatch[1] : '';
var coverUrl = coverUrlFromArticleId(articleId);
results.push({
name: _trim(title),
author: _trim(author),
bookUrl: bUrl,
coverUrl: coverUrl,
kind: category,
lastChapter: _trim(lastChapter)
});
}
legado.log('explore results: ' + results.length);
return results;
}
async function TEST(type) {
if (type === '__list__') return ['search', 'explore', 'bookInfo', 'chapterList', 'chapterContent'];
if (type === 'search') {
var r = await search('\u6597\u7834', 1);
if (!r || r.length < 1) return { passed: false, message: 'search empty' };
return { passed: true, message: 'search count=' + r.length };
}
if (type === 'explore') {
var b = await explore(1, '\u7384\u5e7b');
if (!b || b.length < 1) return { passed: false, message: 'explore empty' };
return { passed: true, message: 'explore count=' + b.length };
}
if (type === 'bookInfo') {
var r = await bookInfo('https://www.deqixs.co/books/3305/');
return { passed: !!(r && r.name), message: 'bookInfo name=' + (r ? r.name : '') };
}
if (type === 'chapterList') {
var r = await chapterList('https://www.deqixs.co/books/3305/');
return { passed: r.length > 0, message: 'chapterList cnt=' + r.length + ' first=' + (r[0] ? r[0].name : '') };
}
if (type === 'chapterContent') {
var r = await chapterContent('https://www.deqixs.co/books/3305/2905167.html');
return { passed: r.length > 100, message: 'chapterContent len=' + r.length };
}
return { passed: false, message: 'unknown type ' + type };
}