爱丽丝书屋

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

helloworld246 (13644) 05/17 21:56 下载:5276

小说 小说 爱丽丝书屋
爱丽丝书屋小说书源,支持搜索、详情、目录、正文、发现;正文严格删除所有空格。
二维码导入(APP尚未完成该功能)
// @uuid 019e15a3-c945-7819-8602-d6dc04136666
// @name 爱丽丝书屋
// @version 1.0.6
// @author hello world
// @url https://xn--vcsx64d.alicesw2.buzz
// @type novel
// @enabled true
// @tags 小说,爱丽丝书屋
// @description 爱丽丝书屋小说书源,支持搜索、详情、目录、正文、发现;正文严格删除所有空格。
var BASE = 'https://xn--vcsx64d.alicesw2.buzz';
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');
    // 彻底删除所有空格类字符(半角空格、全角空格、&nbsp;、制表符、零宽空格等)
    text = text
        .replace(/\u00A0/g, '')   // &nbsp;
        .replace(/\u3000/g, '')   // 全角空格
        .replace(/\u200B/g, '')   // 零宽空格(可选)
        .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) {
    // 段落内部再次严格删除所有空格(保险)
    p = p.replace(/\u00A0/g, '').replace(/\u3000/g, '').replace(/[ \t]+/g, '');
    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: '未知测试类型' };
}
广告