您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Toca um som quando a geração de resposta termina, adiciona "Continue" na caixa de texto e clica em "Not now" quando detectado
// ==UserScript== // @name Outlier Playground Sound Notification // @namespace http://tampermonkey.net/ // @version 5.0 // @description Toca um som quando a geração de resposta termina, adiciona "Continue" na caixa de texto e clica em "Not now" quando detectado // @author luascfl (revisado por Gemini e Claude) // @match https://app.outlier.ai/playground* // @icon https://www.google.com/s2/favicons?sz=64&domain=outlier.ai // @license MIT // @homepageURL https://github.com/luascfl/outlier-playground-sound-notification // @supportURL https://github.com/luascfl/outlier-playground-sound-notification/issues // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // --- CONFIGURAÇÃO --- const SOUND_URL = "https://od.lk/s/MjJfMzM5NTM3ODNf/331673__nicola_ariutti__brass_bell_01_take10.wav"; const POLLING_INTERVAL_MS = 200; // Intervalo de verificação em milissegundos. 200ms é um bom equilíbrio. const AUTO_CONTINUE_TEXT = "Continue"; // Texto a ser adicionado automaticamente const NOT_NOW_CHECK_INTERVAL_MS = 500; // Intervalo para verificar se o botão está clicável const NOT_NOW_MAX_WAIT_MS = 30000; // Tempo máximo de espera (30 segundos) // --- INICIALIZAÇÃO --- const audio = new Audio(SOUND_URL); let lastState = null; let currentNotNowMonitor = null; // Armazena o ID do intervalo atual do monitor console.log("🚀 Iniciando Outlier Playground Sound Notification v5.0..."); /** * Tenta tocar o som de notificação. * Inclui tratamento de erro para casos em que o navegador bloqueia o autoplay. */ function playSound() { console.log("🔔 Tocando som de notificação..."); audio.play().catch(e => console.error("Erro ao tocar o som. O navegador pode ter bloqueado a reprodução automática. Interaja com a página (clique em algo) e tente novamente.", e)); } /** * Adiciona texto "Continue" na caixa de texto do prompt simulando interação real do usuário */ function addContinueText() { const textArea = document.querySelector('textarea.ChatInput_textarea__QUOCH'); if (textArea) { // Limpa e foca textArea.focus(); textArea.select(); document.execCommand('delete'); // Método 1: Usa execCommand para inserir texto (funciona em muitos casos onde outros métodos falham) document.execCommand('insertText', false, AUTO_CONTINUE_TEXT); // Método 2: Simula eventos de teclado para cada caractere setTimeout(() => { if (textArea.value !== AUTO_CONTINUE_TEXT) { textArea.value = ''; textArea.focus(); // Simula pressionamento de tecla para cada caractere for (let i = 0; i < AUTO_CONTINUE_TEXT.length; i++) { const char = AUTO_CONTINUE_TEXT[i]; // KeyDown const keydownEvent = new KeyboardEvent('keydown', { key: char, code: 'Key' + char.toUpperCase(), keyCode: char.charCodeAt(0), which: char.charCodeAt(0), bubbles: true, cancelable: true }); textArea.dispatchEvent(keydownEvent); // KeyPress (deprecated mas alguns sites ainda usam) const keypressEvent = new KeyboardEvent('keypress', { key: char, code: 'Key' + char.toUpperCase(), keyCode: char.charCodeAt(0), which: char.charCodeAt(0), bubbles: true, cancelable: true }); textArea.dispatchEvent(keypressEvent); // Adiciona o caractere textArea.value += char; // Input event const inputEvent = new InputEvent('input', { data: char, inputType: 'insertText', bubbles: true, cancelable: true }); textArea.dispatchEvent(inputEvent); // KeyUp const keyupEvent = new KeyboardEvent('keyup', { key: char, code: 'Key' + char.toUpperCase(), keyCode: char.charCodeAt(0), which: char.charCodeAt(0), bubbles: true, cancelable: true }); textArea.dispatchEvent(keyupEvent); } // Dispara evento de change final textArea.dispatchEvent(new Event('change', { bubbles: true })); } }, 100); // Método 3: Tenta forçar uma atualização do React se os métodos anteriores falharem setTimeout(() => { if (textArea.value === AUTO_CONTINUE_TEXT) { console.log("✍️ Texto 'Continue' adicionado com sucesso!"); // Simula um espaço e backspace para forçar reconhecimento const spaceDown = new KeyboardEvent('keydown', { key: ' ', code: 'Space', keyCode: 32, which: 32, bubbles: true }); const spaceUp = new KeyboardEvent('keyup', { key: ' ', code: 'Space', keyCode: 32, which: 32, bubbles: true }); const backspaceDown = new KeyboardEvent('keydown', { key: 'Backspace', code: 'Backspace', keyCode: 8, which: 8, bubbles: true }); const backspaceUp = new KeyboardEvent('keyup', { key: 'Backspace', code: 'Backspace', keyCode: 8, which: 8, bubbles: true }); // Adiciona e remove um espaço textArea.dispatchEvent(spaceDown); textArea.value += ' '; textArea.dispatchEvent(new InputEvent('input', { data: ' ', inputType: 'insertText', bubbles: true })); textArea.dispatchEvent(spaceUp); setTimeout(() => { textArea.dispatchEvent(backspaceDown); textArea.value = textArea.value.slice(0, -1); textArea.dispatchEvent(new InputEvent('input', { inputType: 'deleteContentBackward', bubbles: true })); textArea.dispatchEvent(backspaceUp); }, 50); } }, 500); } else { console.warn("⚠️ Caixa de texto não encontrada"); } } /** * Verifica se um elemento está visível e interativo */ function isElementClickable(element) { if (!element) return false; // Verifica se o elemento está no DOM if (!document.body.contains(element)) return false; // Verifica se o elemento está visível const style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false; // Verifica se o elemento não está desabilitado if (element.disabled) return false; // Verifica se o elemento tem dimensões const rect = element.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) return false; // Verifica se não há overlay sobre o elemento const elementAtPoint = document.elementFromPoint(rect.left + rect.width/2, rect.top + rect.height/2); if (!elementAtPoint || (!element.contains(elementAtPoint) && !elementAtPoint.contains(element))) return false; return true; } /** * Encontra o botão "Not now" no DOM */ function findNotNowButton() { // Primeiro tenta encontrar pelo container específico const container = document.querySelector('div.rt-Flex.rt-r-fd-column.rt-r-ai-center'); if (container) { const buttons = container.querySelectorAll('button[data-accent-color="gray"].rt-Button'); for (const button of buttons) { if (button.textContent && button.textContent.trim() === 'Not now') { return button; } } } // Se não encontrar, tenta a busca geral const allButtons = document.querySelectorAll('button[data-accent-color="gray"].rt-Button'); for (const button of allButtons) { if (button.textContent && button.textContent.trim() === 'Not now') { return button; } } return null; } /** * Monitora e clica no botão "Not now" quando ele estiver disponível */ function monitorAndClickNotNow() { // Cancela o monitor anterior se existir if (currentNotNowMonitor) { clearInterval(currentNotNowMonitor); console.log("🔄 Cancelando monitor anterior do botão 'Not now'"); } console.log("🔍 Iniciando novo monitoramento do botão 'Not now'..."); let elapsedTime = 0; let buttonFoundOnce = false; currentNotNowMonitor = setInterval(() => { const notNowButton = findNotNowButton(); if (notNowButton) { if (!buttonFoundOnce) { buttonFoundOnce = true; console.log("👀 Botão 'Not now' detectado no DOM"); } if (isElementClickable(notNowButton)) { console.log("✅ Botão 'Not now' está clicável!"); notNowButton.click(); console.log("🖱️ Botão 'Not now' clicado automaticamente"); clearInterval(currentNotNowMonitor); currentNotNowMonitor = null; } else { console.log(`⌛ Botão 'Not now' ainda não está clicável, aguardando... (${elapsedTime/1000}s)`); } } else if (buttonFoundOnce) { // Se o botão foi encontrado antes mas agora desapareceu, para o monitor console.log("❌ Botão 'Not now' desapareceu do DOM"); clearInterval(currentNotNowMonitor); currentNotNowMonitor = null; } elapsedTime += NOT_NOW_CHECK_INTERVAL_MS; // Timeout após o tempo máximo de espera if (elapsedTime >= NOT_NOW_MAX_WAIT_MS) { console.log("⏱️ Tempo limite excedido. Parando monitoramento do botão 'Not now'"); clearInterval(currentNotNowMonitor); currentNotNowMonitor = null; } }, NOT_NOW_CHECK_INTERVAL_MS); } /** * Verifica qual tipo de botão está visível na interface. * @returns {'stop' | 'send-enabled' | 'send-disabled' | 'none'} O estado atual do botão. */ function getButtonType() { // Procura pelo botão de parar (stop) const stopButton = document.querySelector('button:has(svg[data-icon="stop"])'); if (stopButton) { return 'stop'; } // Procura pelo botão de enviar (paper-plane) const sendButton = document.querySelector('button:has(svg[data-icon="paper-plane-top"])'); if (sendButton) { // Verifica se o botão de enviar está desabilitado return sendButton.disabled ? 'send-disabled' : 'send-enabled'; } return 'none'; } /** * Função principal que monitora a mudança de estado e decide quando tocar o som. */ function monitorStateChange() { const currentState = getButtonType(); // Se o estado não foi inicializado ainda, apenas define o estado inicial. if (lastState === null) { lastState = currentState; return; } // Se o estado mudou, processa a lógica. if (currentState !== lastState) { console.log(`Mudança de estado detectada: de '${lastState}' para '${currentState}'`); // SEMPRE procura o botão "Not now" quando há qualquer mudança de estado console.log("🔍 Mudança de estado detectada, verificando presença do botão 'Not now'..."); monitorAndClickNotNow(); // CONDIÇÃO ORIGINAL: // Se o estado anterior era 'stop' e o novo estado é qualquer tipo de 'send', // significa que a geração da resposta acabou de terminar. if (lastState === 'stop' && currentState.startsWith('send')) { console.log("✅ Resposta completa!"); playSound(); // Adiciona um delay maior para garantir que a interface esteja pronta setTimeout(() => { addContinueText(); }, 300); } // Atualiza o último estado conhecido. lastState = currentState; } } // Aguarda um momento para garantir que a interface do site foi carregada // antes de iniciar o monitoramento. setTimeout(() => { // Inicializa o estado pela primeira vez. lastState = getButtonType(); console.log(`Estado inicial do botão: '${lastState}'`); // Inicia o monitoramento contínuo (polling). setInterval(monitorStateChange, POLLING_INTERVAL_MS); console.log("✅ Script iniciado com sucesso! Monitorando o botão de resposta."); console.log("ℹ️ O som tocará e 'Continue' será adicionado quando a resposta do modelo terminar de ser gerada."); console.log("ℹ️ O botão 'Not now' será verificado e clicado automaticamente a cada mudança de estado."); }, 1500); // --- FUNÇÕES DE DEBUG (Opcional) --- function debugElements() { console.log("=== INFORMAÇÕES DE DEBUG ==="); const sendButton = document.querySelector('button:has(svg[data-icon="paper-plane-top"])'); const stopButton = document.querySelector('button:has(svg[data-icon="stop"])'); const textArea = document.querySelector('textarea.ChatInput_textarea__QUOCH'); const notNowButton = findNotNowButton(); const container = document.querySelector('div.rt-Flex.rt-r-fd-column.rt-r-ai-center'); console.log("Botão de Enviar (Send) encontrado:", sendButton); if (sendButton) { console.log("HTML do Botão de Enviar:", sendButton.outerHTML); console.log("Botão Send está desabilitado?", sendButton.disabled); } console.log("Botão de Parar (Stop) encontrado:", stopButton); if (stopButton) console.log("HTML do Botão de Parar:", stopButton.outerHTML); console.log("Caixa de texto encontrada:", textArea); if (textArea) { console.log("HTML da Caixa de texto:", textArea.outerHTML); console.log("Valor atual da textarea:", textArea.value); } console.log("Botão 'Not now' encontrado:", notNowButton); if (notNowButton) { console.log("HTML do Botão 'Not now':", notNowButton.outerHTML); console.log("Botão 'Not now' clicável?", isElementClickable(notNowButton)); } console.log("Container dos botões encontrado:", container); if (container) { console.log("HTML do container:", container.outerHTML); } console.log("Estado atual (getButtonType):", getButtonType()); console.log("Último estado registrado (lastState):", lastState); console.log("Monitor 'Not now' ativo?", currentNotNowMonitor !== null); console.log("=== FIM DO DEBUG ==="); } // Adiciona função para forçar clique no Not now window.forceNotNowClick = function() { const button = findNotNowButton(); if (button) { console.log("Forçando clique no botão 'Not now'"); button.click(); return true; } else { console.log("Botão 'Not now' não encontrado"); return false; } }; // Adiciona função para testar clicabilidade no console window.testClickable = function(selector) { const element = document.querySelector(selector); if (element) { console.log("Elemento encontrado:", element); console.log("É clicável?", isElementClickable(element)); return isElementClickable(element); } else { console.log("Elemento não encontrado com o seletor:", selector); return false; } }; // Adiciona o comando de debug à janela para que possa ser chamado pelo console. window.debugOutlierScript = debugElements; console.log("💡 Dica: Digite 'debugOutlierScript()' no console para verificar o estado dos elementos."); console.log("💡 Dica: Digite 'testClickable(selector)' no console para testar se um elemento é clicável."); console.log("💡 Dica: Digite 'forceNotNowClick()' no console para forçar o clique no botão 'Not now'."); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址