爱丽丝书屋

https://xn--vcsx64d.alicesw2.sbs

chen-muting (10820) 4天前 下载:2178

小说 小说 爱丽丝书屋
爱丽丝书屋小说书源,支持搜索、详情、目录、正文、发现;正文自动分段。
二维码导入(APP尚未完成该功能)
// @uuid        019e15a3-c945-7819-8202-d6dc0413c868
// @name        爱丽丝书屋
// @version     1.0.3
// @author      AI
// @url         https://xn--vcsx64d.alicesw2.sbs
// @type        novel
// @enabled     true
// @tags        小说,爱丽丝书屋
// @description 爱丽丝书屋小说书源,支持搜索、详情、目录、正文、发现;正文自动分段。

var BASE = 'https://xn--vcsx64d.alicesw2.sbs';
var HEADERS = {
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Language': 'zh-CN,zh;q=0.9',
  'Referer': BASE + '/'
};
var CATS = [['最新','/all/order/update_time+desc.html'],['原创','/original.html'],['科幻','/lists/71.html'],['校园','/lists/61.html'],['玄幻','/lists/62.html'],['乡村','/lists/63.html'],['都市','/lists/64.html'],['历史','/lists/67.html'],['武侠','/lists/68.html'],['系统','/lists/69.html'],['明星','/lists/72.html'],['同人','/lists/73.html'],['奇幻','/lists/75.html'],['经典','/lists/79.html'],['穿越','/lists/70.html'],['纯爱','/lists/19.html'],['百合','/lists/47.html'],['其他','/lists/57.html']];

function toAbs(url, baseUrl) {
  if (url === null || url === undefined) return '';
  url = String(url).replace(/&/g, '&').trim();
  if (!url) return '';
  if (url.indexOf('http://') === 0 || url.indexOf('https://') === 0) return url;
  if (url.indexOf('//') === 0) return 'https:' + url;
  var base = baseUrl || BASE;
  if (url.charAt(0) === '/') { var m = base.match(/^(https?:\/\/[^\/]+)/); return (m ? m[1] : BASE) + url; }
  if (base.indexOf('?') >= 0) base = base.substring(0, base.indexOf('?'));
  if (base.charAt(base.length - 1) !== '/') { var p = base.lastIndexOf('/'); if (p > 8) base = base.substring(0, p + 1); else base = base + '/'; }
  return base + url;
}
function decodeHtml(s) { if (!s) return ''; return String(s).replace(/&nbsp;/g,' ').replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/&apos;/g,"'").replace(/&ldquo;/g,'“').replace(/&rdquo;/g,'”').replace(/&mdash;/g,'—'); }
function cleanText(s) { if (s === null || s === undefined) return ''; s = String(s).replace(/<script[\s\S]*?<\/script>/gi,'').replace(/<style[\s\S]*?<\/style>/gi,'').replace(/<[^>]+>/g,''); s = decodeHtml(s).replace(/\u00a0/g,' ').replace(/[ \t\r\n]+/g,' '); return s.trim(); }
function selectText(root, sel) { try { var el = legado.dom.select(root, sel); if (!el) return ''; return cleanText(legado.dom.text(el)); } catch(e) { return ''; } }
function selectAttr(root, sel, attr, baseUrl) { try { var el = legado.dom.select(root, sel); if (!el) return ''; var v = legado.dom.attr(el, attr) || ''; if (attr === 'href' || attr === 'src' || attr === 'data-src') return toAbs(v, baseUrl || BASE); return v; } catch(e) { return ''; } }
function firstMatch(s, reg) { var m = String(s || '').match(reg); return m ? cleanText(m[1]) : ''; }
function bookIdFromUrl(url) { var m = String(url || '').match(/\/novel\/(\d+)\.html/); if (m) return m[1]; m = String(url || '').match(/\/other\/chapters\/id\/(\d+)\.html/); if (m) return m[1]; return ''; }
function pushBook(arr, seen, b) { if (!b || !b.name || !b.bookUrl) return; if (seen[b.bookUrl]) return; seen[b.bookUrl] = 1; arr.push(b); }
function getPageUrl(path, page) { page = page || 1; var url = path; if (page > 1) { if (url.indexOf('?') >= 0) url = url + '&page=' + page; else url = url.replace(/\.html$/, '') + '.html?page=' + page; } return toAbs(url, BASE); }

