轻小说阅读器

修改自pppploi8的通用阅读器,为“真白萌”和“哔哩轻小说”提供阅读模式

目前为 2024-07-25 提交的版本。查看 最新版本

// ==UserScript==
// @name         轻小说阅读器
// @version      0.5.9
// @description  修改自pppploi8的通用阅读器,为“真白萌”和“哔哩轻小说”提供阅读模式
// @require https://update.gf.qytechs.cn/scripts/430421/1225835/%E4%B8%AD%E6%96%87%E7%B9%81%E4%BD%93%E7%AE%80%E4%BD%93%E8%BD%AC%E5%8C%96%E5%BA%93.js
// @require https://update.gf.qytechs.cn/scripts/501723/1416523/font-ch-en.js
// @author       Y_C_Z
// @match        https://masiro.me/*
// @match        https://www.linovelib.com/*
// @match        https://www.bilinovel.com/*
// @grant    GM_getValue
// @grant    GM_setValue
// @grant    GM_deleteValue
// @grant    GM_listValues
// @grant    GM_registerMenuCommand
// @grant    unsafeWindow
// @namespace https://gf.qytechs.cn/users/1335970
// ==/UserScript==
(function() {
    var $ = function(selector){
        return document.querySelector(selector);
    }

    var isSupportFontFamily = function (fontFamily) {//判断浏览器是否支持字体
        if (typeof fontFamily != 'string') {
            return false;
        }

        var defaultFontFamily = 'Arial';
        if (fontFamily.toLowerCase() == defaultFontFamily.toLowerCase()) {
            return true;
        }

        var defaultLetter = 'a';
        var defaultFontSize = 100;

        // 使用该字体绘制的canvas
        var width = 100, height = 100;
        var canvas = document.createElement('canvas');
        var context = canvas.getContext('2d');
        canvas.width = width;
        canvas.height = height;
        // 全局一致的绘制设定
        context.textAlign = 'center';
        context.fillStyle = 'black';
        context.textBaseline = 'middle';
        var getFontData = function (fontFamily) {
            // 清除
            context.clearRect(0, 0, width, height);
            // 设置字体
            context.font = defaultFontSize + 'px ' + fontFamily + ', ' + defaultFontFamily;
            context.fillText(defaultLetter, width / 2, height / 2);

            var data = context.getImageData(0, 0, width, height).data;

            return [].slice.call(data).filter(function(value) {
                return value != 0;
            });
        };

        return getFontData(defaultFontFamily).join('') !== getFontData(fontFamily).join('');
    };

    GM_registerMenuCommand("设置", () => {//打开设置窗口
        addDialog();
        var arrFont = dataFont['windows'].concat(dataFont['OS X'], dataFont['office'], dataFont['open']);//获取浏览器可用字体
        var option = '<option value="">默认</option>';
        arrFont.forEach(function (obj) {
            var fontFamily = obj.en;
            if (isSupportFontFamily(fontFamily)) {
                option = option + '<option value="'+ fontFamily +'">'+ obj.ch +'</option>';
            }
        });
        $("#fontSelect").innerHTML = option;
        if (GM_getValue('ttsbutton') != undefined){
            $("input[name='listen']").checked = GM_getValue('ttsbutton');
        }else{
            $("input[name='listen']").checked = true;
        }
        if (GM_getValue('totopbutton') != undefined){
            $("input[name='scrollToTop']").checked = GM_getValue('totopbutton');
        }else{
            $("input[name='scrollToTop']").checked = true;
        }
        if (GM_getValue('fontFamily') != undefined){
            $("#fontSelect").value = GM_getValue('fontFamily');
        }
        const dialog = document.getElementById('myDialog');
        dialog.showModal();
        $('#closeDialog').onclick = function(){
            if ($("input[name='listen']").checked == false) {//听书按钮
                $("._er-tts") && ($("._er-tts").style.display = 'none');
                GM_setValue('ttsbutton',false);
            }else{
                $("._er-tts") && ($("._er-tts").style.display = 'block');
                GM_setValue('ttsbutton',true);
            }
            if ($("input[name='scrollToTop']").checked == false) {//回到顶部按钮
                $(".toTop") && ($(".toTop").style.display = 'none');
                GM_setValue('totopbutton',false);
            }else{
                $(".toTop") && ($(".toTop").style.display = 'block');
                GM_setValue('totopbutton',true);
            }
            $("._er-title") && ($("._er-title").style.fontFamily = $("#fontSelect").value);//字体设置
            $(".translator") && ($(".translator").style.fontFamily = $("#fontSelect").value);
            $("._er-content") && ($("._er-content").style.fontFamily = $("#fontSelect").value);
            GM_setValue('fontFamily',$("#fontSelect").value);
            dialog.remove();
        }
    });
    var blackList = {"masiro.me": true,"www.linovelib.com": true,"www.bilinovel.com": true};
    if (location.host == "www.linovelib.com" || location.host == "www.bilinovel.com") {//尝试通过Cloudflare
        document.cookie = '__gpi_opt_out=1;';
        document.cookie = 'cf_clearance=MHioFTJ3.DT_FjBcW1pGr7CzIvtm9Z29VMbB7d_TLgE-1721550888-1.0.1.1-7FSsg2Gpn9Y1x36Iq.eXyN60H2j.X2HXp_zPYRj17l0d4gzbOQw24ZSptWj.ktb8QTjubE5qO1Vz2YnTmPxAeQ;';
        var start=1;
        var end=10;
        window.onload = function() {
            var t1 = setInterval(function(){
                for (var n=0;n<10;++n){
                    $(".adsbygoogle") && $(".adsbygoogle").remove();
                }
                $("#bottomads") && $("#bottomads").remove();
                $("script[src*='adsbygoogle.js']") && $("script[src*='adsbygoogle.js']").remove();
                $("script[src*='g.doubleclick.net']") && $("script[src*='g.doubleclick.net']").remove();
                $("div[id*='google_ads']") && $("div[id*='google_ads']").remove();
                $("div[data-google-query-id]") && $("div[data-google-query-id]").remove();
                start++;
                if (start>=end ){
                    clearInterval(t1);
                }
            },1000);
        };
    }
 
    // 通用解析模板
    function parseContentAndTitle(){
        var mainDom = null;
 
        function findMainDom(doms){
            for(var i=0;i<doms.length;i++){
                var dom = doms[i];
                if (dom.classList.contains("nvl-content") || dom.classList.contains("read-content") || dom.classList.contains("bcontent")){//真白萌||哔哩轻小说PC||哔哩轻小说手机端
                    mainDom = dom;
                }
                findMainDom(dom.children||[]);
            }
        }
        findMainDom(document.body.children);
        var newTitle = '';
        var title = document.title;
        var textToRemove1 = "真白萌 | ";
        var textToRemove2 = " _哔哩轻小说";
 
        title = title.replace(textToRemove1, "").replace(textToRemove2, "");
        switch (location.host){
            case 'www.bilinovel.com':
                var atitle = document.querySelector('#atitle');
                if (atitle) {
                    newTitle = atitle.textContent;
                } else {
                    newTitle = title;
                }
                break;
            default:
                // 选择“哔哩轻小说PC”中id为"mlfy_main_text"的元素
                var mlfyMainTextElement = document.querySelector("#mlfy_main_text");
                if (mlfyMainTextElement) {
                    // 获取第一个<h1>元素
                    var firstH1Element = mlfyMainTextElement.querySelector("h1");
                    if (firstH1Element) {
                        newTitle = firstH1Element.textContent;
                    } else {
                        newTitle = title;
                    }
                } else {
                    newTitle = title;
                }
                break;
        }
 
 
        if (mainDom){
            return {content: mainDom.innerText, title: newTitle};
        }
    }
 
    function parsePageUp(){
        var reg, text;
        var href = "";
        var i = 0;
        var as = document.querySelectorAll('a');
        switch (location.host){
            case 'www.bilinovel.com':
                return ReadParams.url_previous;
                break;
            default:
                reg = /上一章|上一篇|上一页|上一话|navigation-prev/;
                for(i=0;i<as.length;i++){
                    text = as[i].outerHTML;
                    href = (as[i].attributes.href && as[i].attributes.href.value) || (as[i].dataset && as[i].dataset.url);
                    if (text && reg.test(text.trim()) && href && href != "#" && href.indexOf("javascript:") !== 0){
                        return href;
                    }
                }
                break;
        }
    }
 
    function parsePageDown(){
        var reg, text;
        var href = "";
        var i = 0;
        var as = document.querySelectorAll('a');
        switch (location.host){
            case 'www.bilinovel.com':
                return ReadParams.url_next;
                break;
            default:
                reg = /下一章|下一篇|下一页|下一话|navigation-next/;
                for(i=0;i<as.length;i++){
                    text = as[i].outerHTML;
                    href = (as[i].attributes.href && as[i].attributes.href.value) || (as[i].dataset && as[i].dataset.url);
                    if (text && reg.test(text.trim()) && href && href != "#" && href.indexOf("javascript:") !== 0){
                        return href;
                    }
                }
                break;
        }
    }
 
    function parsePageIndex(){
        var as = document.querySelectorAll('a');
        var reg = /目录/;
        switch (location.host){
            case 'www.bilinovel.com':
                return ReadParams.url_index;
                break;
            default:
                for(var i=0;i<as.length;i++){
                    var text = as[i].innerText;
                    var href = as[i].attributes.href && as[i].attributes.href.value;
                    if (text && text.length <= 10 && reg.test(text.trim()) && href && href != "#" && href.indexOf("javascript:") !== 0){
                        return href;
                    }
                }
                break;
        }
    }
 
 
    var fontsize = parseInt(localStorage["_er_fontsize"] || 0);
    var padding = parseInt(localStorage["_er_padding"] || 10);
    var autoplay = false;
    if (localStorage['_er-autoplay'] === 'true'){
        autoplay = true;
    }
    delete localStorage['_er-autoplay'];
 
    if (top.window !== window) return; // iframe内的网页不展示按钮,也不支持进入阅读模式
    if (localStorage['_er-enable'] === 'true'){
        localStorage['_er-enable'] = 'false';
        checkAndCreateReader(true);
    } else if (blackList[location.host] == true){
        // 创建阅读模式悬浮按钮
        $('body').children[0].insertAdjacentHTML('beforeBegin', '<button id="_er-entryReadMode" style="' +
            '    position: fixed;' +
            '    right: 50px;' +
            '    bottom: 50px;' +
            '    background-color: rgba(255,255,255,0.5);' +
            '    backdrop-filter: blur(1px);' +
            '    border: 0px solid black;' +
            '    border-radius: 10px;' +
            '    box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);' +
            '    padding: 0 5px;' +
            '    height: 50px;' +
            '    overflow: auto;' +
            '    font-size: 14px;' +
            '    color: black;' +
            '    z-index: 201901272210;">进入阅读模式</button>');
        $('#_er-entryReadMode').onclick = checkAndCreateReader;
        $('#_er-NotShowReadMode').onclick = function(){
            localStorage['_er-disabled'] = 'true';
            $('#_er-entryReadMode').remove();
            $('#_er-NotShowReadMode').remove();
        }
    }
 
    function checkAndCreateReader(notAlert){
        // 通过调用通用模板尝试是否能够成功匹配到阅读内容
        var content = parseContentAndTitle();
        if (content && content.content){
            content.pageup = parsePageUp();
            content.pagedown = parsePageDown();
            content.pageindex = parsePageIndex();
            createReader(content);
        }else{
            if (notAlert !== true){
                alert('当前页面解析失败,无法进入阅读模式!');
            }
        }
    }
 
    function setTheme(theme) {
        switch(theme) {
            case 'black':
                $('._er').style.backgroundColor = 'black';
                $('._er-title').style.color = 'lightgrey';
                $('._er-content').style.color = 'lightgrey';
                $('.translator').style.color = 'lightgrey';
                document.querySelectorAll('.buttonBottom').forEach(function(element) {
                    element.style.color = 'lightgrey';
                    element.style.border = '0.5px solid lightgrey';
                });
                document.querySelectorAll('.buttonTop').forEach(function(element) {
                    element.style.color = 'lightgrey';
                });
                document.querySelectorAll('.exit').forEach(function(element) {
                    element.style.setProperty('box-shadow', '0px 4px 8px rgba(255, 255, 255, 0.2)');
                    element.style.setProperty('background-color', 'rgb(59,59,59)');
                    element.style.setProperty('color', 'white');
                });
                document.querySelectorAll('.toTop').forEach(function(element) {
                    element.style.setProperty('box-shadow', '0px 4px 8px rgba(255, 255, 255, 0.2)');
                    element.style.setProperty('background-color', 'rgb(59,59,59)');
                    element.style.setProperty('color', 'white');
                });
                document.querySelectorAll('._er-tts').forEach(function(element) {
                    element.style.setProperty('box-shadow', '0px 4px 8px rgba(255, 255, 255, 0.2)');
                    element.style.setProperty('background-color', 'rgb(59,59,59)');
                    element.style.setProperty('color', 'white');
                });
                break;
            case 'OliveDrab':
                $('._er').style.backgroundColor = '#D3E1D0';
                $('._er-title').style.color = 'black';
                $('._er-content').style.color = 'black';
                break;
            case 'Khaki':
                $('._er').style.backgroundColor = '#F6F2E7';
                $('._er-title').style.color = 'black';
                $('._er-content').style.color = 'black';
                break;
            case 'blue':
                $('._er').style.backgroundColor = '#D3E5F9';
                $('._er-title').style.color = 'black';
                $('._er-content').style.color = 'black';
                break;
            case 'white':
                $('._er').style.backgroundColor = 'white';
                $('._er-title').style.color = 'black';
                $('._er-content').style.color = 'black';
                $('.translator').style.color = 'black';
                document.querySelectorAll('.buttonBottom').forEach(function(element) {
                    element.style.border = '0.5px solid black';
                    element.style.color = 'black';
                });
                document.querySelectorAll('.buttonTop').forEach(function(element) {
                    element.style.border = '1px solid black';
                    element.style.color = 'black';
                });
                document.querySelectorAll('.exit').forEach(function(element) {
                    element.style.setProperty('box-shadow', '0px 4px 8px rgba(0, 0, 0, 0.2)');
                    element.style.setProperty('background-color', 'rgb(239,239,239)');
                    element.style.setProperty('color', 'black');
                });
                document.querySelectorAll('.toTop').forEach(function(element) {
                    element.style.setProperty('box-shadow', '0px 4px 8px rgba(0, 0, 0, 0.2)');
                    element.style.setProperty('background-color', 'rgb(239,239,239)');
                    element.style.setProperty('color', 'black');
                });
                document.querySelectorAll('._er-tts').forEach(function(element) {
                    element.style.setProperty('box-shadow', '0px 4px 8px rgba(0, 0, 0, 0.2)');
                    element.style.setProperty('background-color', 'rgb(239,239,239)');
                    element.style.setProperty('color', 'black');
                });
                break;
        }
        localStorage['_er-theme'] = theme;
        $('._er').dataset['theme'] = theme;
    }
    function setSimplemode(simplemode, contentHtml) {//简繁切换
        switch(simplemode) {
            case 'simple':
                contentHtml = traditionalized(contentHtml);
                break;
            case 'traditional':
                contentHtml = simplized(contentHtml);
                break;
        }
        localStorage['_er-simplemode'] = simplemode;
        $('._er').dataset['simplemode'] = simplemode;
        return contentHtml;
    }
 
    // 创建阅读器
    function createReader(content){
        document.documentElement.style.overflow = 'hidden';
        $('#_er-entryReadMode') && $('#_er-entryReadMode').remove();
        $('#_er-NotShowReadMode') && $('#_er-NotShowReadMode').remove();
        addClassAndDom();
        if (window.SpeechSynthesisUtterance){
            $('#_er-tts').style.display = 'block';
        }
        if (GM_getValue('ttsbutton')==0){//本地缓存的设置
            document.querySelector('._er-tts').style.display = 'none';
        }
        if (GM_getValue('totopbutton')==0){
            document.querySelector('.toTop').style.display = 'none';
        }
        $("._er-title").style.fontFamily = GM_getValue('fontFamily',);
        $(".translator").style.fontFamily = GM_getValue('fontFamily',);
        $("._er-content").style.fontFamily = GM_getValue('fontFamily',);
        if (hasCSSRule('#TextContent p:last-of-type')) {//针对哔哩轻小说最后一段转码的情况
            addCSSRule('._er-content p:last-of-type', 'font-family: "read" !important;');
        }




        if (localStorage['_er-theme']) {
            setTheme(localStorage['_er-theme']);
        }
        $('._er-title').innerText = content.title;
        var contentArr = content.content.split('\n');
        var contentHtml = '';
        for(var i=0;i<contentArr.length;i++){
            var line = contentArr[i];
            if (line){
                contentHtml += '<p style="text-indent: 2em; margin-bottom: 1em;">' + line + '</p>';
            }
        }
        $('._er-content').innerHTML = contentHtml;
        if (location.host == "masiro.me") {
            var translatorInfoElm = document.getElementById('translator-info-elm');
            var translator = translatorInfoElm.textContent.trim();
            $('.translator').innerHTML = translator;
            $('.translator').style.display = 'block';
        }
        if (localStorage['_er-simplemode']) {
            $('._er-content').innerHTML=setSimplemode(localStorage['_er-simplemode'],$('._er-content').innerHTML);
            $('._er-title').innerHTML=setSimplemode(localStorage['_er-simplemode'],$('._er-title').innerHTML);
            $('.translator').innerHTML=setSimplemode(localStorage['_er-simplemode'],$('.translator').innerHTML);
        }
        var spanNodes = document.querySelectorAll('._er-content p');
        for(i=0;i<spanNodes.length;i++){
            spanNodes[i].onclick = function(){
                if ($('#_er-tts').innerText == '听书'){
                    for(var j=0;j<spanNodes.length;j++){
                        spanNodes[j].classList.remove('_er-none');
                        spanNodes[j].classList.remove('_er-current');
                    }
                    this.classList.add('_er-none');
                }
            }
        }
        // 挂接键盘事件,实现键盘上下左右切换阅读功能
        $('body').onkeydown = function(e){
            e.stopPropagation();
            switch(e.keyCode || e.which || e.charCode){
                case 38: // up
                    if (e.ctrlKey) {
                        $('._er').scrollTop = $('._er').scrollTop - (document.documentElement.clientHeight - 24)
                    } else {
                        toPrevReadPos();
                        updateReadPos();
                    }
                    break;
                case 40: // down
                    if (e.ctrlKey) {
                        $('._er').scrollTop = $('._er').scrollTop + (document.documentElement.clientHeight - 24);
                    } else {
                        toNextReadPos();
                        updateReadPos();
                    }
                    break;
                case 37: // left
                    if (e.ctrlKey) {
                        toPrevPage();
                    } else {
                        $('._er').scrollTop = $('._er').scrollTop - (document.documentElement.clientHeight - 24)
                    }
                    break;
                case 39: // right
                    if (e.ctrlKey) {
                        toNextPage()
                    } else {
                        $('._er').scrollTop = $('._er').scrollTop + (document.documentElement.clientHeight - 24);
                    }
                    break;
                default:
                    return true;
            }
            return false;
 
            function toPrevPage(){
                if (content.pageup){
                    localStorage['_er-enable'] = 'true';
                    location.href = content.pageup;
                }else{
                    alert('很抱歉,没有匹配到上一页!');
                }
            }
            function toNextPage(){
                if (content.pagedown){
                    localStorage['_er-enable'] = 'true';
                    location.href = content.pagedown;
                }else{
                    alert('很抱歉,没有匹配到下一页!');
                }
            }
        };
        $('._er-content').onclick = function(e){ // 适用于墨水屏的左右点击无动画翻页
            var x = e.pageX;
            var width = document.documentElement.clientWidth;
            if (x <= width*0.1){ // 前翻一页
                $('._er').scrollTop = $('._er').scrollTop - (document.documentElement.clientHeight - 24)
            }else if(x >= width*0.9){ // 后翻一页
                $('._er').scrollTop = $('._er').scrollTop + (document.documentElement.clientHeight - 24);
            }
        }
        var erpageindex = document.querySelectorAll('#_er-pageindex');
        for (i = 0; i < erpageindex.length; i++) {
            erpageindex[i].onclick = function() {
                if (content.pageindex) {
                    location.href = content.pageindex;
                } else {
                    alert('很抱歉,没有匹配到目录!');
                }
            };
        }
        $('#_er-switch-theme').onclick = function(){
            var current = $('._er').dataset['theme'] || 'white';
            var themeList = ['white', 'Khaki', 'blue', 'OliveDrab', 'black'];
            var index = themeList.indexOf(current);
            if (index === -1) index = 0;
            index++;
            if (index >= themeList.length) {
                index = 0;
            }
            setTheme(themeList[index]);
        }
        var pressTimer;//定时器
        var touchpressTimer;//触摸定时器
        var isLongPress = false; // 长按标记
        $('#_er-simplemode').addEventListener('touchstart', function() {//触摸屏
            touchpressTimer = setTimeout(function() {
                isLongPress = true;
                localStorage['_er-simplemode'] = '';
                $('._er-content').innerHTML = contentHtml;
                $('._er-title').innerText = content.title;
                if (location.host == "masiro.me") {
                    $('.translator').innerHTML = translator;
                }
            }, 500);
        });
        $('#_er-simplemode').addEventListener('touchend', function() {
            clearTimeout(touchpressTimer);
        });
 
        $('#_er-simplemode').addEventListener('mousedown', function() {//PC
            pressTimer = setTimeout(function() {
                isLongPress = true;
                localStorage['_er-simplemode'] = '';
                $('._er-content').innerHTML = contentHtml;
                $('._er-title').innerText = content.title;
                if (location.host == "masiro.me") {
                    $('.translator').innerHTML = translator;
                }
            }, 500);
        });
        $('#_er-simplemode').addEventListener('mouseup', function() {
            clearTimeout(pressTimer);
            if (!isLongPress) {
                var current = $('._er').dataset['simplemode'] || 'traditional';
                var simplemodeList = ['simple', 'traditional'];
                var index = simplemodeList.indexOf(current);
                if (index === -1) index = 0;
                index++;
                if (index >= simplemodeList.length) {
                    index = 0;
                }
                $('._er-content').innerHTML=setSimplemode(simplemodeList[index],$('._er-content').innerHTML);
                $('.translator').innerHTML=setSimplemode(simplemodeList[index],$('.translator').innerHTML);
                $('._er-title').innerHTML=setSimplemode(simplemodeList[index],$('._er-title').innerHTML);
            } else {
                $('._er').dataset['simplemode'] = '';
                current = $('._er').dataset['simplemode'] || 'traditional';
                simplemodeList = ['simple', 'traditional'];
                index = simplemodeList.indexOf(current);;
            }
            isLongPress = false;
        });
        var erpageup = document.querySelectorAll('#_er-pageup');
        for (i = 0; i < erpageup.length; i++) {
            erpageup[i].onclick = function() {
                if (content.pageup) {
                    localStorage['_er-enable'] = 'true';
                    location.href = content.pageup;
                } else {
                    alert('很抱歉,没有匹配到上一页!');
                }
            };
        }
        var erpagedown = document.querySelectorAll('#_er-pagedown');
        for (i = 0; i < erpagedown.length; i++) {
            erpagedown[i].onclick = function() {
                if (content.pagedown) {
                    localStorage['_er-enable'] = 'true';
                    location.href = content.pagedown;
                } else {
                    alert('很抱歉,没有匹配到下一页!');
                }
            };
        }
        $('#_er-pagedown').dataset['nexturl'] = content.pagedown;
        setFontSize();
        setPadding();
 
        // 按钮事件处理
        $('#_er-close').onclick = removeDom;
        $('#_er-font-plus').onclick = function(){
            fontsize += 2;
            setFontSize();
        };
        $('#_er-font-minus').onclick = function(){
            fontsize -= 2;
            setFontSize();
        };
        $('#_er-border').onclick= function() {
            padding = padding == 10 ? 5 : 10;
            setPadding();
        }
        $('#_er-toTop').onclick= function() {
            document.querySelector('._er').scrollTop = 0;
        }
        $('#_er-tts').onclick = function(){
            if (this.dataset['pause'] === 'true'){
                // 开始播放
                this.innerText = '停止';
                this.dataset['pause'] = 'false';
                playNextText();
            }else{
                this.innerText = '听书';
                this.dataset['pause'] = 'true';
            }
        };
 
        if (autoplay){
            $('#_er-tts').innerText = '停止';
            $('#_er-tts').dataset['pause'] = 'false';
            playNextText();
        }else{
            $('#_er-tts').dataset['pause'] = 'true';
        }
    }
 
    // 听书功能
    function playNextText(){
        updateReadPos();
        var current = $('._er-current');
        var ernone = $('._er-none');
        var playText = '';
        if (current){
            playText = current.innerText;
        }else if(ernone) {
            playText = ernone.innerText;
            ernone.classList.add('_er-current');
            ernone.classList.remove('_er-none');
        }else{
            playText = $('._er-title').innerText;
        }
        if (playText){
            var utterThis = new SpeechSynthesisUtterance();
            utterThis.text = playText;
            utterThis.onerror = function(){
                $('#_er-tts').dataset['pause'] = 'true';
                alert("TTS语音转换文字出现异常,听书已停止运行!");
            };
            utterThis.onend = function(){
                toNextReadPos();
                if (!$('._er-current')){
                    var nextUrl = $('#_er-pagedown').dataset['nexturl'];
                    console.log(nextUrl);
                    if (nextUrl){
                        localStorage['_er-autoplay'] = 'true';
                        localStorage['_er-enable'] = 'true';
                        location.href = nextUrl;
                    }
                    return;
                }
                if ($('#_er-tts').dataset['pause'] === 'false'){
                    playNextText();
                }
            };
            speechSynthesis.speak(utterThis);
        }else{
            toNextReadPos();
            playNextText();
        }
    }
    function toNextReadPos(){
        var current = $('._er-current');
        var nextSpan = null;
         if (current){
            nextSpan = current.nextElementSibling;
            while(nextSpan && nextSpan.nodeName !== 'P'){
                nextSpan = nextSpan.nextElementSibling;
            }
        }else{
            nextSpan = $('._er-content p');
        }
        if (current) current.classList.remove('_er-current');
        if (nextSpan) nextSpan.classList.add('_er-current');
    }
 
    function toPrevReadPos(){
        var current = $('._er-current');
        var prevSpan = null;
        if (current){
            prevSpan = current.previousElementSibling;
            while(prevSpan && prevSpan.nodeName !== 'P'){
                prevSpan = prevSpan.previousElementSibling;
            }
        }
        if (current) current.classList.remove('_er-current');
        if (prevSpan) prevSpan.classList.add('_er-current');
    }
 
    function updateReadPos(){
        if ($('._er-current'))
            $('._er').scrollTop =  $('._er-current').offsetTop - (document.documentElement.clientHeight / 2);
    }
 
    function setFontSize(){
        localStorage["_er_fontsize"] = fontsize;
        $('._er-title').style.fontSize = (20+fontsize) + 'px';
        $('._er-title').style.lineHeight = ((20+fontsize)*1.5) + 'px';
        $('._er-content').style.fontSize = (14+fontsize) + 'px';
        $('._er-content').style.lineHeight = ((14+fontsize)*1.5) + 'px';
        $('.translator').style.fontSize = (10+fontsize) + 'px';
        $('.translator').style.lineHeight = ((10+fontsize)*1.5) + 'px';
    }
 
    function setPadding() {
        localStorage["_er_padding"] = padding;
        $('._er-content').style.padding = '10px ' + padding + '%';
    }

    function hasCSSRule(selector) {
        // 获取所有的样式表
        const styleSheets = document.styleSheets;
        for (let i = 0; i < styleSheets.length; i++) {
            const rules = styleSheets[i].cssRules || styleSheets[i].rules; // 兼容不同浏览器
            if (rules) {
                for (let j = 0; j < rules.length; j++) {
                    if (rules[j].selectorText === selector) {
                        return true;
                    }
                }
            }
        }

        return false;
    }
    function addCSSRule(selector, styles) {
        const styleSheet = document.styleSheets[0];
        const rule = `${selector} { ${styles} }`;
        styleSheet.insertRule(rule, styleSheet.cssRules.length);
    }
 
    var oldOverflow = '';
    var oldOnKeyDown = $('body').onkeydown;
 
    function removeDom(){
        var erScrollTop = document.querySelector('._er').scrollTop;
        var erScrollHeight = document.querySelector('._er').scrollHeight;
        var erClientHeight = document.documentElement.clientHeight;
        var scrollPercentage = (erScrollTop / (erScrollHeight - erClientHeight)) * 100;
        $('._er').remove();
        document.documentElement.style.overflow = 'auto';
        $('body').style.overflow = oldOverflow;
        $('body').onkeydown = oldOnKeyDown;
        $('body').children[0].insertAdjacentHTML('beforeBegin', '<button id="_er-entryReadMode" style="' +
            '    position: fixed;' +
            '    right: 50px;' +
            '    bottom: 50px;' +
            '    background-color: rgba(255,255,255,0.5);' +
            '    backdrop-filter: blur(1px);' +
            '    border: 0px solid black;' +
            '    border-radius: 10px;' +
            '    box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);' +
            '    padding: 0 5px;' +
            '    height: 50px;' +
            '    overflow: auto;' +
            '    font-size: 14px;' +
            '    color: black;' +
            '    z-index: 201901272210;">进入阅读模式</button>');
        $('#_er-entryReadMode').onclick = checkAndCreateReader;
        setTimeout(function() {
            var restoredScrollTop = ((erScrollHeight - erClientHeight) * (scrollPercentage / 100));
            window.scrollTo(0, restoredScrollTop);
        }, 0);
    }
 
    function addClassAndDom(){
        var originalScrollTop = window.pageYOffset;
        var originalScrollHeight = document.documentElement.scrollHeight;
        var originalClientHeight = document.documentElement.clientHeight;
        var scrollPercentage = (originalScrollTop / (originalScrollHeight - originalClientHeight)) * 100;
        oldOverflow = $('body').style.overflow;
        $('body').style.overflow = 'hidden';
 
        $('body').children[0].insertAdjacentHTML('beforeBegin',
            '<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no"><div class="_er">' +
            '    <button type="button" id="_er-tts" class="_er-tts">听书</button>' +
            '    <div class="_er-tools" style="margin-top:0;">' +
            '        <button type="button" id="_er-pageup"  class="buttonBottom" style="width:50%; margin: 0;">上一页</button>' +
            '        <button type="button" id="_er-pageindex"  class="buttonBottom" style="padding: 5px 40px; margin: 0;">目录</button>' +
            '        <button type="button" id="_er-pagedown"  class="buttonBottom" style="width:50%; margin: 0;">下一页</button>' +
            '    </div>' +
            '    <div class="_er-tools">' +
            '        <button type="button" id="_er-switch-theme" class="buttonTop">切换主题</button>' +
            '        <button type="button" id="_er-font-plus" class="buttonTop">字号+</button>' +
            '        <button type="button" id="_er-font-minus" class="buttonTop">字号-</button>' +
            '        <button type="button" id="_er-border" class="buttonTop">边距</button>' +
            '        <button type="button" id="_er-simplemode" class="buttonTop">简 / 繁</button>' +
            '        <button type="button" id="_er-toTop" class="toTop">' +
            '            <span class="first-line">回到</span>' +
            '            <span class="second-line">顶部</span>' +
            '        </button>' +
            '        <button type="button" id="_er-close" class="exit">退出</button>' +
            '    </div>' +
            '    <div class="_er-title"></div>' +
            '    <div class="translator"></div>' +
            '    <div class="_er-content">' +
            '    </div>' +
            '    <div class="_er-tools">' +
            '        <button type="button" id="_er-pageup"  class="buttonBottom" style="width: 50%;">上一页</button>' +
            '        <button type="button" id="_er-pageindex"  class="buttonBottom" style="padding: 5px 40px;">目录</button>' +
            '        <button type="button" id="_er-pagedown"  class="buttonBottom" style="width: 50%;">下一页</button>' +
            '    </div>' +
            '</div>');
        if (!document.querySelector('#_er-styles')) {
            $('body').children[0].insertAdjacentHTML('beforeBegin',
                '<style id="_er-styles">' +
                '* {' +
                    '-webkit-user-select: none; /* Safari */' +
                    '-moz-user-select: none; /* Firefox */' +
                    '-ms-user-select: none; /* IE 10+ */' +
                    'user-select: none; /* 标准语法 */' +
                '}' +
                '._er{' +
                '    position: fixed;' +
                '    left: 0;' +
                '    right: 0;' +
                '    top: 0;' +
                '    bottom: 0;' +
                '    overflow: auto;' +
                '    background-color: white;' +
                '    z-index: 201901272211;' +
                '}' +
                '._er-title{' +
                '    text-align: center;' +
                '    font-size: 20px;' +
                '    line-height: 30px;' +
                '    font-weight: 900;' +
                '    padding: 10px 10%;' +
                '    color: black;' +
                '}' +
                '.translator{' +
                '    text-align: center;' +
                '    font-size: 10px;' +
                '    line-height: 15px;' +
                '    padding: 0 10% 10px;' +
                '    color: black;' +
                '    display: none;' +
                '}' +
                '._er-content{' +
                '    padding: 10px 10%;' +
                '    font-size: 14px;' +
                '    line-height: 21px;' +
                '    color: black;' +
                '}' +
                '._er-tools{' +
                '    margin-top: 10px;' +
                '    margin-bottom: 10px;' +
                '    text-align: center;' +
                '    display: flex;' +
                '    justify-content: center;' +
                '}' +
                '.buttonTop{' +
                '    cursor: pointer;' +
                '    color: black;' +
                '    background-color: rgba(255,255,255,0.5);' +
                '    border: 1px solid black;' +
                '    padding: 5px 8px;' +
                '    margin: 8px 5px 0;' +
                '    border-radius: 10px;' +
                '    white-space: nowrap;' +
                '}' +
                '.buttonTop:hover{' +
                '    background-color: rgba(255,255,255,0.9);' +
                '}' +
                '.buttonTop:active{' +
                '    background-color: rgba(255,255,255,0.7);' +
                '}' +
                '.buttonBottom{' +
                '    cursor: pointer;' +
                '    color: black;' +
                '    background-color: rgba(255,255,255,0);' +
                '    border: 0.5px solid black;' +
                '    padding: 5px 50px;' +
                '    margin: 20px 0 80px;' +
                '    font-size: 20px;' +
                '    white-space: nowrap;' +
                '}' +
                '.buttonBottom:hover{' +
                '    background-color: rgba(255,255,255,0.8);' +
                '}' +
                '.buttonBottom:active{' +
                '    background-color: rgba(255,255,255,0.5);' +
                '}' +
                '.toTop{' +
                '    width: 50px;' +
                '    height: 50px;' +
                '    position: fixed;' +
                '    right: 15px;' +
                '    bottom: 80px;' +
                '    z-index: 201901272212;' +
                '    color: black;' +
                '    border: 0px solid black;' +
                '    box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);' +
                '    opacity: 0.8;' +
                '    cursor: pointer;' +
                '    border-radius: 25px;' +
                '    font-size: 14px;' +
                '    padding: 0;' +
                '    display: flex;' +
                '    flex-direction: column;' +
                '    align-items: center;' +
                '    justify-content: center;' +
                '}' +
                '.first-line,' +
                '.second-line {' +
                '    display: inline-block;' +
                '    white-space: nowrap;' +
                '}' +
                '.exit{' +
                '    width: 50px;' +
                '    height: 50px;' +
                '    position: fixed;' +
                '    right: 15px;' +
                '    bottom: 15px;' +
                '    z-index: 201901272212;' +
                '    color: black;' +
                '    border: 0px solid black;' +
                '    box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);' +
                '    opacity: 0.8;' +
                '    cursor: pointer;' +
                '    border-radius: 25px;' +
                '    font-size: 14px;' +
                '    padding: 0;' +
                '    white-space: nowrap;' +
                '}' +
                '._er-tts {' +
                '    width: 50px;' +
                '    height: 50px;' +
                '    position: fixed;' +
                '    left: 15px;' +
                '    bottom: 15px;' +
                '    z-index: 201901272212;' +
                '    color: black;' +
                '    border: 0px solid black;' +
                '    box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);' +
                '    opacity: 0.8;' +
                '    cursor: pointer;' +
                '    border-radius: 25px;' +
                '    font-size: 14px;' +
                '    padding: 0;' +
                '    white-space: nowrap;' +
                '    display: none;' +
                '}' +
                '._er-current{' +
                '    background-color: yellow;' +
                '    color: black;' +
                '}' +
                '._er-none{' +
                '}' +
                '</style>');
        }
        setTimeout(function() {
            var restoredScrollTop = ((originalScrollHeight - originalClientHeight) * (scrollPercentage / 100));
            document.querySelector('._er').scrollTop = restoredScrollTop;
        }, 0);
    }
    function addDialog(){
        if (!document.querySelector('#myDialog')) {
            $('body').children[0].insertAdjacentHTML('beforeBegin',
                                                     '<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no"><dialog id="myDialog">' +
                                                     '    <h2>设置</h2>' +
                                                     '    <div class="checkbox-container">' +
                                                     '        <div class="checkbox">' +
                                                     '            <label>' +
                                                     '                <input type="checkbox" name="scrollToTop" value="1"> 回到顶部按钮' +
                                                     '            </label>' +
                                                     '        </div>' +
                                                     '        <div class="checkbox">' +
                                                     '            <label>' +
                                                     '                <input type="checkbox" name="listen" value="1"> 听书按钮' +
                                                     '            </label>' +
                                                     '        </div>' +
                                                     '        <div class="font-selector">' +
                                                     '            <label for="fontSelect">选择字体样式:</label>' +
                                                     '            <select id="fontSelect">' +
                                                     '            </select>' +
                                                     '        </div>' +
                                                     '    </div>' +
                                                     '    <button id="closeDialog">确定</button>' +
                                                     '</dialog>');
        }
        if (!document.querySelector('#dialog-styles')) {
            $('body').children[0].insertAdjacentHTML('beforeBegin',
                '<style id="dialog-styles">' +
                '#myDialog {' +
                '    width: 300px;' +
                '    padding: 20px;' +
                '    margin: 0;' +
                '    background-color: #f2f2f2;' +
                '    border: 1px solid #ccc;' +
                '    border-radius: 10px;' +
                '    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);' +
                '    font-family: Arial, sans-serif;' +
                '    display: flex;' +
                '    flex-direction: column;' +
                '    align-items: center;' +
                '    position: fixed;' +
                '    top: 50%;' +
                '    left: 50%;' +
                '    transform: translate(-50%, -50%);' +
                '}' +
                '#myDialog::backdrop {' +
                '    background: rgba(30,30,30,0.2);' +
                '    backdrop-filter: blur(3px);' +
                '}' +
                '#myDialog h2 {' +
                '    margin-top: 0;' +
                '    font-size: 24px;' +
                '    font-weight: bold;' +
                '    margin-bottom: 10px;' +
                '}' +
                '.checkbox-container {' +
                '    display: flex;' +
                '    flex-direction: column;' +
                '    align-items: flex-start;' +
                '}' +
                '.checkbox {' +
                '    margin-bottom: 10px;' +
                '}' +
                '.font-selector {' +
                '    margin-bottom: 20px;' +
                '    display: block;' +
                '}' +
                '.font-selector label {' +
                '    font-size: 16px;' +
                '    font-weight: bold;' +
                '    margin-right: 10px;' +
                '    color: #333;' +
                '}' +
                '.font-selector select {' +
                '    font-size: 16px;' +
                '    padding: 5px;' +
                '    border: 1px solid #ccc;' +
                '    border-radius: 4px;' +
                '    background-color: #fff;' +
                '    cursor: pointer;' +
                '    transition: border-color 0.3s ease;' +
                '    color: black;' +
                '}' +
                '.font-selector select:hover {' +
                '    border-color: #007BFF;' +
                '}' +
                '.font-selector select:focus {' +
                '    outline: none;' +
                '    border-color: #007BFF;' +
                '    box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);' +
                '}' +
                '.checkbox label {' +
                '    display: block;' +
                '    margin: 0 0 10px;' +
                '    font-size: 16px;' +
                '}' +
                '.checkbox input[type="checkbox"] {' +
                '    border: 1px solid #B4B4B4;' +
                '    padding: 1px;' +
                '    margin: 3px;' +
                '    width: 16px;' +
                '    height: 16px;' +
                '    background: none;' +
                '    cursor: pointer;' +
                '    visibility: visible;' +
                '    position: static;' +
                '    vertical-align: middle;' +
                '}' +
                '#myDialog button {' +
                '    padding: 8px 16px;' +
                '    background-color: #4CAF50;' +
                '    color: #fff;' +
                '    border: none;' +
                '    border-radius: 4px;' +
                '    cursor: pointer;' +
                '    font-size: 14px;' +
                '}' +
                '#myDialog button:hover {' +
                '    background-color: #45a049;' +
                '}' +
                '</style>');
        }
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址