/*
*/
// ==UserScript==
// @name utTeRAnCE_0803
// @namespace Violentmonkey Scripts
// @match *://*/*
// @grant none
// @version 1.0
// @author -
// @description 2025/6/30 13:45:42
// ==/UserScript==
(() => {
'use strict';
const createWindow = ( elem, param ) => {
const div = document.createElement("div");
div.id = param;
const oWin = new Object();
oWin.callback = (panel) => panel.content.appendChild( div );
oWin.opacity = .9;
oWin.theme = "primary";
switch ( param ) {
case undefined:
oWin.contentSize = "400 250";
const identify = Date.now().toString( 36 );
oWin.headerTitle = identify;
oWin.id = identify;
oWin.position = "right-top -10 125";
break;
case "dashBoard":
oWin.contentSize = "450 250";
oWin.headerTitle = param;
oWin.id = param;
oWin.position = "right-bottom -10 -10";
break;
}
jsPanel.create( oWin );
if ( elem ) {
div.appendChild( elem );
}
}
const matrixRetrieve = ( branch ) => {
const oM = new Object();
oM.jsPanel = new Object();
oM.jsPanel.existing = typeof jsPanel;
oM.jsPanel.scheduled = "object"; // `jsPanel` in window
oM.jsPanel.referS = [
"https://jspanel.de/jspanel/dist/jspanel.min.css",
"https://jspanel.de/jspanel/dist/jspanel.min.js",
];
oM.jsPanel.method = ( elem, param ) => {
createWindow ( elem, param );
};
return oM [ branch ];
}
const createByExtens = ( urlFile, fileExtens ) => {
switch ( fileExtens ) {
case '.css':
const linkRefer = document.createElement('link');
linkRefer.href = urlFile;
linkRefer.setAttribute('rel', 'stylesheet');
return linkRefer;
case '.js':
case '.md':
const scriptRefer = document.createElement('script');
scriptRefer.src = urlFile;
return scriptRefer;
default:
console.log(fileExtens);
break;
}
}
const appendRefer = ( referUrl ) => {
const fileExtension = referUrl.match(/\.[^/.]+$/);
const referElem = createByExtens( referUrl, fileExtension[0] );
const fileName = referUrl.match(/[^\/=\b]+(?=\.[^\/.]*$)/)[0];
referElem.id = fileName.replace(/\./g,'_')
+ fileExtension[0].replace(/\./g,'_');
return referElem;
}
const secuReFerShell = ( referObj, targetElem, param ) => {
if ( referObj.existing === referObj.scheduled ) {
referObj.method( targetElem, param );
} else {
const urlS = referObj.referS;
const exeCuTable = /(_JS\.md|\.js)$/i;
urlS.forEach(( url ) => {
const tag = appendRefer ( url );
if ( exeCuTable.test(url) ) {
tag.addEventListener("load", () => {
referObj.method( targetElem, param );
});
}
document.body.appendChild(tag);
});
}
}
const initializeVoiceSC = ( elem ) => {
const mLingual_r = /Multilingual /i;
const rLang = /zh-/i;
speechSynthesis.addEventListener("voiceschanged", () => {
const voices = synthGetVoices ();
const arrayEmpty = new Array();
if ( elem.length < 1 ) {
for (const [i, voice] of voices.entries()) {
const condition = mLingual_r.test( voice.name )
|| rLang.test( voice.lang );
if ( condition ) {
arrayEmpty.push( voice );
const option = document.createElement("option");
option.textContent = `[${ i }]【${ voice.lang }】${ voice.name }`;
option.value = i;
elem.appendChild( option );
}
}
}
});
}
const visualizeOutlet = ( unitIn ) => {
const jsPanel = matrixRetrieve ( "jsPanel" );
const div = document.createElement("div");
div.appendChild( unitIn );
secuReFerShell ( jsPanel, div, "outLet" );
}
const parsePassage = ( txtIn ) => {
const regExp = /(?<=[\n\r!,?。!,?])/i;
const sentenceS = txtIn.split( regExp );
return sentenceS;
}
const toggleMount = ( content ) => {
const elem = document.querySelector("div#outLet");
if ( elem ) {
elem.appendChild( content );
} else {
visualizeOutlet ( content );
}
}
const visualizeCluster = ( arr ) => {
const div = document.createElement("div");
arr.forEach(( elem ) => {
const pTag = document.createElement("p");
pTag.textContent = elem;
div.appendChild( pTag );
});
return div;
}
const visualizeComponentS = () => {
const textarea = document.createElement("textarea");
textarea.addEventListener("dblclick", () => {
textarea.value = "";
});
textarea.addEventListener("paste", () => {
setTimeout(() => {
artIculate ( textarea.value );
}, 1);
});
textarea.cols = "50";
textarea.rows = "10";
textarea.style.overflow = "auto";
textarea.value = "故大德必得其位,必得其禄。必得其名,必得其寿,故天之生物,必因其材而笃焉。";
const button = document.createElement("button");
button.addEventListener("click", () => {
styleCssInject ();
});
button.textContent = "button";
const newWin = document.createElement("button");
newWin.addEventListener("click", () => {
const array = parsePassage ( textarea.value );
artiCULate ( array );
});
newWin.textContent = "newWin";
const select = document.createElement("select");
select.id = "voiceSSel";
select.addEventListener("change", () => {
artiCULate ( textarea.value, select.value );
});
const container = document.createElement("div");
container.appendChild( textarea );
container.appendChild( button );
container.appendChild( newWin );
container.appendChild( select );
return container;
}
const renderText = (arr) => {
// 仅在非 iframe 模式下才清空和重新生成 DOM
if (!isIframeMode) {
textContainer.innerHTML = "";
arr.forEach((sentence, index) => {
const sentenceElement = document.createElement("span");
sentenceElement.classList.add("sentence");
sentenceElement.textContent = sentence;
sentenceElement.dataset.index = index;
textContainer.appendChild(sentenceElement);
});
} else {
// 在 iframe 模式下,直接修改页面上的 P 元素
pTagsInIframe.forEach((elem, index) => {
elem.dataset.index = index;
elem.classList.remove('highlight-sentence'); // 清除旧高亮
elem.textContent = currentArrSchedule[index]; // 恢复为纯文本
});
}
};
// 辅助函数:根据模式查找元素
const findElementByIndex = ( index ) => {
if ( isIframeMode ) {
return pTagsInIframe [ index ];
} else {
const el = document.querySelector( `.sentence[data-index="${index}"]` );
return el;
}
}
const utterRecursive = ( index ) => {
const currentSentenceElement = findElementByIndex(index);
if (currentSentenceElement) {
// 确保在朗读开始时才应用句子高亮
currentSentenceElement.classList.add('highlight-sentence');
}
// 关键改动:设置语音
if (voices.length > 0) {
// 从 voxIdx 数组中获取当前语音的索引
const voiceIdx = voxIdx[currentVoiceIndex % voxIdx.length];
utterance.voice = voices[voiceIdx];
// 更新语音索引,以便下一次朗读使用不同的语音
currentVoiceIndex++;
}
utterance.text = currentArrSchedule[index];
window.speechSynthesis.speak( utterance );
};
const artiCULate = ( arrSchedule ) => {
// 停止之前的朗读
window.speechSynthesis.cancel();
// 1. 检查URL,决定是否进入iframe模式
const iframeSelector = "div.epub-view>iframe";
if (window.location.href.includes("https://alist-org.github.io/static/epub.js/viewer.html")) {
try {
const iframe = document.querySelector(iframeSelector);
if (iframe && iframe.contentDocument) {
const pTags = iframe.contentDocument.querySelectorAll("div>h4, p");
pTagsInIframe = Array.from(pTags); // 将 NodeList 转换为数组
const array = pTagsInIframe.map(elem => elem.textContent);
currentArrSchedule = array;
isIframeMode = true;
} else {
throw new Error("Iframe not found or content not accessible.");
}
} catch (e) {
console.error("无法访问iframe内容,可能是跨域或元素不存在。", e);
currentArrSchedule = Array.isArray(arrSchedule) ? arrSchedule : [arrSchedule];
isIframeMode = false;
}
} else {
currentArrSchedule = Array.isArray(arrSchedule) ? arrSchedule : [arrSchedule];
isIframeMode = false;
}
// 新增:事件委托逻辑
const startReadingFromClick = (event) => {
let clickedElement = event.target;
// 确保点击的是 h4 或 p 元素
if (clickedElement.tagName === "H4" || clickedElement.tagName === "P") {
const index = parseInt(clickedElement.dataset.index, 10);
if (!isNaN(index)) {
currentIndex = index;
window.speechSynthesis.cancel();
utterRecursive(currentIndex);
}
}
};
if (isIframeMode) {
const iframeDoc = document.querySelector("div.epub-view>iframe").contentDocument;
if (iframeDoc) {
iframeDoc.body.addEventListener('click', startReadingFromClick);
}
} else {
// 非iframe模式下,监听主容器
textContainer.addEventListener('click', startReadingFromClick);
}
currentIndex = 0;
// 重新渲染DOM
renderText(currentArrSchedule);
// 开始朗读
utterRecursive(currentIndex);
}
const styleCssInject = () => {
const style = document.createElement("style");
style.textContent = `
.highlight-sentence {
background-color: yellow;
transition: background-color 0.3s ease;
}
.highlight-word {
color: red;
font-weight: bold;
}`;
const iframe = document.querySelector("div.epub-view>iframe");
if ( iframe && iframe.contentDocument ) {
iframe.contentDocument.body.appendChild( style );
} else {
document.body.appendChild( style );
}
}
const processWorkflow = () => {
const jsPanel = matrixRetrieve ( "jsPanel" );
const componentS = visualizeComponentS ();
secuReFerShell ( jsPanel, componentS, "dashBoard" );
secuReFerShell ( jsPanel, textContainer, "outLet" );
const targetElem = componentS.querySelector("select#voiceSSel"); // asynchronous processing
initializeVoiceSC ( targetElem );
styleCssInject ();
}
const textContainer = document.createElement("div");
textContainer.id = "textContainer";
processWorkflow ();
const utterance = new SpeechSynthesisUtterance();
let currentIndex = 0;
let currentArrSchedule = [];
let isIframeMode = false; // 用于跟踪当前是否处于 iframe 模式
let pTagsInIframe = []; // 新增:用于存储 iframe 中的 p 元素
const synthGetVoices = () => speechSynthesis.getVoices();
let voices = [];
const voxIdx = [139, 61, 152];
let currentVoiceIndex = 0; // 新增:当前语音在 voxIdx 中的索引
window.speechSynthesis.addEventListener("voiceschanged", () => {
voices = synthGetVoices();
});
// 只添加一次监听器,避免重复
utterance.addEventListener("end", () => {
// 清除上一个句子的所有高亮
const prevSentenceElement = findElementByIndex ( currentIndex );
if (prevSentenceElement) {
prevSentenceElement.classList.remove('highlight-sentence');
// 恢复原始文本内容
prevSentenceElement.textContent = currentArrSchedule[currentIndex];
}
currentIndex++;
if (currentIndex < currentArrSchedule.length) {
utterRecursive(currentIndex);
} else {
// 朗读完毕后重置状态
currentIndex = 0;
isIframeMode = false; // 重置模式
}
});
utterance.addEventListener("boundary", ({ charIndex, charLength }) => {
const currentSentenceElement = findElementByIndex ( currentIndex );
if (currentSentenceElement) {
const text = currentArrSchedule[currentIndex];
const before = text.substring(0, charIndex);
const word = text.substring(charIndex, charIndex + charLength);
const after = text.substring(charIndex + charLength);
const fragment = document.createDocumentFragment();
if (before) {
fragment.appendChild(document.createTextNode(before));
}
const wordSpan = document.createElement('span');
wordSpan.classList.add('highlight-word');
wordSpan.textContent = word;
fragment.appendChild(wordSpan);
if (after) {
fragment.appendChild(document.createTextNode(after));
}
currentSentenceElement.innerHTML = '';
currentSentenceElement.appendChild(fragment);
}
});
// Your code here...
})();
/*
*/