// ==UserScript== // @name Text-to-Speech Reader // @namespace http://tampermonkey.net/ // @version 1.1 // @description Read selected text using OpenAI TTS API // @author https://linux.do/u/snaily // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // Add a button to the page for reading selected text const button = document.createElement('button'); button.innerText = 'Read Aloud'; button.style.position = 'absolute'; button.style.zIndex = '1000'; button.style.display = 'none'; // Initially hidden button.style.backgroundColor = '#007BFF'; // Blue background button.style.color = '#FFFFFF'; // White text button.style.border = 'none'; button.style.borderRadius = '5px'; button.style.padding = '10px 20px'; button.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)'; button.style.cursor = 'pointer'; button.style.fontSize = '14px'; button.style.fontFamily = 'Arial, sans-serif'; document.body.appendChild(button); // Function to get selected text function getSelectedText() { let text = ''; if (window.getSelection) { text = window.getSelection().toString(); } else if (document.selection && document.selection.type != 'Control') { text = document.selection.createRange().text; } console.log('Selected Text:', text); // Debugging line return text; } // Function to call OpenAI TTS API function callOpenAITTS(text, baseUrl, apiKey, voice) { const cachedAudioUrl = getCachedAudio(text); if (cachedAudioUrl) { console.log('Using cached audio'); playAudio(cachedAudioUrl); resetButton(); return; } const url = `${baseUrl}/v1/audio/speech`; console.log('Calling OpenAI TTS API with text:', text); GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, data: JSON.stringify({ model: 'tts-1', input: text, voice: voice }), responseType: 'arraybuffer', onload: function(response) { if (response.status === 200) { console.log('API call successful'); // Debugging line const audioBlob = new Blob([response.response], { type: 'audio/mpeg' }); const audioUrl = URL.createObjectURL(audioBlob); playAudio(audioUrl); cacheAudio(text, audioUrl); } else { console.error('Error:', response.statusText); } // Reset button after request is complete resetButton(); }, onerror: function(error) { console.error('Request failed', error); // Reset button after request is complete resetButton(); } }); } // Function to play audio function playAudio(url) { const audio = new Audio(url); audio.play(); } // Function to use browser's built-in TTS function speakText(text) { const utterance = new SpeechSynthesisUtterance(text); speechSynthesis.speak(utterance); } // Function to set button to loading state function setLoadingState() { button.disabled = true; button.innerText = 'Loading...'; button.style.backgroundColor = '#6c757d'; // Grey background button.style.cursor = 'not-allowed'; } // Function to reset button to original state function resetButton() { button.disabled = false; button.innerText = 'Read Aloud'; button.style.backgroundColor = '#007BFF'; // Blue background button.style.cursor = 'pointer'; } // Helper function to get cached audio URL function getCachedAudio(text) { const cache = GM_getValue('cache', {}); const item = cache[text]; if (item) { const now = new Date().getTime(); const weekInMillis = 7 * 24 * 60 * 60 * 1000; // One day in milliseconds if (now - item.timestamp < weekInMillis) { return item.audioUrl; } else { delete cache[text]; // Remove expired cache item GM_setValue('cache', cache); } } return null; } // Helper function to cache audio URL function cacheAudio(text, audioUrl) { const cache = GM_getValue('cache', {}); cache[text] = { audioUrl: audioUrl, timestamp: new Date().getTime() }; GM_setValue('cache', cache); } // Function to clear cache function clearCache() { GM_setValue('cache', {}); alert('Cache cleared successfully.'); } // Event listener for button click button.addEventListener('click', () => { const selectedText = getSelectedText(); if (selectedText) { let apiKey = GM_getValue('apiKey', null); let baseUrl = GM_getValue('baseUrl', null); let voice = GM_getValue('voice', 'onyx'); // Default to 'onyx' if (!baseUrl) { alert('Please set the base URL for the TTS API in the Tampermonkey menu.'); return; } if (!apiKey) { alert('Please set the API key for the TTS API in the Tampermonkey menu.'); return; } setLoadingState(); // Set button to loading state if (window.location.hostname === 'github.com') { speakText(selectedText); resetButton(); // Reset button immediately for built-in TTS }else { callOpenAITTS(selectedText, baseUrl, apiKey, voice); } } else { alert('Please select some text to read aloud.'); } }); // Show the button near the selected text document.addEventListener('mouseup', (event) => { // Check if the mouseup event is triggered by the button itself if (event.target === button) { return; } const selectedText = getSelectedText(); if (selectedText) { const mouseX = event.pageX; const mouseY = event.pageY; button.style.left = `${mouseX + 10}px`; button.style.top = `${mouseY + 10}px`; button.style.display = 'block'; } else { button.style.display = 'none'; } }); // Initialize UI components function initModal() { const modalHTML = `
`; document.body.insertAdjacentHTML('beforeend', modalHTML); document.getElementById('saveConfig').addEventListener('click', saveConfig); document.getElementById('cancelConfig').addEventListener('click', closeModal); } function saveConfig() { const baseUrl = document.getElementById('baseUrl').value; const apiKey = document.getElementById('apiKey').value; const voice = document.getElementById('voice').value; GM_setValue('baseUrl', baseUrl); GM_setValue('apiKey', apiKey); GM_setValue('voice', voice); alert('Settings saved successfully.'); closeModal(); } function closeModal() { document.getElementById('configModal').style.display = 'none'; } function openModal() { if (!document.getElementById('configModal')) { initModal(); } document.getElementById('configModal').style.display = 'flex'; } GM_registerMenuCommand('Configure TTS Settings', openModal); // Register menu command to clear cache GM_registerMenuCommand('Clear TTS Cache', clearCache); })();