275听书网

https://m.i275.com

zpccool (13551) 5小时前 下载:174

音频 有声小说 听书 免费
275听书网,有声小说免费听
二维码导入(APP尚未完成该功能)
// @name        275听书网
// @uuid        275tingshuwang
// @version     1.0.0
// @author      Ai
// @url         https://m.i275.com
// @type        music
// @enabled     true
// @tags        有声小说,听书,免费
// @description 275听书网,有声小说免费听

var BASE = 'https://m.i275.com';
var UA = 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36';

function makeHeaders(referer) {
    var h = {
        'User-Agent': UA,
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Origin': BASE
    };
    h.Referer = referer || BASE + '/';
    return h;
}

function toAbs(url) {
    if (!url) return '';
    if (url.indexOf('//') === 0) return 'https:' + url;
    if (url.indexOf('http://') === 0 || url.indexOf('https://') === 0) return url;
    if (url.charAt(0) === '/') return BASE + url;
    return BASE + '/' + url;
}

function cleanText(text) {
    if (!text) return '';
    return String(text).replace(/\s+/g, ' ').trim();
}

function cleanAudioUrl(url) {
    if (!url) return '';
    url = String(url);
    url = url.replace(/\\\//g, '/');
    url = url.replace(/\\u0026/g, '&').replace(/\\x26/g, '&');
    url = url.replace(/&/g, '&').replace(/&/g, '&').trim();
    return toAbs(url);
}

function extractBookName(item) {
    var name = legado.dom.selectText(item, 'h3');
    if (!name) name = legado.dom.selectText(item, '.font-medium.text-sm');
    if (!name) name = legado.dom.selectText(item, '.font-medium');
    if (!name) name = legado.dom.selectAttr(item, 'img', 'alt');
    return cleanText(name);
}

function extractBookList(doc, selector) {
    var items = legado.dom.selectAll(doc, selector);
    var books = [];
    var seen = {};

    legado.log('[extractBookList] selector=' + selector + ' found=' + items.length);

    for (var i = 0; i < items.length; i++) {
        var item = items[i];
        var bookUrl = legado.dom.attr(item, 'href') || legado.dom.selectAttr(item, 'a[href^="/book/"]', 'href') || '';
        var name = extractBookName(item);
        var coverUrl = legado.dom.selectAttr(item, 'img', 'src') || '';
        var intro = cleanText(legado.dom.selectText(item, '.line-clamp-2'));

        if (!name || !bookUrl || seen[bookUrl]) continue;
        seen[bookUrl] = true;

        books.push({
            name: name,
            author: 'Ai',
            bookUrl: bookUrl,
            coverUrl: toAbs(coverUrl),
            intro: intro,
            kind: '有声小说'
        });
    }

    legado.log('[extractBookList] total=' + books.length);
    return books;
}

function parseInfoText(doc, label) {
    var texts = legado.dom.selectAllTexts(doc, '.mt-2 p');
    for (var i = 0; i < texts.length; i++) {
        var text = cleanText(texts[i]);
        if (text.indexOf(label + ':') === 0) {
            return cleanText(text.substring(label.length + 1));
        }
    }
    return '';
}

function guessBookReferer(chapterUrl) {
    var m = String(chapterUrl).match(/\/play\/(\d+)\//);
    if (m && m[1]) return BASE + '/book/' + m[1] + '.html';
    return BASE + '/';
}

function selectChapterItems(doc) {
    var items = legado.dom.selectAll(doc, 'a[id^="chapter-pos-"][href^="/play/"]');
    if (!items.length) items = legado.dom.selectAll(doc, '.grid a[href^="/play/"]');
    if (!items.length) items = legado.dom.selectAll(doc, 'a[href^="/play/"]');
    return items;
}

function makeAudioResult(audioUrl, chapterUrl) {
    return JSON.stringify({
        url: cleanAudioUrl(audioUrl),
        type: 'audio',
        referer: chapterUrl,
        headers: {
            'User-Agent': UA,
            'Referer': chapterUrl,
            'Origin': BASE
        }
    });
}

function extractAudioFromHtml(html) {
    var patterns = [
        /url\s*:\s*["']([^"']+\.(?:mp3|m4a|aac|flac|wav|ogg|opus|ape|wma)(?:\?[^"']*)?)["']/i,
        /audio_url\s*[=:]\s*["']([^"']+\.(?:mp3|m4a|aac|flac|wav|ogg|opus|ape|wma)(?:\?[^"']*)?)["']/i,
        /file\s*[=:]\s*["']([^"']+\.(?:mp3|m4a|aac|flac|wav|ogg|opus|ape|wma)(?:\?[^"']*)?)["']/i,
        /(https?:\/\/[^"'<>\\\s]+\.(?:mp3|m4a|aac|flac|wav|ogg|opus|ape|wma)(?:\?[^"'<>\\\s]*)?)/i
    ];

    for (var i = 0; i < patterns.length; i++) {
        var m = html.match(patterns[i]);
        if (m && m[1]) return cleanAudioUrl(m[1]);
    }
    return '';
}

async function search(keyword, page) {
    page = page || 1;
    legado.log('[search] keyword=' + keyword + ' page=' + page);

    var url = BASE + '/search.php?q=' + encodeURIComponent(keyword) + '&page=' + page;
    var html = await legado.http.get(url, makeHeaders(BASE + '/'));
    var doc = legado.dom.parse(html);
    var books = extractBookList(doc, '.divide-y > a[href^="/book/"], .divide-y a[href^="/book/"]');

    if (!books.length) {
        books = extractBookList(doc, 'a[href^="/book/"]');
    }

    legado.log('[search] total=' + books.length);
    return books;
}

async function bookInfo(bookUrl) {
    legado.log('[bookInfo] url=' + bookUrl);

    var url = toAbs(bookUrl);
    var html = await legado.http.get(url, makeHeaders(BASE + '/'));
    var doc = legado.dom.parse(html);

    var name = cleanText(legado.dom.selectText(doc, 'h1.text-2xl'));
    if (!name) {
        var title = cleanText(legado.dom.selectText(doc, 'title'));
        name = title.replace(/\s*-\s*.*$/, '');
    }

    var coverUrl = legado.dom.selectAttr(doc, '.bg-white .w-32 img, .bg-white .w-48 img, img[alt]', 'src') || '';
    var intro = cleanText(legado.dom.selectText(doc, '.line-clamp-3'));
    var status = parseInfoText(doc, '状态');
    var heat = parseInfoText(doc, '热度');

    var chapterCount = 0;
    var chapterText = cleanText(legado.dom.selectText(doc, '.active-tab'));
    var countMatch = chapterText.match(/\((\d+)\)/);
    if (countMatch && countMatch[1]) chapterCount = parseInt(countMatch[1], 10);

    var chapters = selectChapterItems(doc);
    var lastChapter = '';
    var latestChapterUrl = '';
    if (chapters.length) {
        var last = chapters[chapters.length - 1];
        lastChapter = cleanText(legado.dom.selectText(last, '.text-sm.text-gray-700')) || cleanText(legado.dom.text(last));
        latestChapterUrl = legado.dom.attr(last, 'href') || '';
        if (!chapterCount) chapterCount = chapters.length;
    }

    return {
        name: name,
        author: 'Ai',
        bookUrl: url,
        coverUrl: toAbs(coverUrl),
        intro: intro,
        kind: '有声小说',
        lastChapter: lastChapter,
        latestChapter: lastChapter,
        latestChapterUrl: latestChapterUrl,
        chapterCount: chapterCount,
        status: status,
        wordCount: heat,
        tocUrl: url
    };
}

async function chapterList(tocUrl) {
    legado.log('[chapterList] url=' + tocUrl);

    var url = toAbs(tocUrl);
    var html = await legado.http.get(url, makeHeaders(BASE + '/'));
    var doc = legado.dom.parse(html);
    var chapters = [];
    var seen = {};

    var items = selectChapterItems(doc);
    legado.log('[chapterList] found=' + items.length);

    for (var i = 0; i < items.length; i++) {
        var item = items[i];
        var chapterUrl = legado.dom.attr(item, 'href') || '';
        var name = cleanText(legado.dom.selectText(item, '.text-sm.text-gray-700')) || cleanText(legado.dom.text(item));

        if (!chapterUrl || chapterUrl.indexOf('/play/') === -1 || !name || seen[chapterUrl]) continue;
        seen[chapterUrl] = true;

        chapters.push({
            name: name,
            url: chapterUrl
        });
    }

    legado.log('[chapterList] total=' + chapters.length);
    return chapters;
}

async function chapterContent(chapterUrl) {
    legado.log('[chapterContent] url=' + chapterUrl);

    var url = toAbs(chapterUrl);
    var referer = guessBookReferer(url);
    var html = await legado.http.get(url, makeHeaders(referer));
    var audioUrl = extractAudioFromHtml(html);

    if (audioUrl) {
        legado.log('[chapterContent] audio=' + audioUrl);
        return makeAudioResult(audioUrl, url);
    }

    var doc = legado.dom.parse(html);
    var src = legado.dom.selectAttr(doc, 'audio source, audio', 'src');
    if (!src) src = legado.dom.selectAttr(doc, 'audio[data-src], audio[data-url]', 'data-src');
    if (!src) src = legado.dom.selectAttr(doc, 'audio[data-src], audio[data-url]', 'data-url');
    if (src) {
        audioUrl = cleanAudioUrl(src);
        legado.log('[chapterContent] audio-dom=' + audioUrl);
        return makeAudioResult(audioUrl, url);
    }

    legado.log('[chapterContent] no direct audio found, fallback to play page');
    return makeAudioResult(url, url);
}

async function explore(page, category) {
    page = page || 1;
    category = category || 'GETALL';
    legado.log('[explore] page=' + page + ' category=' + category);

    if (category === 'GETALL' || category === '') {
        return ['首页'];
    }

    var html = await legado.http.get(BASE + '/', makeHeaders(BASE + '/'));
    var doc = legado.dom.parse(html);
    var books = extractBookList(doc, '.grid a[href^="/book/"]');

    legado.log('[explore] total=' + books.length);
    return books;
}

async function TEST(type) {
    if (type === '__list__') return ['search', 'explore', 'info', 'toc', 'content'];

    if (type === 'explore') {
        var books = await explore(1, '首页');
        if (!books || books.length < 1) return { passed: false, message: '发现页为空' };
        return { passed: true, message: '发现页 ' + books.length + ' 条' };
    }

    if (type === 'info') {
        var info = await bookInfo('/book/39328.html');
        if (!info || !info.name) return { passed: false, message: '书籍详情为空' };
        return { passed: true, message: '书籍详情: ' + info.name };
    }

    if (type === 'toc') {
        var chapters = await chapterList('/book/39328.html');
        if (!chapters || chapters.length < 1) return { passed: false, message: '章节目录为空' };
        return { passed: true, message: '章节 ' + chapters.length + ' 条' };
    }

    if (type === 'content') {
        var content = await chapterContent('/play/39328/23406840.html');
        if (!content || content.indexOf('.m4a') === -1) return { passed: false, message: '音频地址获取失败' };
        return { passed: true, message: '音频地址获取成功' };
    }

    if (type === 'search') {
        var results = await search('凡人修仙传', 1);
        if (!results || results.length < 1) return { passed: false, message: '搜索无结果' };
        return { passed: true, message: '搜索返回 ' + results.length + ' 条' };
    }

    return { passed: false, message: '未知测试: ' + type };
}
广告