您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Чтение статей, книг и больших текстов с подсветкой слов, плавной прокруткой и естественной речью Irina. Поддержите проект: https://finance.ozon.ru/apps/sbp/ozonbankpay/01997254-f6d5-7d4c-8a72-15f9a62fea9d
当前为
// ==UserScript== // @name АвтоTTS Irina с подсветкой слов v2.0 // @namespace https://gf.qytechs.cn/ru/users/You // @version 2.0 // @description Чтение статей, книг и больших текстов с подсветкой слов, плавной прокруткой и естественной речью Irina. Поддержите проект: https://finance.ozon.ru/apps/sbp/ozonbankpay/01997254-f6d5-7d4c-8a72-15f9a62fea9d // @author You // @license MIT // @match *://*/* // @grant none // ==/UserScript== (function() { 'use strict'; let rate = 2.5; let paragraphs = []; let currentIndex = 0; let isReading = false; let currentUtter = null; let selectedVoice = null; let highlightedSpan = null; // ====== Кнопка активации ====== const activateBtn = document.createElement("button"); activateBtn.textContent = "Загрузка голоса..."; Object.assign(activateBtn.style, { position: "fixed", top: "10px", left: "10px", zIndex: "2147483647", padding: "5px 10px", backgroundColor: "#007bff", color: "#fff", border: "none", borderRadius: "5px", cursor: "not-allowed", fontFamily: "Arial, sans-serif" }); document.body.appendChild(activateBtn); // ====== Панель управления ====== const indicator = document.createElement("div"); Object.assign(indicator.style, { position: "fixed", bottom: "10px", right: "10px", padding: "5px 10px", backgroundColor: "rgba(0,0,0,0.7)", color: "#fff", fontSize: "14px", borderRadius: "5px", zIndex: "2147483647", fontFamily: "Arial, sans-serif", display: "flex", alignItems: "center", gap: "5px", cursor: "move" }); const rateText = document.createElement("span"); rateText.textContent = `Скорость: ${rate.toFixed(1)}`; indicator.appendChild(rateText); const btnUp = document.createElement("button"); btnUp.textContent = "▲"; indicator.appendChild(btnUp); const btnDown = document.createElement("button"); btnDown.textContent = "▼"; indicator.appendChild(btnDown); const btnPause = document.createElement("button"); btnPause.textContent = "⏸"; indicator.appendChild(btnPause); const btnStop = document.createElement("button"); btnStop.textContent = "⏹"; indicator.appendChild(btnStop); document.body.appendChild(indicator); // ====== Перетаскивание ====== function makeDraggable(el) { let drag = false, ox, oy; el.addEventListener("mousedown", e => { drag = true; ox = e.clientX - el.offsetLeft; oy = e.clientY - el.offsetTop; }); document.addEventListener("mousemove", e => { if (drag) { el.style.left = (e.clientX - ox) + "px"; el.style.top = (e.clientY - oy) + "px"; } }); document.addEventListener("mouseup", () => drag = false); } makeDraggable(activateBtn); makeDraggable(indicator); // ====== Голос Irina ====== function waitForVoice(callback) { const check = setInterval(() => { const voices = speechSynthesis.getVoices(); selectedVoice = voices.find(v => /irina/i.test(v.name) && /ru/i.test(v.lang)) || voices.find(v => v.lang.toLowerCase().startsWith("ru")); if (selectedVoice) { clearInterval(check); callback(); } }, 100); } // ====== Фильтры ====== function isAdBlock(text) { const lower = text.toLowerCase(); return /реклама|подписк|контак|меню|ссылка|поделиться|рекомендуем|©|инн|телефон|cookie|политика/.test(lower) || text.length>500; } function findWidestBlock() { const all = Array.from(document.body.querySelectorAll("div, section")); let maxW = 0, widest = null; all.forEach(el => { const rect = el.getBoundingClientRect(); if (rect.width>maxW && el.querySelectorAll("p").length>0) { maxW = rect.width; widest = el; } }); return widest; } // ====== Инициализация параграфов ====== function initParagraphs() { const container = document.querySelector("article") || document.querySelector("main") || findWidestBlock() || document.body; paragraphs = Array.from(container.querySelectorAll("p")) .filter(p => p.textContent.trim().length>30 && !isAdBlock(p.textContent)); paragraphs.forEach(p => { const words = p.textContent.trim().split(/\s+/); p.innerHTML = words.map(w => `<span>${w}</span>`).join(" "); }); } // ====== Подсветка ====== function highlightSpan(span) { if (highlightedSpan) highlightedSpan.style.backgroundColor = ""; highlightedSpan = span; if (span) { span.style.backgroundColor = "orange"; span.scrollIntoView({behavior:"smooth", block:"center"}); } } // ====== Чтение ====== function readParagraph(index) { if (!isReading || index>=paragraphs.length) return; const p = paragraphs[index]; const utter = new SpeechSynthesisUtterance(p.textContent); utter.voice = selectedVoice; utter.rate = rate; utter.lang = "ru-RU"; let words = Array.from(p.querySelectorAll("span")); utter.onboundary = function(event) { if (event.name==="word") { const charIndex = event.charIndex; let cumulative = 0; for (let i=0; i<words.length; i++) { const len = words[i].textContent.length + 1; if (cumulative + len >= charIndex) { highlightSpan(words[i]); break; } cumulative += len; } } }; utter.onend = () => { highlightSpan(null); readParagraph(index+1); }; currentUtter = utter; speechSynthesis.speak(utter); } function startReading() { if (paragraphs.length===0) initParagraphs(); if (paragraphs.length===0) return; isReading = true; currentIndex = 0; activateBtn.textContent = "Стоп TTS"; readParagraph(currentIndex); } function stopReading() { isReading = false; speechSynthesis.cancel(); activateBtn.textContent = "Активировать TTS"; if (highlightedSpan) highlightedSpan.style.backgroundColor = ""; } // ====== Кнопки ====== waitForVoice(() => { activateBtn.textContent = "Активировать TTS"; activateBtn.style.cursor = "pointer"; activateBtn.addEventListener("click", () => isReading ? stopReading() : startReading()); }); btnUp.addEventListener("click", () => { rate += 0.1; rateText.textContent = `Скорость: ${rate.toFixed(1)}`; }); btnDown.addEventListener("click", () => { rate = Math.max(0.5, rate-0.1); rateText.textContent = `Скорость: ${rate.toFixed(1)}`; }); btnPause.addEventListener("click", () => { if (!currentUtter) return; speechSynthesis.paused ? speechSynthesis.resume() : speechSynthesis.pause(); }); btnStop.addEventListener("click", stopReading); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址