起点听书/www.qidian.com

阅读界面左侧功能栏增加“听书”按钮,点击“听书”开始朗读:Esc-结束朗读;空格-暂定/继续(360安全浏览器急速模式(已测试),Chrome浏览器);后台静默复制文章内容到剪贴板。

// ==UserScript==
// @name         起点听书/www.qidian.com
// @namespace    yoursatan
// @version      0.2
// @description  阅读界面左侧功能栏增加“听书”按钮,点击“听书”开始朗读:Esc-结束朗读;空格-暂定/继续(360安全浏览器急速模式(已测试),Chrome浏览器);后台静默复制文章内容到剪贴板。
// @author       yorusatan
// @include      https://read.qidian.com/chapter/*
// @grant        none
// @require      https://code.jquery.com/jquery-2.1.4.min.js
// @license      MIT License
// ==/UserScript==
// v0.2 修复一些使用中发现的bug。
// v0.1 在阅读界面左侧功能栏添加“听书”按钮,点击“听书”开始朗读:Esc-结束朗读;空格-暂定/继续(360安全浏览器急速模式(已测试),Chrome浏览器);后台静默复制文章内容到剪贴板。
(function() {
    "use strict";

    // 用于获取story内文本的父元素全体
    var storyAll = "";
    // 用于获取story文本全体
    var story = "";
    // 用于存储格式化后story文本全体
    var newStory = "";
    // 侧边栏添加 听书 按钮
    $(".left-bar-list dl").append(
        '<dd style="background:#e60022;"><a href="javascript:"><i><em class="iconfont">&#xe60f;</em><span id ="btnSmartRead">听书</span></i></a><div class="guide-box"><cite>   </cite></div></dd>'
    );

    // 获取文章内容
    story = $(".read-content")
        .html()
        .replace(/<\/?p data-type="2">/gi, "\n")
        .replace(/<\/p>/gi, "\n")
        .replace(/<span class="content-wrap">/g, "\n")
        .replace(/<i><cite><\/cite><\/i>/g, "\n")
        .replace(/<span class="review-count.*data-segid=\"\d*\">\d*/g, "\n")
        .replace(/<\/span>/g, "\n");

    $(".read-content").empty();
    $(".read-content").append("<div id='storycontent'></div>");
    // hover 事件
    $("#btnSmartRead").hover(
        function() {
            $("#btnSmartRead").css({ color: "white" });
        },
        function() {
            $("#btnSmartRead").css({ color: "black" });
        }
    );

    // 将原文本进行格式化,分割存储;
    var storyArr = story
        .replace(/<div class="hc">[\s\S].*<\/div>/gi, "")
        .replace(/<\/?p>/gi, "\n")
        .replace(/<\/?div>/gi, "\n")
        .replace(/<br\s*\/?>/gi, "\n")
        .replace(/\n(\n)*( )*(\n)*\n/g, "\n")
        .replace(/\&nbsp;/g, "")
        .split(/\n/);

    // 用于存储格式化后,并移除空行文本数组
    var newStoryArr = [];

    // 移除数组空项(文本空行)
    const countPara = storyArr.length;
    for (var i = 0; i < countPara; i++) {
        storyArr[i] = storyArr[i].replace(/\s+/g, " ").trim();
        if (storyArr[i] != "") {
            newStory += "<p>" + storyArr[i] + "</p>";
            newStoryArr.push(storyArr[i]);
        }
    }

    // 重新加载文章内容,并更改默认样式
    $("#storycontent").append(newStory);
    $("#storycontent").css({
        "font-size": "1em",
        "line-height": "1.5em",
        "font-family": "sans-serif",
        "font-weight": 300
    });
    const newCountPara = newStoryArr.length;

    // 用于逐段朗读
    var flag = 0;

    // 朗读
    var speaker = new window.SpeechSynthesisUtterance();
    speaker.rate = 1.24;
    speaker.lang = "en-US";
    speaker.voiceURI = "Microsoft Zira Desktop - English (United States)";
    speaker.lang = "zh-CN";
    speaker.voiceURI = "Microsoft Huihui Desktop - Chinese (Simplified)";
    $("#storycontent").css("font-family", "PingFangSC-Regular");
    // 多次尝试再for循环中无法循环朗读,故添加flag步进;利用setInterval进行循环。
    $("#btnSmartRead").click(function() {
        // 复制文章内容到剪贴板
        if (
            // https://read.qidian.com/chapter/ 网站支持
            window.location.href.indexOf("https://read.qidian.com/chapter/") >
            -1
        ) {
            // 功能参考插件Enable Copy:https://bitbucket.org/keakon/enable-copy/src/default/enable.js
            // 非常感谢!Sun Jing。Thanks very much.
            var doc = document;
            var body = doc.body;
            var html = doc.documentElement;
            function allowUserSelect(element) {
                element.setAttribute(
                    "style",
                    "-webkit-user-select: text !important"
                );
                element.setAttribute("style", "user-select: text !important");
                return element;
            }

            function allowUserSelectById(element_id) {
                return allowUserSelect(doc.getElementById(element_id));
            }

            function allowUserSelectByClassName(element_class) {
                var elements = doc.getElementsByClassName(element_class);
                var len = elements.length;
                for (var i = 0; i < len; ++i) {
                    allowUserSelect(elements[i]);
                }
                return elements;
            }

            function clearHandlers() {
                // html.onselectstart = html.oncopy = html.oncut = html.onpaste = html.onkeydown = html.oncontextmenu = html.onmousemove = body.oncopy = body.oncut = body.onpaste = body.onkeydown = body.oncontextmenu = body.onmousedown = body.onmousemove = body.onselectstart = body.ondragstart = doc.onselectstart = doc.oncopy = doc.oncut = doc.onpaste = doc.onkeydown = doc.oncontextmenu = doc.onmousedown = doc.onmouseup = window.onkeyup = window.onkeydown = null;
                // 起点网下列三个设置足以
                html.oncopy = body.oncopy = doc.oncopy = null;
                allowUserSelect(html);
                allowUserSelect(body);
            }
            clearHandlers();

            function defaultHandler(event) {
                event.returnValue = true;
            }

            for (var event_type in ["copy", "cut", "paste"]) {
                // 起点网,以上三个足以;
                // var event_type in ['selectstart', 'copy', 'cut', 'paste', 'keydown', 'contextmenu', 'dragstart']
                html.addEventListener(event_type, defaultHandler);
                body.addEventListener(event_type, defaultHandler);
                doc.addEventListener(event_type, defaultHandler);
            }

            function removeEventAttributes(element) {
                /*
                element.removeAttribute('oncontextmenu');
                element.removeAttribute('ondragstart');
                element.removeAttribute('onselectstart');
                element.removeAttribute('onselect');
                element.removeAttribute('oncopy');
                element.removeAttribute('onbeforecopy');
                element.removeAttribute('oncut');
                element.removeAttribute('onpaste');
                element.removeAttribute('onclick');
                element.removeAttribute('onmousedown');
                element.removeAttribute('onmouseup');
                */
                // 起点网设置这一个足以
                element.removeAttribute("oncopy");
            }

            var jQuery = window.jQuery;

            var $Fn = window.$Fn;
            if ($Fn) {
                try {
                    $Fn.freeElement(doc);
                    $Fn.freeElement(body);
                } catch (e) {}
            }
            /* // 不需要
            var jindo = window.jindo;
            if (jindo) {
            jindo.$A = null;
            }
            */

            function replaceElementEventsWithClone(element) {
                var clone = element.cloneNode();
                while (element.firstChild) {
                    clone.appendChild(element.firstChild);
                }
                element.parentNode.replaceChild(clone, element);
            }

            function replaceElementsEventsWithClone(elements) {
                var length = elements.length;
                for (var i = 0; i < length; ++i) {
                    replaceElementEventsWithClone(elements[i]);
                }
            }

            var url = doc.URL;
            var domain_pattern = /^https?:\/\/([^\/]+)/;
            var result = domain_pattern.exec(url);
            if (result) {
                var domain = result[1];
                if (
                    domain.length > 11 &&
                    domain.substr(-11, 11) == ".lofter.com"
                ) {
                    replaceElementsEventsWithClone(jQuery(".pic>a"));
                    return;
                }

                switch (domain) {
                    case "wenku.baidu.com":
                        jQuery(".doc-reader")
                            .off("copy")
                            .removeAttr("oncopy");
                        jQuery("#reader-container-1").off("copy");
                        break;
                    case "www.qidian.com":
                    case "read.qidian.com":
                    case "vipreader.qidian.com":
                    case "big5.qidian.com":
                    case "www.qdmm.com":
                        var element = doc.getElementById("bigcontbox");
                        if (element) {
                            element.onmousedown = null;
                        }
                        //jQuery(body).off('contextmenu copy cut');
                        // 可使用复制功能
                        jQuery(body).off("copy");
                        break;
                }
                /*
                // 百度文库不能覆盖这些事件
                // 起点会造成无限递归 bug
                if (jQuery) {
                var $doc = jQuery(doc);
                var $body = jQuery(body);
                if ($doc.off) {
                    $doc.off();
                    $body.off();
                    jQuery(window).off();
                    } else {
                    $doc.unbind();
                    $body.unbind();
                    jQuery(window).unbind();
                    }
                }
                */
            }
        }
        // 完成后台复制
        var copyStory = document.createElement("textarea"); //创建textarea对象
        copyStory.id = "copyArea";
        $("#storycontent").prepend(copyStory); //添加元素
        var storyTitle = $("head title").html();
        copyStory.value = storyTitle + "\n" + newStoryArr.join("\n"); // 组合文章标题
        copyStory.focus();
        if (copyStory.setSelectionRange) {
            copyStory.setSelectionRange(0, copyStory.value.length); //获取光标起始位置到结束位置
        } else {
            copyStory.select();
        }
        document.execCommand("Copy", "false", null); //执行复制
        if (document.execCommand("Copy", "false", null)) {
            console.log(
                "已复制文章到剪贴板!Success,The story  has been copied to clipboard!--yoursatan"
            );
        }
        $("#copyArea").remove(); //删除元素

        // 朗读文字数组
        var storyAllRead = newStoryArr;

        // 用于文字选中效果
        var range = document.createRange();
        var selection = window.getSelection();

        // 朗读
        var readStory = function() {
            var reading = setInterval(function() {
                if (!window.speechSynthesis.speaking && flag < newCountPara) {
                    speaker.text = storyAllRead[flag];
                    window.speechSynthesis.speak(speaker);
                    flag += 1;

                    // 朗读段落文字选中效果
                    var referenceNode = document
                        .getElementById("storycontent")
                        .childNodes.item(flag - 1);
                    // 起点网朗读效果,当前朗读段落文字变红
                    $("#storycontent p")
                        .eq(flag - 1)
                        .css("color", "red");
                    $("html,body").animate(
                        {
                            scrollTop:
                                $("#storycontent p")
                                    .eq(flag - 1)
                                    .offset().top -
                                document.documentElement.clientHeight * 0.382
                        },
                        500 /*scroll实现定位滚动*/
                    ); //代码参考,感谢:https://blog.csdn.net/qq_30109365/article/details/86592336
                    if (flag - 1) {
                        $("#storycontent p")
                            .eq(flag - 2)
                            .css("color", "black");
                    }
                } else if (flag >= newCountPara) {
                    // 朗读结束
                    // window.speechSynthesis.cancel();
                    clearInterval(reading);
                    selection.removeAllRanges();

                    $("#storycontent p")
                        .eq(flag - 1)
                        .css("color", "black");

                    flag = 0;
                    alert("The story is finished");
                }
            }, 500);
            // 监听键盘:Esc/F5
            $(document).keyup(function(event) {
                if (event.keyCode == 27 || event.keyCode == 116) {
                    window.speechSynthesis.cancel();
                    clearInterval(reading);
                    selection.removeAllRanges();
                    if (
                        // https://read.qidian.com/chapter/ 网站支持
                        window.location.href.indexOf(
                            "https://read.qidian.com/chapter/"
                        ) > -1
                    ) {
                        $("#storycontent p")
                            .eq(flag - 1)
                            .css("color", "black");
                    }
                    flag = 0;
                }
            });

            // 监听键盘:空格键
            $(document).keypress(function(event) {
                if (event.keyCode == 32) {
                    if (window.speechSynthesis.speaking) {
                        window.speechSynthesis.pause();
                    }
                    if (window.speechSynthesis.paused) {
                        window.speechSynthesis.resume();
                    }
                }
            });

            // 监听标签关闭事件
            window.onbeforeunload = function(e) {
                clearInterval(reading);
                window.speechSynthesis.cancel();
            }
        };
        readStory();
    });
})();