您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
阅读界面右侧功能栏增加“听书”按钮、“语音”按钮。点击“听书”开始朗读:Esc-结束朗读;空格-暂定/继续;后台静默复制文章内容到剪贴板。点击“语音”按钮,打开设置页,可以调整语速。
// ==UserScript== // @name 起点听书/www.qidian.com // @namespace yoursatan // @version 0.4 // @description 阅读界面右侧功能栏增加“听书”按钮、“语音”按钮。点击“听书”开始朗读:Esc-结束朗读;空格-暂定/继续;后台静默复制文章内容到剪贴板。点击“语音”按钮,打开设置页,可以调整语速。 // @author yorusatan // @include https://www.qidian.com/chapter* // @include https://read.qidian.com/chapter* // @grant none // @require https://code.jquery.com/jquery-2.1.4.min.js // @license MIT License // @require https://scriptcat.org/lib/513/2.0.1/ElementGetter.js#sha256=V0EUYIfbOrr63nT8+W7BP1xEmWcumTLWu2PXFJHh5dg= // ==/UserScript== // v0.4 增加“语音(设置)”按钮,可调整语速。 // v0.3.1 小调整。 // v0.3 修复功能,优化代码。 // v0.2 修复一些使用中发现的bug。 // v0.1 在阅读界面右侧功能栏添加“听书”按钮,点击“听书”开始朗读:Esc-结束朗读;空格-暂定/继续;后台静默复制文章内容到剪贴板。 (async function () { "use strict"; // 获取文章内容 const str = await elmGetter.get("main"); const storyContent = $(str).children("p"); const storyTextArr = Array.from(storyContent).map((el) => el.textContent); const newStoryArr = []; window.speechSynthesis.getVoices(); // 侧边栏添加 听书 按钮 const rMenu = await elmGetter.get("#r-menu"); const btnRead = `<div data-v-6cdbc58a data-v-47ffe1e class="tooltip-wrapper relative flex" style="margin-bottom:10px"> <a id ="btnRead" data-v-47ffe1ec target="#" href="javascript"> <button data-v-47ffe1ec class="w-64px h-64px flex flex-col items-center justify-center rounded-8px bg-sheet-b-gray-50 text-s-gray-900 noise-bg group hover:bg-sheet-b-bw-white hover:text-primary-red-500 hover:bg-none"> <span class="icon-audio text-24px"></span> <span class="text-bo4 text-s-gray-500 mt-2px group-hover:text-primary-red-500" style="font-weight:600;">听书</span> </button></a><!----></div>`; const btnReadSet = `<div data-v-6cdbc58a data-v-47ffe1e class="tooltip-wrapper relative flex" style="margin-bottom:10px"> <a id ="btnReadSet" data-v-47ffe1ec target="#" href="javascript"> <button data-v-47ffe1ec class="w-64px h-64px flex flex-col items-center justify-center rounded-8px bg-sheet-b-gray-50 text-s-gray-900 noise-bg group hover:bg-sheet-b-bw-white hover:text-primary-red-500 hover:bg-none"> <span class="icon-setting-bold text-24px"></span> <span class="text-bo4 text-s-gray-500 mt-2px group-hover:text-primary-red-500" style="font-weight:600;">语音</span> </button></a><!----></div>`; const readSetPage = ` <section id="readSetPage" class="bg-sheet-b-bw-white shadow-sd16 w-480px pl-32px pt-42px pb-44px absolute right-full top-64px" hidden> <button class="bg-s-gray-100 w-28px h-28px rounded-1 flex items-center justify-center hover-24 active-10 p-0 absolute right-10px top-10px"> <span class="icon-close text-20px text-s-gray-400"></span> </button> <div class="text-rh4 font-medium text-s-gray-900">设置</div> <div class="w-359px"> <div class="flex items-center mt-32px"><span class="text-s4 font-medium text-s-gray-500 sm:pr-12px mr-16px flex-shrink-0">语速调整</span> <div class="flex flex-grow"> <span class="text-s4 font-medium text-s-gray-500 ">0.8 </span> <input id="slider" type="range" value="1.25" min="0.8" max="2" step="0.1" style="width:270px;margin:0 5px"> <span class="text-s4 font-medium text-s-gray-500 "> 2.0</span> </div> </div> <div hidden> <div class="flex items-center mt-20px"><span class="text-s4 font-medium text-s-gray-500 sm:pr-12px mr-16px flex-shrink-0" >选择语音</span> <div class="flex flex-grow" > <select aria-label="选择语音" class="text-s4 w-320px" id="voiceList" > </select> </div> </div> </div> </div> </section> `; $(rMenu).prepend($(readSetPage)).prepend($(btnReadSet)).prepend($(btnRead)); setTimeout(() => { const voicesArr = window.speechSynthesis.getVoices(); const voices = voicesArr.filter((item) => item.lang.includes("zh-")); let options = ``; for (i = 0; i < voices.length; i++) { options += `<option value="${voices[i].voiceURI}">${voices[i].name .replace("Microsoft ", "") .replace(" Chinese ", "")}</option>`; } $("#voiceList").append(options); $("#voiceList option:first").prop("selected", true); }, 100); let voice = ""; $("#voiceList").change(function () { voice = $("#voiceList option:selected").prop("value"); }); $("#btnReadSet").click(function () { event.preventDefault(); $("#readSetPage").toggle(); }); $(".icon-close").click(() => { $("#readSetPage").toggle(); }); $("#r-menu") .children() .first() .click(function () { event.preventDefault(); }); // 移除数组空项(文本空行) const countPara = storyTextArr.length; for (var i = 0; i < countPara; i++) { storyTextArr[i] = storyTextArr[i].replace(/\s+/g, " ").trim(); if (storyTextArr[i] != "") { newStoryArr.push(storyTextArr[i]); } } const newCountPara = newStoryArr.length; // 用于逐段朗读 var flag = 0; // 朗读 $("#btnRead").click(function () { event.preventDefault(); // 朗读文字数组 var storyAllRead = newStoryArr; // 用于文字选中效果 var range = document.createRange(); var selection = window.getSelection(); if (window.speechSynthesis.speaking) { window.speechSynthesis.pause(); } if (window.speechSynthesis.paused) { window.speechSynthesis.resume(); } // 朗读 var readStory = function () { var speaker = new window.SpeechSynthesisUtterance(); speaker.rate = $("#slider").val(); $("#slider").on("input", function () { speaker.rate = $(this).val(); }); speaker.lang = "zh-CN"; speaker.pitch = 1.24; speaker.voiceURI = voice === "" ? "Microsoft Huihui - Chinese (Simplified, PRC)" : voice; var reading = setInterval(function () { if (!window.speechSynthesis.speaking && flag <= newCountPara) { speaker.text = storyAllRead[flag]; window.speechSynthesis.speak(speaker); flag += 1; // 朗读段落文字选中效果 var referenceNode = document .getElementsByTagName("main p") .item(flag - 1); // 起点网朗读效果,当前朗读段落文字变红 $("main p") .eq(flag - 1) .css("color", "red"); $("html,body").animate( { scrollTop: $("main p") .eq(flag - 1) .offset().top - document.documentElement.clientHeight * 0.382 }, 300 /*scroll实现定位滚动*/ ); //代码参考,感谢:https://blog.csdn.net/qq_30109365/article/details/86592336 if (flag - 1) { $("main p") .eq(flag - 2) .css("color", "black"); } } else if (flag > newCountPara) { // 朗读结束 window.speechSynthesis.cancel(); clearInterval(reading); selection.removeAllRanges(); flag = 0; $("main p") .eq(flag - 1) .css("color", "black"); $("main p").eq(flag).css("color", "black"); alert("本章已读完。"); } }, 300); // 后台复制文章内容到剪贴板 var copyStory = document.createElement("textarea"); //创建textarea对象 copyStory.id = "copyArea"; $("main").prepend(copyStory); //添加元素 var storyTitle = $(".title") .contents() .filter(function () { return this.nodeType === 3; // 过滤掉非文本节点 }) .text(); 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(); //删除元素 // 监听键盘: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 || window.location.href.indexOf("https://www.qidian.com/chapter/") > -1 ) { $("main p") .eq(flag - 1) .css("color", "black"); } flag = 0; } }); // 监听键盘:空格键 $(document).keypress(function (event) { event.preventDefault(); 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(); }); })();