// ==UserScript== // @name 小说朗读 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 自用小说朗读 // @author FHT // @icon https://www.google.com/s2/favicons?sz=64&domain=csdn.net // @grant GM_addStyle // @grant unsafeWindow // @license MIT // @require https://code.jquery.com/jquery-3.6.3.min.js // @match https://www.scjld.net/*/*.html // @match https://www.douyinxs.com/bqg/*/*.html // @downloadURL none // ==/UserScript== (function() { 'use strict'; // Your code here... let css = ` *{ margin: 0; padding: 0; } #FHT_main { width: 100vw; height: 100vh; display: flex; overflow: hidden; background: url(http://qidian.gtimg.com/qd/images/read.qidian.com/theme/theme_1_bg_2x.0.3.png); } #FHT_catalogue { width: 250px; height: 100%; overflow: hidden; background: rgb(70,70,70); color: white; font-size: 14px; cursor: pointer; padding :8px; } #FHT_name { text-align: center; font-size: 24px; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding :10px 10px 15px ; border-bottom: 2px solid black; } #FHT_bottem { display: flex; justify-content:space-evenly; padding :10px; border-bottom: 1px solid black; } #FHT_chapters{ overflow: auto; height: 85%; } #FHT_chapters li { padding :10px; border-bottom: 1px solid black; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #FHT_contents { overflow: auto; width: 100%; height: 100%; min-width: 60%; font-size: 26px; line-height: 50px; white-space:pre-line; } .FHT_Tit { text-align: center; margin: 10px 80px; padding :10px; border-bottom: solid 1px rgb(134, 124, 124); font-size: 32px; } .FHT_content { margin: 0 200px; letter-spacing: 4px; } #FHT_read { position: fixed; top: 10px; right: 30px; } .li_activ{ background: #000; } .activ { color: red; } .remove { display: none; } a{ color: white; } a:visited{ color: white; }` GM_addStyle(css) //自定义规则 const DIYRule = [{ url: '域名', nextSelector: '下一页(jq选择器)', prevSelector: '上一页', indexSelector: '目录', titleSelector: '章节名', bookTitleSelector: '书名', contentSelector: '正文', contentReplace: '/去广告/' }] //通用规则 let Rule = { nextSelectors: ["a:contains('下一页')", "a:contains('下一章')", "a:contains('下一节')", "a:contains('下页')"], prevSelectors: ["a:contains('书首页')", "a:contains('上一页')", "a:contains('上一章')", "a:contains('上一节')", "a:contains('上页')"], indexSelectors: ["a:contains('返回书目')", "a:contains('章节目录')", "a:contains('章节列表')", "a:contains('最新章节')", "a:contains('回目录')", "a:contains('回书目')", "a:contains('目 录')", "a:contains('目录')"], titleSelectors: ['h1'], bookTitleSelectors: ['.con_top > a:last-of-type'], contentSelectors: ["#pagecontent", "#contentbox", "#bmsy_content", "#bookpartinfo", "#htmlContent", "#text_area", "#chapter_content", "#chapterContent", "#chaptercontent", "#partbody", "#BookContent", "#read-content", "#article_content", "#BookTextRead", "#booktext", "#book_text", "#BookText", "#BookTextt", "#readtext", "#readcon", "#read", "#TextContent", "#txtContent", "#text_c", "#txt_td", "#TXT", "#txt", "#zjneirong", ".novel_content", ".readmain_inner", ".noveltext", ".booktext", ".yd_text2", "#contentTxt", "#oldtext", "#a_content", "#contents", "#content2", "#contentts", "#content1", "#content", ".content"], contentReplace: [ ['/需要替换的字符串/g', '新字符串'], [/

