// ==UserScript== // @name 🤖ChatGPT 朗读助手 - 英语听力神器! // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description 在ChatGPT原生网页中添加朗读功能的脚本,可以让你听到ChatGPT的声音~。 // @author OpenAI - ChatGPT // @match https://chat.openai.com/* // @license GNU GPLv3 // @downloadURL none // ==/UserScript== (function () { 'use strict'; const buttonStyles = { shared: { borderColor: 'rgba(86,88,105,var(--tw-border-opacity))', fontSize: '.875rem', lineHeight: '1.25rem', padding: '0.5rem 0.75rem', borderRadius: '0.25rem', marginLeft: '0.25rem', position: 'relative', bottom: '5px', }, light: { backgroundColor: 'white', color: 'black', }, dark: { backgroundColor: 'rgba(52,53,65,var(--tw-bg-opacity))', color: 'rgba(217,217,227,var(--tw-text-opacity))', }, }; const selectorStyles = { shared: { marginLeft: '0.25rem', position: 'relative', bottom: '5px', fontSize: '.875rem', padding: '0.1rem 0.5rem', borderRadius: '0.25rem', }, light: { backgroundColor: 'white', color: 'black', }, dark: { backgroundColor: 'rgba(52,53,65,var(--tw-bg-opacity))', color: 'rgba(217,217,227,var(--tw-text-opacity))', }, }; function isDarkMode() { return document.documentElement.classList.contains('dark'); } function createButton() { const button = document.createElement('button'); button.innerHTML = '朗读'; button.title = '朗读'; button.setAttribute('data-chatgpt', ''); Object.assign(button.style, buttonStyles.shared, isDarkMode() ? buttonStyles.dark : buttonStyles.light); return button; } function createLanguageSelector() { const select = document.createElement('select'); select.setAttribute('data-chatgpt', ''); Object.assign(select.style, selectorStyles.shared, isDarkMode() ? selectorStyles.dark : selectorStyles.light); const voices = window.speechSynthesis.getVoices().filter(voice => voice.name.includes('Microsoft')); const voiceGroups = voices.reduce((groups, voice) => { const language = voice.lang.split('-')[0]; if (!groups[language]) { groups[language] = []; } groups[language].push(voice); return groups; }, {}); Object.keys(voiceGroups).forEach((language) => { const optgroup = document.createElement('optgroup'); optgroup.label = language; voiceGroups[language].forEach((voice) => { const option = document.createElement('option'); option.value = voice.name; option.text = voice.name; optgroup.appendChild(option); }); select.appendChild(optgroup); }); select.value = 'Microsoft Xiaoxiao Online (Natural) - Chinese (Mainland)'; return select; } function addButtonAndSelector() { const elements = document.querySelectorAll('.markdown.prose'); elements.forEach((elm) => { if (elm.nextElementSibling?.nodeName ==='SELECT') return; const button = createButton(); const languageSelector = createLanguageSelector(); button.addEventListener('mouseenter', () => { button.style.backgroundColor = buttonStyles.hover.backgroundColor; }); button.addEventListener('mouseleave', () => { button.style.backgroundColor = buttonStyles.normal.backgroundColor; }); button.addEventListener('click', () => { if (button.classList.contains('playing')) { window.speechSynthesis.cancel(); button.innerHTML = '朗读'; button.classList.remove('playing'); button.disabled = false; return; } button.classList.add('playing'); button.innerHTML = '生成中请稍等...'; button.disabled = true; const msg = new SpeechSynthesisUtterance(elm.textContent); msg.rate = 0.825; msg.addEventListener('boundary', (event) => { const currentWord = elm.textContent.slice(event.charIndex, event.charIndex + event.charLength); button.innerHTML = `朗读中: ${currentWord}`; button.disabled = false; }); msg.addEventListener('end', () => { button.innerHTML = '朗读'; button.classList.remove('playing'); button.disabled = false; }); msg.voice = speechSynthesis.getVoices().find(voice => voice.name === languageSelector.value); msg.onerror = (errorEvent) => { if (errorEvent.error === 'interrupted') { return; } const errorMsg = `发生错误: ${errorEvent.error}`; button.innerHTML = `发生错误: ${errorEvent.error}`; button.classList.remove('playing'); button.disabled = false; }; window.speechSynthesis.speak(msg); }); elm.parentNode.insertBefore(languageSelector, elm.nextSibling); elm.parentNode.insertBefore(button, languageSelector.nextSibling); }); } function updateButtonAndSelectorStyles() { const buttons = document.querySelectorAll('button[data-chatgpt]'); const selectors = document.querySelectorAll('select[data-chatgpt]'); buttons.forEach((button) => { Object.assign(button.style, buttonStyles.shared, isDarkMode() ? buttonStyles.dark : buttonStyles.light); }); selectors.forEach((select) => { Object.assign(select.style, selectorStyles.shared, isDarkMode() ? selectorStyles.dark : selectorStyles.light); }); } window.addEventListener('keydown', (event) => { if (event.key === 'Escape') { window.speechSynthesis.cancel(); const buttons = document.querySelectorAll('button.playing'); buttons.forEach((button) => { button.innerHTML = '朗读'; button.classList.remove('playing'); }); } }); window.speechSynthesis.onvoiceschanged = () => { addButtonAndSelector(); }; setInterval(() => { addButtonAndSelector(); }, 2000); const darkModeObserver = new MutationObserver(updateButtonAndSelectorStyles); darkModeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); })();