Greasy Fork is available in English.
Add a Download button for Google Cloud Text-to-Speech AI.
// ==UserScript== // @name Google Cloud Text-to-Speech AI Downloader // @description Add a Download button for Google Cloud Text-to-Speech AI. // @icon https://www.google.com/s2/favicons?sz=64&domain=cloud.google.com // @version 1.0 // @author afkarxyz // @namespace https://github.com/afkarxyz/misc-scripts/ // @supportURL https://github.com/afkarxyz/misc-scripts/issues // @license MIT // @match https://www.gstatic.com/cloud-site-ux/text_to_speech/text_to_speech.min.html // @grant none // ==/UserScript== (function() { 'use strict'; let lastResponse = null; let lastPayload = null; const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { this.customURL = url; if (url.includes('texttospeech.googleapis.com/v1beta1/text:synthesize')) { this.addEventListener('readystatechange', function() { if (this.readyState === 4) { try { const response = JSON.parse(this.responseText); lastResponse = response.audioContent; } catch (e) {} } }); } originalOpen.apply(this, arguments); }; const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function(data) { if (this.customURL && this.customURL.includes('texttospeech.googleapis.com/v1beta1/text:synthesize')) { try { lastPayload = typeof data === 'string' ? JSON.parse(data) : data; } catch (e) {} } originalSend.apply(this, arguments); }; const base64ToArrayBuffer = base64 => { const binary = atob(base64); const buffer = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { buffer[i] = binary.charCodeAt(i); } return buffer.buffer; }; const showCustomPopup = (message, type = 'success') => { const existingPopup = document.getElementById('custom-popup'); if (existingPopup) { existingPopup.remove(); } const popup = document.createElement('div'); popup.id = 'custom-popup'; popup.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) translateY(-100%); z-index: 9999; background-color: ${type === 'success' ? '#4CAF50' : '#f44336'}; color: white; padding: 0.938em 1.563em; border-radius: 0.313em; opacity: 0; transition: all 0.2s ease-in-out; text-align: center; max-width: 80%; `; popup.textContent = message; document.body.appendChild(popup); requestAnimationFrame(() => { popup.style.opacity = '1'; popup.style.transform = 'translate(-50%, -50%)'; }); setTimeout(() => { popup.style.opacity = '0'; popup.style.transform = 'translate(-50%, -50%) translateY(-100%)'; setTimeout(() => { popup.remove(); }, 300); }, 1000); }; const downloadAudio = () => { if (!lastResponse || !lastPayload) { showCustomPopup('Generate audio first!', 'error'); return; } const now = new Date(); const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`; const truncatedText = lastPayload.input.text.substring(0, 25) + '...'; const filename = `${timestamp}_${lastPayload.voice.name}_${truncatedText}.wav`; const blob = new Blob([base64ToArrayBuffer(lastResponse)], { type: 'audio/wav' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); URL.revokeObjectURL(link.href); showCustomPopup('Audio downloaded successfully!'); lastResponse = null; lastPayload = null; }; const createDownloadButton = () => { const button = document.createElement('paper-button'); button.id = 'downloadButton'; button.className = 'state-ready'; button.setAttribute('role', 'button'); button.tabIndex = 0; const iconSpan = document.createElement('span'); iconSpan.className = 'icon'; iconSpan.style.width = '1.125em'; iconSpan.style.height = '1.125em'; const ironIcon = document.createElement('iron-icon'); ironIcon.setAttribute('icon', 'file-download'); ironIcon.style.width = '1.125em'; ironIcon.style.height = '1.125em'; iconSpan.appendChild(ironIcon); const labelSpan = document.createElement('span'); labelSpan.className = 'label'; labelSpan.style.paddingLeft = '0.25em'; const readySpan = document.createElement('span'); readySpan.className = 'ready'; readySpan.textContent = 'Download'; labelSpan.appendChild(readySpan); const buttonInner = document.createElement('span'); buttonInner.className = 'button-inner'; buttonInner.style.padding = '0 0.625em'; buttonInner.appendChild(iconSpan); buttonInner.appendChild(labelSpan); button.appendChild(buttonInner); button.style.marginLeft = '0.625em'; button.style.background = 'var(--google-blue-500)'; button.style.color = '#fff'; button.addEventListener('click', downloadAudio); return button; }; const addDownloadButton = () => { const app = document.querySelector('ts-app'); if (!app) { return; } const shadowRoot = app.shadowRoot; if (!shadowRoot) { return; } const controlPlayback = shadowRoot.querySelector('.control-playback'); if (!controlPlayback) { return; } if (controlPlayback.querySelector('#downloadButton')) { return; } const existingButton = controlPlayback.querySelector('ts-button'); if (existingButton) { existingButton.parentNode.insertBefore(createDownloadButton(), existingButton.nextSibling); } }; const checkPageReadiness = () => { const app = document.querySelector('ts-app'); if (app && app.shadowRoot) { addDownloadButton(); } else { requestAnimationFrame(checkPageReadiness); } }; checkPageReadiness(); const observer = new MutationObserver(addDownloadButton); observer.observe(document.body, { childList: true, subtree: true }); })();