您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
采用更稳定的轮询检测机制注入按钮,支持播放、暂停和续播,并使用SVG图标以保证显示效果。
// ==UserScript== // @name 微信公众号文章朗读助手 // @namespace http://tampermonkey.net/ // @version 2.2 // @description 采用更稳定的轮询检测机制注入按钮,支持播放、暂停和续播,并使用SVG图标以保证显示效果。 // @author xiangmingya // @match https://mp.weixin.qq.com/* // @grant GM_addStyle // @license MPL-2.0 License // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzQyYjM2YSIgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjRweCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0zIDl2Nmg0bDUgNVY0TDcgOWg0em0xMy41IDNjMC0xLjc3LTEuMDItMy4yOS0yLjUtNC4wM3Y4LjA1YzEuNDgtLjczIDIuNS0yLjI1IDIuNS00LjAyek0xNCAzLjk4djIuMDZjMi44OS44NiA1IDEuNzEgNSA1Ljk2czEuMjggMy40NSA0IDQuM3YtMi4xYzAtMy44Ny0yLjQzLTYuNjYtNS03Ljl6Ii8+PC9zdmc+ // ==/UserScript== (function() { 'use strict'; console.log('[朗读助手 v2.1] 脚本启动'); // --- 配置项 --- const TARGET_VOICE_NAME = "Microsoft YunJhe Online (Natural) - Chinese (Taiwan)"; // 使用SVG图标以获得最佳兼容性 const PLAY_ICON_SVG = `<svg viewBox="0 0 1024 1024" width="14" height="14" style="vertical-align: middle; fill: currentColor;"><path d="M192 128l640 384-640 384z"></path></svg>`; const PAUSE_ICON_SVG = `<svg viewBox="0 0 1024 1024" width="14" height="14" style="vertical-align: middle; fill: currentColor;"><path d="M320 128h128v768H320zM576 128h128v768H576z"></path></svg>`; const BUTTON_TEXT_PLAY = "朗读"; const BUTTON_TEXT_PAUSE = "暂停"; const BUTTON_TEXT_RESUME = "继续"; let speechState = 'idle'; // 'idle', 'playing', 'paused' let speechAPI = window.speechSynthesis; let targetVoice = null; let speechUtteranceChunks = []; let currentChunkIndex = 0; /** * 使用轮询来等待目标元素出现 */ function waitForElement(selector, callback) { let interval = setInterval(() => { const element = document.querySelector(selector); if (element) { console.log(`[朗读助手 v2.1] 成功找到目标元素: ${selector}`); clearInterval(interval); callback(element); } }, 250); // 每250毫秒检查一次 // 10秒后如果还没找到,就超时放弃 setTimeout(() => clearInterval(interval), 10000); } /** * 主初始化函数 */ function initializeReader(targetContainer) { if (document.getElementById('custom-read-aloud-button')) { console.log('[朗读助手 v2.1] 按钮已存在,跳过初始化。'); return; } console.log('[朗读助手 v2.1] 开始初始化朗读模块...'); if (!('speechSynthesis' in window)) { console.warn("[朗读助手 v2.1] 浏览器不支持 Web Speech API。"); return; } populateVoiceList(); if (speechAPI.onvoiceschanged !== undefined) { speechAPI.onvoiceschanged = populateVoiceList; } createReadAloudButton(targetContainer); } function populateVoiceList() { const voices = speechAPI.getVoices(); if (voices.length === 0) return; targetVoice = voices.find(voice => voice.name === TARGET_VOICE_NAME) || voices.find(voice => /zh|chinese/i.test(voice.lang)); if (targetVoice) { console.log(`[朗读助手 v2.1] 已选择语音: ${targetVoice.name}`); } } function createReadAloudButton(container) { const readButton = document.createElement('span'); readButton.id = 'custom-read-aloud-button'; GM_addStyle(` #custom-read-aloud-button { display: inline-flex; align-items: center; gap: 4px; margin-left: 16px; padding: 2px 8px; border-radius: 12px; background-color: #f0f0f0; color: #555; cursor: pointer; font-size: 14px; transition: all 0.2s ease-in-out; user-select: none; } #custom-read-aloud-button:hover { background-color: #e0e0e0; transform: scale(1.05); } #custom-read-aloud-button.speaking { background-color: #d4edda; color: #155724; } #custom-read-aloud-button.paused { background-color: #fff3cd; color: #856404; } `); readButton.addEventListener('click', mainControl); container.insertAdjacentElement('afterend', readButton); console.log('[朗读助手 v2.1] 按钮DOM已注入。'); updateButtonState(speechState); // 在按钮注入后立即更新其内容 } function mainControl() { switch (speechState) { case 'idle': startReading(); break; case 'playing': pauseReading(); break; case 'paused': resumeReading(); break; } } function startReading() { const contentElement = document.getElementById('js_content'); if (!contentElement || !contentElement.innerText.trim()) { alert("文章内容为空或无法找到。"); return; } speechAPI.cancel(); const textToRead = contentElement.innerText; const chunks = textToRead.match(/[^.!?\n,。!?\s]+[.!?\n,。!?\s]?/g) || [text]; speechUtteranceChunks = chunks.map(chunk => { const utterance = new SpeechSynthesisUtterance(chunk.trim()); utterance.voice = targetVoice; utterance.lang = targetVoice ? targetVoice.lang : 'zh-CN'; utterance.rate = 1.0; return utterance; }); currentChunkIndex = 0; speechState = 'playing'; updateButtonState(speechState); playNextChunk(); } function pauseReading() { speechAPI.pause(); speechState = 'paused'; updateButtonState(speechState); } function resumeReading() { speechAPI.resume(); speechState = 'playing'; updateButtonState(speechState); } function resetReading() { speechAPI.cancel(); speechState = 'idle'; updateButtonState(speechState); } function playNextChunk() { if (currentChunkIndex >= speechUtteranceChunks.length) { resetReading(); return; } const utterance = speechUtteranceChunks[currentChunkIndex]; utterance.onend = () => { if (speechState === 'playing') { currentChunkIndex++; playNextChunk(); } }; utterance.onerror = (event) => { console.error('[朗读助手 v2.1] 朗读错误:', event.error); resetReading(); }; speechAPI.speak(utterance); } function updateButtonState(state) { const button = document.getElementById('custom-read-aloud-button'); if (!button) { console.warn('[朗读助手 v2.1] 更新状态时未找到按钮。'); return; } console.log(`[朗读助手 v2.1] 更新按钮状态为: ${state}`); button.classList.remove('speaking', 'paused'); switch (state) { case 'playing': button.innerHTML = `${PAUSE_ICON_SVG} <span class="button-text">${BUTTON_TEXT_PAUSE}</span>`; button.classList.add('speaking'); break; case 'paused': button.innerHTML = `${PLAY_ICON_SVG} <span class="button-text">${BUTTON_TEXT_RESUME}</span>`; button.classList.add('paused'); break; case 'idle': default: button.innerHTML = `${PLAY_ICON_SVG} <span class="button-text">${BUTTON_TEXT_PLAY}</span>`; break; } } // --- 脚本执行入口 --- // 等待 #meta_content_hide_info 元素出现后,再执行初始化 waitForElement('#meta_content_hide_info', initializeReader); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址