function htmlToContent(block) {
  if (!block) return '';
  block = String(block);
  block = block.replace(/<script[\s\S]*?<\/script>/gi, '').replace(/<style[\s\S]*?<\/style>/gi, '');
  block = block.replace(/<\s*br\s*\/?>/gi, '\n');
  block = block.replace(/<\/(?:p|div|li|h[1-6])\s*>/gi, '\n\n');
  block = block.replace(/<(?:p|div|li|h[1-6])\b[^>]*>/gi, '');
  block = block.replace(/<[^>]+>/g, '');
  block = decodeHtml(block);
  return segmentText(block);
}
function segmentText(text) {
  if (text === null || text === undefined) return '';
  text = String(text).replace(/\r/g, '\n').replace(/\u00a0/g, ' ');
  text = text.replace(/[ \t\f\v]+/g, ' ');
  text = text.replace(/[ \t]*\n[ \t]*/g, '\n');
  text = text.replace(/\n{3,}/g, '\n\n');
  var raw = text.split(/\n+/);
  var out = [];
  for (var i = 0; i < raw.length; i++) {
    var p = raw[i].replace(/^[ \s]+|[ \s]+$/g, '');
    if (!p) continue;
    addSegmentedParagraph(out, p);
  }
  return out.join('\n\n').replace(/\n{3,}/g, '\n\n').trim();
}
function addSegmentedParagraph(out, p) {
  var max = 220;
  while (p.length > max) {
    var cut = -1;
    var start = max - 80;
    if (start < 40) start = 40;
    var end = max + 60;
    if (end > p.length) end = p.length;
    for (var i = end; i >= start; i--) {
      var ch = p.charAt(i);
      if (ch === '。' || ch === '!' || ch === '?' || ch === ';' || ch === '…') { cut = i + 1; break; }
    }
    if (cut < 0) {
      for (var j = end; j >= start; j--) {
        var ch2 = p.charAt(j);
        if (ch2 === '”' || ch2 === '」' || ch2 === '』') { cut = j + 1; break; }
      }
    }
    if (cut < 0) cut = max;
    var s = p.substring(0, cut).replace(/^[ \s]+|[ \s]+$/g, '');
    if (s) out.push(s);
    p = p.substring(cut).replace(/^[ \s]+|[ \s]+$/g, '');
  }
  if (p) out.push(p);
}
function regexChapterContent(html) {
  var m = String(html || '').match(/<div[^>]+class=["'][^"']*(?:read-content|j_readContent)[^"']*["'][^>]*>([\s\S]*?)<\/div>/i);
  if (m) return htmlToContent(m[1]);
  m = String(html || '').match(/<div[^>]+id=["']?j_chapterBox["']?[^>]*>([\s\S]*?)<div[^>]+class=["'](?:admire-wrap|chapter-control|right-bar-list)/i);
  if (m) return htmlToContent(m[1]);
  return '';
}

function parseSearchBooks(html, pageUrl) {
  var doc = legado.dom.parse(html); var books = []; var seen = {}; var items = legado.dom.selectAll(doc, '.list-group .list-group-item');
  for (var i = 0; i < items.length; i++) { var it = items[i]; var meta = selectText(it, 'p.mb-1'); var kind = selectText(it, 'p.text-muted').replace(/标签[::]?\s*/, ''); pushBook(books, seen, { name: selectText(it, 'h5 a').replace(/^\d+\.\s*/, ''), author: selectText(it, 'p.mb-1 a'), bookUrl: selectAttr(it, 'h5 a', 'href', pageUrl), intro: selectText(it, '.content-txt'), kind: kind, status: selectText(it, 'small'), wordCount: firstMatch(meta, /字数[::]\s*([^\s ]+)/) }); }
  legado.dom.free(doc); return books;
}
function parseListBooks(html, pageUrl) {
  var doc = legado.dom.parse(html); var books = []; var seen = {}; var items; var i;
  items = legado.dom.selectAll(doc, 'ul.item-index li');
  for (i = 0; i < items.length; i++) { var it1 = items[i]; pushBook(books, seen, { name: selectText(it1, 'a.ititle'), author: selectText(it1, '.author'), bookUrl: selectAttr(it1, 'a.ititle', 'href', pageUrl), kind: selectText(it1, 'a.ctitle'), latestChapter: selectText(it1, 'a.zjie'), latestChapterUrl: selectAttr(it1, 'a.zjie', 'href', pageUrl), updateTime: selectText(it1, '.time') }); }
  items = legado.dom.selectAll(doc, 'ul');
  for (i = 0; i < items.length; i++) { var it2 = items[i]; var name2 = selectText(it2, 'li.two a'); var url2 = selectAttr(it2, 'li.two a', 'href', pageUrl); if (!url2 || url2.indexOf('/novel/') < 0) continue; pushBook(books, seen, { name: name2, author: selectText(it2, 'li.four'), bookUrl: url2, kind: selectText(it2, 'li.sev'), latestChapter: selectText(it2, 'li.three a'), latestChapterUrl: selectAttr(it2, 'li.three a', 'href', pageUrl), wordCount: selectText(it2, 'li.five'), updateTime: selectText(it2, 'li.six') }); }
  items = legado.dom.selectAll(doc, '.class-img, .hot-img, .itemr');
  for (i = 0; i < items.length; i++) { var it3 = items[i]; var name3 = selectText(it3, 'h5 a'); if (!name3) name3 = selectText(it3, 'a.hottitle'); var url3 = selectAttr(it3, 'h5 a', 'href', pageUrl); if (!url3) url3 = selectAttr(it3, 'a.hottitle', 'href', pageUrl); if (!url3 || url3.indexOf('/novel/') < 0) continue; var author3 = selectText(it3, '.authors'); if (!author3) author3 = firstMatch(legado.dom.text(it3), /作者[::]\s*([^\s]+)/); author3 = author3.replace(/^作者[::]\s*/, ''); pushBook(books, seen, { name: name3, author: author3, bookUrl: url3, coverUrl: selectAttr(it3, 'img', 'data-src', pageUrl) || selectAttr(it3, 'img', 'src', pageUrl), intro: selectText(it3, 'p') }); }
  legado.dom.free(doc); return books;
}

async function search(keyword, page) { legado.log('[爱丽丝书屋][search] keyword=' + keyword + ' page=' + page); page = page || 1; var url = BASE + '/search.html?q=' + encodeURIComponent(keyword) + '&f=_all'; if (page > 1) url = url + '&page=' + page; var html = await legado.http.get(url, HEADERS); var books = parseSearchBooks(html, url); legado.log('[爱丽丝书屋][search] found=' + books.length); return books; }
async function bookInfo(bookUrl) { legado.log('[爱丽丝书屋][bookInfo] url=' + bookUrl); var html = await legado.http.get(bookUrl, HEADERS); var doc = legado.dom.parse(html); var id = bookIdFromUrl(bookUrl); var name = selectText(doc, '.novel_title'); if (!name) name = firstMatch(html, /<title>([\s\S]*?)(?:-|_)/); var infoText = selectText(doc, '.novel_info'); var author = selectText(doc, '.novel_info p a[href*="f=author"]'); if (!author) author = firstMatch(infoText, /作\s*者[::]\s*([^\s]+)/); var kind = selectText(doc, '.bread-crumbs li:nth-child(2) a') || firstMatch(infoText, /分\s*类[::]\s*([^\s]+)/); var wordCount = firstMatch(infoText, /字\s*数[::]\s*([^·\s]+)/); var chapterCount = firstMatch(infoText, /章\s*节[::]\s*(\d+)/); var status = firstMatch(infoText, /状\s*态[::]\s*([^\s]+)/); var lastChapter = selectText(doc, '.novel_info p a[href*="/book/"]'); var lastChapterUrl = selectAttr(doc, '.novel_info p a[href*="/book/"]', 'href', bookUrl); var tags = selectText(doc, '.tags_list').replace(/标签[::]?\s*/, '').replace(/注意[\s\S]*$/, '').trim(); if (tags) kind = kind ? kind + ' ' + tags : tags; var intro = selectText(doc, '.jianjie p') || firstMatch(html, /<meta property="og:description" content="([^"]*)"/i); var coverUrl = selectAttr(doc, '.pic img', 'data-src', bookUrl) || selectAttr(doc, '.pic img', 'src', bookUrl) || firstMatch(html, /<meta property="og:image" content="([^"]*)"/i); legado.dom.free(doc); return { name: name, author: author, bookUrl: bookUrl, coverUrl: coverUrl, intro: intro, kind: kind, wordCount: wordCount, chapterCount: chapterCount, status: status, lastChapter: lastChapter, latestChapter: lastChapter, latestChapterUrl: lastChapterUrl, tocUrl: id ? BASE + '/other/chapters/id/' + id + '.html' : bookUrl }; }
async function chapterList(tocUrl) { legado.log('[爱丽丝书屋][chapterList] url=' + tocUrl); var id = bookIdFromUrl(tocUrl); if (id && tocUrl.indexOf('/other/chapters/') < 0) tocUrl = BASE + '/other/chapters/id/' + id + '.html'; var html = await legado.http.get(tocUrl, HEADERS); var doc = legado.dom.parse(html); var chapters = []; var items = legado.dom.selectAll(doc, '.mulu_list li a'); if (!items || items.length === 0) items = legado.dom.selectAll(doc, '.book_newchap .con li a'); for (var i = 0; i < items.length; i++) { var name = cleanText(legado.dom.text(items[i])); var url = toAbs(legado.dom.attr(items[i], 'href') || '', tocUrl); if (name && url) chapters.push({ name: name, url: url }); } legado.dom.free(doc); legado.log('[爱丽丝书屋][chapterList] count=' + chapters.length); return chapters; }
async function chapterContent(chapterUrl) { legado.log('[爱丽丝书屋][chapterContent] url=' + chapterUrl); var html = await legado.http.get(chapterUrl, HEADERS); var content = regexChapterContent(html); if (!content || content.length < 20) { var doc = legado.dom.parse(html); var box = legado.dom.select(doc, '.read-content.j_readContent'); if (!box) box = legado.dom.select(doc, '.read-content'); if (!box) box = legado.dom.select(doc, '.j_readContent'); if (box) { var ps = legado.dom.selectAll(box, 'p'); var arr = []; for (var i = 0; i < ps.length; i++) { var t = cleanText(legado.dom.text(ps[i])); if (t) addSegmentedParagraph(arr, t); } if (arr.length > 0) content = arr.join('\n\n'); else content = segmentText(legado.dom.text(box)); } legado.dom.free(doc); } content = segmentText(content.replace(/爱丽丝书屋所有小说中出现的人物均为18岁以上的成人/g, '')); legado.log('[爱丽丝书屋][chapterContent] len=' + content.length); return content; }
async function explore(page, category) { legado.log('[爱丽丝书屋][explore] category=' + category + ' page=' + page); page = page || 1; if (!category || category === 'GETALL') { var names = []; for (var i = 0; i < CATS.length; i++) names.push(CATS[i][0]); return names; } var path = ''; for (var j = 0; j < CATS.length; j++) { if (CATS[j][0] === category) { path = CATS[j][1]; break; } } if (!path) return []; var url = getPageUrl(path, page); var html = await legado.http.get(url, HEADERS); var books = parseListBooks(html, url); legado.log('[爱丽丝书屋][explore] found=' + books.length); return books; }
async function TEST(type) { if (type === '__list__') return ['explore','bookInfo','chapterList','chapterContent','search']; if (type === 'explore') { var e = await explore(1, '玄幻'); return { passed: e && e.length > 0, message: '发现返回 ' + (e ? e.length : 0) + ' 条' }; } if (type === 'bookInfo') { var b = await bookInfo(BASE + '/novel/50761.html'); return { passed: !!(b && b.name && b.tocUrl), message: '书名=' + (b ? b.name : '') }; } if (type === 'chapterList') { var c = await chapterList(BASE + '/other/chapters/id/50761.html'); return { passed: c && c.length > 0, message: '目录 ' + (c ? c.length : 0) + ' 章' }; } if (type === 'chapterContent') { var txt = await chapterContent(BASE + '/book/51995/3d547b821dac7.html'); return { passed: txt && txt.length > 100, message: '正文长度=' + (txt ? txt.length : 0) }; } if (type === 'search') { var s = await search('斗罗', 1); return { passed: s && s.length > 0, message: '搜索返回 ' + (s ? s.length : 0) + ' 条' }; } return { passed: false, message: '未知测试类型' }; }
广告