您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Divide un testo lungo in chunk, intercetta il pulsante di download su ttsfree.com e riproduce gli audio in sequenza con un lettore personalizzato
// ==UserScript== // @name TTSFree Long Text Reader // @namespace http://tampermonkey.net/ // @version 1.9 // @description Divide un testo lungo in chunk, intercetta il pulsante di download su ttsfree.com e riproduce gli audio in sequenza con un lettore personalizzato // @author Flejta & Grok (con aiuto di xAI) // @match https://ttsfree.com/* // @grant none // @license mit // ==/UserScript== (function() { 'use strict'; // Aggiunge l’interfaccia personalizzata function addCustomControls() { const container = document.createElement('div'); container.id = 'custom-tts-controls'; container.style = 'position: fixed; top: 10px; right: 10px; background: white; padding: 15px; border: 2px solid #007bff; border-radius: 8px; z-index: 1000; box-shadow: 0 2px 5px rgba(0,0,0,0.2); width: 400px;'; container.innerHTML = ` <h3 style="margin: 0 0 10px; font-size: 16px; color: #333;">TTS Long Text Reader</h3> <textarea id="custom-text" placeholder="Inserisci il testo lungo" style="width: 100%; height: 150px; border: 1px solid #ccc; border-radius: 5px; padding: 5px; font-size: 14px; box-sizing: border-box;"></textarea> <div style="margin-top: 10px;"> <label style="font-size: 14px;">Limite caratteri: </label> <select id="chunk-size" style="margin-left: 5px; padding: 2px; width: 150px;"> <option value="500">500 (senza login)</option> <option value="2000" selected>2000 (con login)</option> </select> </div> <button id="custom-start" style="margin-top: 10px; padding: 8px 15px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;">Avvia lettura</button> <div id="status" style="margin-top: 10px; font-size: 14px; color: #333;">Stato: In attesa</div> `; document.body.appendChild(container); } // Divide il testo in chunk function splitText(text, maxLength) { console.log(`Divisione testo: lunghezza=${text.length}, maxLength=${maxLength}`); const chunks = []; let start = 0; while (start < text.length) { let end = Math.min(start + maxLength, text.length); if (end < text.length) { const lastPeriod = text.lastIndexOf('.', end); const lastSpace = text.lastIndexOf(' ', end); end = Math.max(lastPeriod, lastSpace) > start ? Math.max(lastPeriod, lastSpace) + 1 : end; } chunks.push(text.slice(start, end)); start = end; } console.log(`Creati ${chunks.length} chunk`); return chunks; } // Simula un'azione di incolla usando document.execCommand function simulatePaste(textarea, text) { console.log('Inizio simulazione incolla'); // Imposta il focus sulla textarea textarea.focus(); console.log('Focus impostato sulla textarea'); // Seleziona tutto il testo esistente (per sovrascriverlo) textarea.select(); // Inserisce il testo usando document.execCommand try { const success = document.execCommand('insertText', false, text); if (success) { console.log('Testo inserito con document.execCommand:', text.substring(0, 50) + '...'); // Invia eventi input e paste per aggiornare il framework const inputEvent = new Event('input', { bubbles: true }); const pasteEvent = new Event('paste', { bubbles: true }); textarea.dispatchEvent(inputEvent); textarea.dispatchEvent(pasteEvent); console.log('Eventi input e paste inviati'); } else { console.error('Errore: document.execCommand non ha funzionato'); throw new Error('document.execCommand non ha funzionato'); } } catch (error) { console.error('Errore durante l\'inserimento del testo:', error); throw error; } } // Simula l’inserimento del testo e il click su "Convert Now" function insertTextAndConvert(text) { return new Promise((resolve, reject) => { console.log('Tentativo di inserimento testo:', text.substring(0, 50) + '...'); const textarea = document.querySelector('#input_text'); if (!textarea) { console.error('Textarea non trovata'); reject(new Error('Textarea non trovata')); return; } console.log('Textarea trovata'); try { simulatePaste(textarea, text); // Verifica se il testo è stato inserito setTimeout(() => { if (textarea.value !== text) { console.error('Errore: il testo non è stato inserito correttamente. Valore attuale:', textarea.value); reject(new Error('Testo non inserito nella textarea')); return; } console.log('Testo inserito correttamente:', textarea.value.substring(0, 50) + '...'); const convertButton = document.querySelector('.convert-now'); if (!convertButton) { console.error('Pulsante "Convert Now" non trovato'); reject(new Error('Pulsante "Convert Now" non trovato')); return; } console.log('Pulsante "Convert Now" trovato, simulazione click'); convertButton.click(); resolve(); }, 100); // Breve ritardo per consentire al framework di aggiornare } catch (error) { reject(error); } }); } // Monitora l’aggiunta o la modifica del pulsante di download function waitForDownloadButton(chunkIndex) { return new Promise((resolve, reject) => { console.log(`Inizio monitoraggio pulsante di download per chunk ${chunkIndex}`); const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { // Controlla i nodi aggiunti mutation.addedNodes.forEach((node) => { if (node.id === 'savevoice' || (node.querySelector && node.querySelector('#savevoice'))) { const downloadButton = node.id === 'savevoice' ? node : node.querySelector('#savevoice'); if (downloadButton && downloadButton.href) { console.log(`Pulsante di download trovato per chunk ${chunkIndex}, URL: ${downloadButton.href}`); observer.disconnect(); resolve(downloadButton.href); } } }); // Controlla le modifiche agli attributi di savevoice if (mutation.type === 'attributes' && mutation.target.id === 'savevoice' && mutation.target.href) { console.log(`Modifica attributo href trovata per chunk ${chunkIndex}, URL: ${mutation.target.href}`); observer.disconnect(); resolve(mutation.target.href); } }); }); // Monitora sia l'aggiunta di nodi che le modifiche agli attributi observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['href'] }); // Timeout per evitare attese infinite setTimeout(() => { console.error(`Timeout attesa pulsante di download per chunk ${chunkIndex}`); observer.disconnect(); reject(new Error('Timeout attesa pulsante di download')); }, 30000); // 30 secondi }); } // Gestore della coda audio class AudioPlayer { constructor() { this.audio = new Audio(); this.queue = []; this.isPlaying = false; } addToQueue(audioUrl, chunkIndex) { console.log(`Aggiunto audio alla coda per chunk ${chunkIndex}: ${audioUrl}`); this.queue.push({ url: audioUrl, index: chunkIndex }); console.log(`Stato coda: ${JSON.stringify(this.queue.map(item => ({ index: item.index, url: item.url })))}`); if (!this.isPlaying) { this.playNext(); } } playNext() { if (this.queue.length === 0) { console.log('Coda audio vuota, riproduzione terminata'); this.isPlaying = false; return; } this.isPlaying = true; const { url, index } = this.queue.shift(); console.log(`Riproduzione audio per chunk ${index}: ${url}`); this.audio.src = url; this.audio.play().catch((error) => { console.error(`Errore riproduzione per chunk ${index}:`, error); }); this.audio.onended = () => { console.log(`Audio terminato per chunk ${index}, passaggio al successivo`); this.playNext(); }; } } // Processo principale async function processLongText(maxLength) { const customText = document.querySelector('#custom-text').value; const status = document.querySelector('#status'); const player = new AudioPlayer(); const chunks = splitText(customText, maxLength); if (chunks.length === 0 || !customText) { console.error('Testo non valido o vuoto'); status.textContent = 'Stato: Inserisci un testo valido'; return; } console.log(`Inizio elaborazione, ${chunks.length} chunk`); status.textContent = `Stato: Elaborazione 1/${chunks.length}`; for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i]; try { console.log(`Elaborazione chunk ${i + 1}/${chunks.length}`); await insertTextAndConvert(chunk); const audioUrl = await waitForDownloadButton(i + 1); player.addToQueue(audioUrl, i + 1); status.textContent = `Stato: Elaborazione ${i + 2}/${chunks.length}`; await new Promise(resolve => setTimeout(resolve, 20000)); // Ritardo di 20 secondi } catch (error) { console.error(`Errore con il blocco ${i + 1}:`, error); status.textContent = `Stato: Errore al blocco ${i + 1} - ${error.message}`; break; } } console.log('Elaborazione completata'); status.textContent = `Stato: Completato`; } // Inizializza addCustomControls(); document.querySelector('#custom-start').addEventListener('click', () => { console.log('Avvio processo'); const chunkSize = parseInt(document.querySelector('#chunk-size').value, 10); processLongText(chunkSize); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址