/g, '
'], [/阅读最新章节请下载爱阅小说[\s\S]+/g, ''] ] } let body = $('body')[0] let BookData = { chapters: [], } const utterThis = new SpeechSynthesisUtterance(); let delBtn = false let focusText = 0 let state = 0 //网络获取的状态,防抖 let PageIndex = 0 const synth = window.speechSynthesis; synth.cancel() //初始化语音 let Voiceslist initialization() //语音朗读状态函数 utterThis.onstart = () => { FHT_del.classList.remove("remove"); voiceSelect.classList.add("remove"); FHT_play.classList.add("remove"); } utterThis.onend = () => { focusText = null $(`#FHT_Chapter_${BookData.readIndex}>.FHT_content`)[0].innerHTML = $(`#FHT_Chapter_${BookData.readIndex}>.FHT_content`)[0].innerText if (delBtn) { FHT_play.classList.remove("remove"); voiceSelect.classList.remove("remove"); FHT_del.classList.add("remove"); delBtn = false return } else if (BookData.readIndex < BookData.chapters.length - 1) { BookData.readIndex += 1 speaking($(`#FHT_Chapter_${BookData.readIndex}>.FHT_content`)[0].innerText) window.history.replaceState('', '', BookData.chapters[BookData.readIndex].next) FHT_contents.scrollTop = $(`#FHT_Chapter_${BookData.readIndex}`)[0].offsetTop } else { getAjax(BookData.chapters[BookData.readIndex].next).then((res) => { getData(/]*>([\s\S]*)<\/body>/.exec(res)[1]) BookData.readIndex += 1 speaking($(`#FHT_Chapter_${BookData.readIndex}>.FHT_content`)[0].innerText) FHT_contents.scrollTop = $(`#FHT_Chapter_${BookData.readIndex}`)[0].offsetTop }) } }; utterThis.onboundary = (event) => { let div = $(`#FHT_Chapter_${BookData.readIndex}>.FHT_content`)[0] let str let read_text = [] if (focusText) { let t1 = div.innerText.split(focusText)[0] let t2 = focusText + div.innerText.split(focusText)[1] str = t2.substr(event.charIndex, event.charLength) read_text[0] = t2.slice(0, event.charIndex) read_text[1] = t2.slice(event.charIndex) div.innerHTML = t1 + read_text[0] + read_text[1].replace(str, "" + str + "") } else { str = div.innerText.substr(event.charIndex, event.charLength) read_text[0] = div.innerText.slice(0, event.charIndex) read_text[1] = div.innerText.slice(event.charIndex) div.innerHTML = read_text[0] + read_text[1].replace(str, "" + str + "") } if ($('.activ')[0].offsetTop > FHT_contents.scrollTop + FHT_contents.clientHeight) { FHT_contents.scrollTop = $('.activ')[0].offsetTop - 50 } } //创建章节page function addPage() { let i = BookData.chapters.length - 1 let data = BookData.chapters[i] let url addTag(FHT_chapters, { name: 'li', key: 'id', value: 'FHT_Chapters_' + i, text: data.title }) addTag(FHT_contents, { name: 'div', key: 'id', value: 'FHT_Chapter_' + i }) let FHT_Chapter = $(`#FHT_Chapter_${i}`)[0] let li = $(`#FHT_Chapters_${i}`)[0] if (i == 0) { url = window.location.href; } else { url = BookData.chapters[i - 1].next } BookData.chapters[i].index = url //当前页url li.onclick = function () { FHT_contents.scrollTop = FHT_Chapter.offsetTop window.history.replaceState('', '', url) } addTag(FHT_Chapter, { name: 'div', key: 'class', value: 'FHT_Tit', text: data.title }) addTag(FHT_Chapter, { name: 'div', key: 'class', value: 'FHT_content', text: data.content.innerHTML }) //判断开始时是否有滚动条 if (FHT_contents.clientHeight >= FHT_contents.scrollHeight) { getAjax(BookData.chapters[i].next.href).then((res) => { getData(/]*>([\s\S]*)<\/body>/.exec(res)[1]) }) } window.history.replaceState('', '', url) // console.log(BookData); } //获取dom指定的内容,参数DOM.innerHTM function getData(domData) { addTag(body, { name: 'div', key: 'id', value: 'data_none', text: domData }); DIYRule.forEach(element => { if (element.url == window.location.host) { setData(element) } else { setData(Rule) } }); data_none.remove(); addPage() //规则函数 function setData(rule) { let data = {} // 目录 rule.indexSelectors.forEach(element => { if ($("#data_none " + element).length) { BookData.meun = $("#data_none " + element)[0].href } }) // 下一页 rule.nextSelectors.forEach(element => { if ($("#data_none " + element).length) { data.next = $("#data_none " + element)[0].href } }) // 上一页 rule.prevSelectors.forEach(element => { if ($("#data_none " + element).length) { data.prev = $("#data_none " + element)[0].href } }) // 章节名 rule.titleSelectors.forEach(element => { if ($("#data_none " + element).length) { data.title = $("#data_none " + element)[0].innerText } }) // 书名 rule.bookTitleSelectors.forEach(element => { if ($("#data_none " + element).length) { BookData.bookName = $("#data_none " + element)[0].innerText } }) // 正文 rule.contentSelectors.forEach(element => { if ($("#data_none " + element).length) { data.content = $("#data_none " + element)[0] } }) //去广告 rule.contentReplace.forEach(element => { if (data.content) { data.content.innerHTML = data.content.innerHTML.replace(element[0], element[1]) } }); BookData.chapters.push(data) } } //初始化 function initialization() { let data = body.innerHTML body.innerHTML = '' addTag(body, { name: 'div', key: 'id', value: 'FHT_main' }) addTag(FHT_main, { name: 'div', key: 'id', value: 'FHT_catalogue' }) addTag(FHT_main, { name: 'div', key: 'id', value: 'FHT_contents' }) addTag(FHT_main, { name: 'div', key: 'id', value: 'FHT_read' }) addTag(FHT_catalogue, { name: 'div', key: 'id', value: 'FHT_name', }) addTag(FHT_catalogue, { name: 'div', key: 'id', value: 'FHT_bottem' }) addTag(FHT_catalogue, { name: 'ul', key: 'id', value: 'FHT_chapters' }) addTag(FHT_read, { name: 'select', key: 'id', value: 'voiceSelect' }) addTag(FHT_read, { name: 'button', key: 'id', value: 'FHT_play', text: '播放' }) addTag(FHT_read, { name: 'button', key: 'id', value: 'FHT_del', text: '终止' }); FHT_del.classList.add("remove"); addTag(FHT_read, { name: 'button', key: 'id', value: 'FHT_suspend', text: '暂停' }) addTag(FHT_read, { name: 'button', key: 'id', value: 'FHT_recovery', text: '恢复' }); FHT_recovery.classList.add("remove"); readBtn() getData(data) FHT_name.innerText = BookData.bookName addTag(FHT_bottem, { name: 'a', key: 'id', value: 'FHT_prev', text: '上一章' }); FHT_prev.href = BookData.chapters[BookData.chapters.length - 1].prev addTag(FHT_bottem, { name: 'a', key: 'id', value: 'FHT_index', text: '目 录' }); FHT_index.href = BookData.meun addTag(FHT_bottem, { name: 'a', key: 'id', value: 'FHT_next', text: '下一章' }); FHT_next.href = BookData.chapters[BookData.chapters.length - 1].next } //read按钮函数 function readBtn() { FHT_play.onclick = function () { BookData.readIndex = BookData.PageIndex let text = $(`#FHT_Chapter_${BookData.readIndex}>.FHT_content`)[0].innerText //判断是否有选中文字 if (window.getSelection().toString()) { focusText = window.getSelection().toString() text = focusText + text.split(focusText)[1] } speaking(text) } FHT_del.onclick = function () { delBtn = true synth.cancel() } FHT_suspend.onclick = function () { synth.pause() FHT_recovery.classList.remove("remove"); FHT_suspend.classList.add("remove"); } FHT_recovery.onclick = function () { synth.resume() FHT_suspend.classList.remove("remove"); FHT_recovery.classList.add("remove"); } } //监听函数 FHT_contents.addEventListener("scroll", () => { //监听正文滚动到400 if (FHT_contents.scrollHeight - FHT_contents.scrollTop - FHT_contents.clientHeight <= 400) { state += 1 if (state == 1) { getAjax(BookData.chapters[BookData.chapters.length - 1].next).then((res) => { state = 0 getData(/]*>([\s\S]*)<\/body>/.exec(res)[1]) }) } } let array = $('#FHT_contents>div') //判断当前页面在第几章 switch (array.length) { case 1: BookData.PageIndex = 0; break; case 2: FHT_contents.scrollTop >= array[1].offsetTop ? BookData.PageIndex = 1 : BookData.PageIndex = 0 break; default: for (let index = 0; index < array.length - 1; index++) { if (array[index].offsetTop <= FHT_contents.scrollTop && FHT_contents.scrollTop < array[index + 1].offsetTop) { BookData.PageIndex = index break } else { FHT_contents.scrollTop < array[1].offsetTop ? BookData.PageIndex = 0 : BookData.PageIndex = index + 1 } } break; } }, true); Object.defineProperty(BookData, 'PageIndex', { get() { return PageIndex }, set(value) { let a = $('#FHT_chapters>li') PageIndex = value; Array.from(a).forEach((e, i) => { if (i == value) { e.classList.add("li_activ"); } else { e.classList.remove("li_activ"); } }); } }) //AJAX function getAjax(url) { let xhr = new XMLHttpRequest(); xhr.open("GET", url); let promise = new Promise(function (resolve, reject) { xhr.onreadystatechange = function () { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; xhr.send(); }); return promise; }; //创建标签 function addTag(fatherTag, sonTag) { let tag = document.createElement(sonTag.name) tag.setAttribute(sonTag.key, sonTag.value) tag.innerHTML = sonTag.text || '' fatherTag.appendChild(tag) } //获取朗读引擎 function getVoiceList() { voiceSelect.innerHTML = '' Voiceslist = []; let voices = synth.getVoices() for (let i = 0; i < voices.length; i++) { if (voices[i].lang == 'zh-CN') { Voiceslist.push(voices[i]) let option = document.createElement('option'); option.textContent = voices[i].name + ' (' + voices[i].lang + ')'; if (voices[i].default) { option.textContent += ' -- DEFAULT'; } option.setAttribute('data-lang', voices[i].lang); option.setAttribute('data-name', voices[i].name); voiceSelect.appendChild(option); } } voiceSelect.selectedIndex = 5 //默认语音 } if (speechSynthesis.onvoiceschanged !== undefined) { speechSynthesis.onvoiceschanged = getVoiceList; } //语音播放函数 function speaking(text) { utterThis.voice = Voiceslist[voiceSelect.selectedIndex] utterThis.text = text synth.speak(utterThis) } })();