您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Автоматический переход с настройками звука, голосовым управлением, улучшенной автогромкостью, изменением голоса и выбором тем для nekto.me audiochat
// ==UserScript== // @name AutoNektome // @namespace http://tampermonkey.net/ // @version 4.1.1 // @description Автоматический переход с настройками звука, голосовым управлением, улучшенной автогромкостью, изменением голоса и выбором тем для nekto.me audiochat // @author @paracosm17 // @match https://nekto.me/audiochat // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // ### Настройка звуков уведомлений const START_CONVERSATION_SOUND_URL = 'https://zvukogram.com/mp3/p2/2862/skayp-zvuk-soobschenie-poluchil-message-received-23007.mp3'; // Ссылка на звук начала разговора const END_CONVERSATION_SOUND_URL = 'https://zvukogram.com//mp3/cats/791/enderman_teleport.mp3'; // Ссылка на звук окончания разговора const START_SOUND_VOLUME = 0.4; // Громкость звука начала разговора (0.0 - 1.0) const END_SOUND_VOLUME = 0.3; // Громкость звука окончания разговора (0.0 - 1.0) // ### Настройка голосовых команд const VOICE_COMMANDS = { skip: ['скип', 'skip', 'скиф', 'скипнуть', 'кефир'], stop: ['завершить', 'остановить', 'закончить', 'кумыс'], start: ['чат'] }; // ### Настройки автогромкости собеседника const TARGET_VOLUME = 50; // Целевая громкость звука в процентах (0-100), к которой стремится автогромкость const MIN_VOLUME = 10; // Минимально допустимая громкость в процентах (0-100), ниже которой автогромкость не опустит звук const MAX_VOLUME = 90; // Максимально допустимая громкость в процентах (0-100), выше которой автогромкость не поднимет звук const TRANSITION_DURATION = 1000; // Длительность плавного перехода громкости в миллисекундах (1000 мс = 1 секунда) const VOLUME_CHECK_INTERVAL = 200; // Интервал проверки громкости в миллисекундах (как часто анализируется уровень звука) const HOLD_DURATION = 5000; // Время удержания текущей громкости в миллисекундах после громкого звука (5000 мс = 5 секунд) const SILENCE_THRESHOLD = 5; // Порог тишины в процентах (0-100), ниже которого звук считается слишком тихим const HISTORY_SIZE = 15; // Размер истории измерений громкости (количество последних значений для усреднения) // ### Темы const THEMES = { 'Original': null, 'Dracula': 'https://raw.githubusercontent.com/paracosm17/AutoNektome/refs/heads/main/dracula.css', 'GitHub Dark': 'https://raw.githubusercontent.com/paracosm17/AutoNektome/refs/heads/main/githubdark.css', 'One Dark': 'https://raw.githubusercontent.com/paracosm17/AutoNektome/refs/heads/main/onedark.css', 'Monokai': 'https://raw.githubusercontent.com/paracosm17/AutoNektome/refs/heads/main/monokai.css', 'Nord': 'https://raw.githubusercontent.com/paracosm17/AutoNektome/refs/heads/main/nord.css' }; // ### Настройки из localStorage const settings = { enableLoopback: loadSetting('enableLoopback', false), autoGainControl: loadSetting('autoGainControl', false), noiseSuppression: loadSetting('noiseSuppression', true), echoCancellation: loadSetting('echoCancellation', false), gainValue: loadSetting('gainValue', 1.5, parseFloat), voiceControl: loadSetting('voiceControl', false), autoVolume: loadSetting('autoVolume', true), voicePitch: loadSetting('voicePitch', false), pitchLevel: loadSetting('pitchLevel', 0, parseFloat), conversationCount: loadSetting('conversationCount', 0, parseInt), conversationStats: loadSetting('conversationStats', { over5min: 0, over15min: 0, over30min: 0, over1hour: 0, over2hours: 0, over3hours: 0, over5hours: 0 }), selectedTheme: loadSetting('selectedTheme', 'Original') }; // ### Переменные состояния let isAutoModeEnabled = true; let isVoiceControlEnabled = settings.voiceControl; let observer = null; let globalStream = null; let audioContext = null; let gainNode = null; let micStream = null; let recognition = null; let voiceHintElement = null; let remoteAudioContext = null; let volumeAnalyser = null; let volumeCheckIntervalId = null; let lastLoudTime = 0; let volumeHistory = []; let lastAdjustedVolume = TARGET_VOLUME; let pitchNode = null; let pitchAudioContext = null; let pitchSource = null; let pitchWorkletNode = null; let conversationTimer = null; let currentConversationStart = null; let isConversationActive = false; let isMicMuted = false; let isHeadphonesMuted = false; let currentThemeLink = null; // ### Утилиты const endConversationAudio = new Audio(END_CONVERSATION_SOUND_URL); endConversationAudio.volume = END_SOUND_VOLUME; const startConversationAudio = new Audio(START_CONVERSATION_SOUND_URL); startConversationAudio.volume = START_SOUND_VOLUME; // Блокировка звука connect.mp3 через MutationObserver const blockConnectSound = () => { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes.length) { const audioElements = document.querySelectorAll('audio'); audioElements.forEach(audio => { if (audio.src.includes('connect.mp3') && !audio.dataset.custom) { audio.src = ''; audio.muted = true; audio.pause(); audio.removeAttribute('preload'); audio.setAttribute('data-blocked', 'true'); console.log('Звук connect.mp3 заблокирован'); } }); } }); }); observer.observe(document.body, { childList: true, subtree: true }); }; // Запускаем блокировку сразу blockConnectSound(); const originalPlay = HTMLAudioElement.prototype.play; HTMLAudioElement.prototype.play = function() { if (this.src.includes('connect.mp3') && !this.dataset.custom) { console.log('Попытка воспроизведения connect.mp3 заблокирована'); return Promise.resolve(); } return originalPlay.apply(this, arguments); }; function loadSetting(key, defaultValue, transform = JSON.parse) { const value = localStorage.getItem(key); return value !== null ? transform(value) : defaultValue; } function saveSetting(key, value) { localStorage.setItem(key, JSON.stringify(value)); } // ### Управление темами function applyTheme(themeName) { if (currentThemeLink) { currentThemeLink.remove(); currentThemeLink = null; } const loadingIndicator = document.querySelector('#settings-container select + span + span'); if (loadingIndicator) loadingIndicator.style.display = 'block'; if (themeName !== 'Original' && THEMES[themeName]) { const styleElement = document.createElement('style'); styleElement.id = 'custom-theme-style'; fetch(THEMES[themeName]) .then(response => { if (!response.ok) throw new Error('Ошибка загрузки CSS'); return response.text(); }) .then(css => { styleElement.textContent = css; document.head.appendChild(styleElement); currentThemeLink = styleElement; if (loadingIndicator) loadingIndicator.style.display = 'none'; }) .catch(error => { console.error('Ошибка при загрузке темы:', error); if (loadingIndicator) loadingIndicator.style.display = 'none'; }); } else if (themeName === 'Original') { const existingStyles = document.querySelectorAll('style[id="custom-theme-style"]'); existingStyles.forEach(style => style.remove()); currentThemeLink = null; if (loadingIndicator) loadingIndicator.style.display = 'none'; } settings.selectedTheme = themeName; saveSetting('selectedTheme', themeName); } function createThemeSelector() { const themeContainer = document.createElement('div'); themeContainer.style.marginTop = '20px'; const themeLabel = document.createElement('span'); themeLabel.textContent = 'Тема оформления'; themeLabel.style.fontSize = '14px'; themeLabel.style.color = '#fff'; themeLabel.style.fontWeight = 'bold'; themeLabel.style.textShadow = '0 0 3px rgba(255,255,255,0.5)'; themeLabel.style.display = 'block'; themeLabel.style.marginBottom = '8px'; const selectWrapper = document.createElement('div'); selectWrapper.style.position = 'relative'; selectWrapper.style.width = '100%'; const select = document.createElement('select'); select.style.width = '100%'; select.style.padding = '8px 25px 8px 10px'; select.style.background = '#2b2b2b'; select.style.color = '#fff'; select.style.border = '1px solid #ff007a'; select.style.borderRadius = '8px'; select.style.fontSize = '14px'; select.style.cursor = 'pointer'; select.style.appearance = 'none'; select.style.outline = 'none'; select.style.transition = 'border-color 0.3s ease'; for (const themeName in THEMES) { const option = document.createElement('option'); option.value = themeName; option.textContent = themeName; if (themeName === settings.selectedTheme) { option.selected = true; } select.appendChild(option); } const arrow = document.createElement('span'); arrow.textContent = '▼'; arrow.style.position = 'absolute'; arrow.style.right = '10px'; arrow.style.top = '50%'; arrow.style.transform = 'translateY(-50%)'; arrow.style.color = '#ff007a'; arrow.style.pointerEvents = 'none'; selectWrapper.appendChild(select); selectWrapper.appendChild(arrow); themeContainer.appendChild(themeLabel); themeContainer.appendChild(selectWrapper); select.addEventListener('change', (e) => { const selectedTheme = e.target.value; applyTheme(selectedTheme); }); select.addEventListener('mouseover', () => { select.style.borderColor = '#00ff9d'; }); select.addEventListener('mouseout', () => { select.style.borderColor = '#ff007a'; }); return themeContainer; } // ### AudioWorklet процессор для pitch shifting const pitchShiftWorkletCode = ` class PitchShiftProcessor extends AudioWorkletProcessor { constructor() { super(); this.bufferSize = 4096; this.buffer = new Float32Array(this.bufferSize); this.writeIndex = 0; this.readIndex = 0; this.pitchFactor = 1.0; this.port.onmessage = (event) => { this.pitchFactor = event.data; }; } process(inputs, outputs, parameters) { const input = inputs[0][0]; const output = outputs[0][0]; if (!input || !output) return true; for (let i = 0; i < input.length; i++) { this.buffer[this.writeIndex] = input[i]; this.writeIndex = (this.writeIndex + 1) % this.bufferSize; } for (let i = 0; i < output.length; i++) { const intIndex = Math.floor(this.readIndex); const frac = this.readIndex - intIndex; const sample1 = this.buffer[intIndex % this.bufferSize]; const sample2 = this.buffer[(intIndex + 1) % this.bufferSize]; output[i] = sample1 + (sample2 - sample1) * frac; this.readIndex = (this.readIndex + this.pitchFactor) % this.bufferSize; } return true; } } registerProcessor('pitch-shift-processor', PitchShiftProcessor); `; // ### Функции авторежима function checkAndClickButton() { if (!isAutoModeEnabled) return; const button = document.querySelector('button.btn.btn-lg.go-scan-button'); if (button) { button.click(); } } function skipConversation() { const stopButton = document.querySelector('button.btn.btn-lg.stop-talk-button'); if (stopButton) { stopButton.click(); setTimeout(() => { const confirmButton = document.querySelector('button.swal2-confirm.swal2-styled'); if (confirmButton) { confirmButton.click(); playNotificationOnEnd(); } }, 500); } } function playNotificationOnEnd() { if (isConversationActive) { endConversationAudio.play(); isConversationActive = false; } } function playNotificationOnStart() { if (!isConversationActive) { startConversationAudio.dataset.custom = 'true'; // Помечаем как кастомный звук startConversationAudio.play(); isConversationActive = true; } } function updateSliderStyles(enable) { const slider = document.querySelector('.slider'); const sliderCircle = document.querySelector('.slider-circle'); if (slider) slider.style.background = enable ? '#00ff9d' : '#555'; if (sliderCircle) sliderCircle.style.left = enable ? '40px' : '4px'; } function applyCustomStyles(enable) { const styleId = 'custom-slider-styles'; let styleElement = document.getElementById(styleId); if (!styleElement) { styleElement = document.createElement('style'); styleElement.id = styleId; document.head.appendChild(styleElement); } styleElement.textContent = ` .slider { background: ${enable ? '#00ff9d' : '#555'} !important; transition: background 0.4s !important; } .slider-circle { left: ${enable ? '40px' : '4px'} !important; } `; } function toggleAutoMode(enable) { isAutoModeEnabled = enable; const toggleInput = document.querySelector('input[type="checkbox"]'); const toggleLabel = document.querySelector('span.toggle-label'); if (toggleInput) toggleInput.checked = enable; if (toggleLabel) { toggleLabel.textContent = `Авторежим ${enable ? 'ВКЛ' : 'ВЫКЛ'}`; toggleLabel.style.color = enable ? '#00ff9d' : '#ff4d4d'; toggleLabel.style.textShadow = `0 0 5px ${enable ? '#00ff9d' : '#ff4d4d'}`; } updateSliderStyles(enable); applyCustomStyles(enable); } // ### Аудио функции async function getMicStream() { try { micStream = await navigator.mediaDevices.getUserMedia({ audio: { autoGainControl: settings.autoGainControl, noiseSuppression: settings.noiseSuppression, echoCancellation: settings.echoCancellation } }); return micStream; } catch (e) { console.error('Ошибка получения микрофона:', e); return null; } } function enableSelfListening(stream) { if (!stream || !stream.getAudioTracks().length) return; if (audioContext) audioContext.close(); audioContext = new AudioContext(); const source = audioContext.createMediaStreamSource(stream); gainNode = audioContext.createGain(); gainNode.gain.value = settings.gainValue; if (settings.voicePitch && settings.pitchLevel > 0) { pitchNode = audioContext.createBiquadFilter(); pitchNode.type = 'lowshelf'; pitchNode.frequency.value = 300; pitchNode.gain.value = 10; source.connect(pitchNode); pitchNode.connect(gainNode); } else { source.connect(gainNode); } gainNode.connect(audioContext.destination); } async function createPitchShiftedStream(stream) { if (pitchAudioContext) pitchAudioContext.close(); pitchAudioContext = new AudioContext(); pitchSource = pitchAudioContext.createMediaStreamSource(stream); const outputNode = pitchAudioContext.createGain(); if (settings.voicePitch && settings.pitchLevel > 0) { const blob = new Blob([pitchShiftWorkletCode], { type: 'application/javascript' }); const url = URL.createObjectURL(blob); await pitchAudioContext.audioWorklet.addModule(url); pitchWorkletNode = new AudioWorkletNode(pitchAudioContext, 'pitch-shift-processor'); const pitchShiftFactor = 1.0 - settings.pitchLevel; pitchWorkletNode.port.postMessage(pitchShiftFactor); pitchNode = pitchAudioContext.createBiquadFilter(); pitchNode.type = 'lowshelf'; pitchNode.frequency.value = 300; pitchNode.gain.value = 10; pitchSource.connect(pitchWorkletNode); pitchWorkletNode.connect(pitchNode); pitchNode.connect(outputNode); } else { pitchSource.connect(outputNode); } const destination = pitchAudioContext.createMediaStreamDestination(); outputNode.connect(destination); return destination.stream; } function updatePitchEffect(enable) { settings.voicePitch = enable; if (globalStream) { createPitchShiftedStream(micStream || globalStream).then(newStream => { globalStream.getAudioTracks().forEach(track => track.stop()); globalStream = newStream; if (settings.enableLoopback) enableSelfListening(newStream); }); } } function updatePitchLevel(value) { settings.pitchLevel = value; localStorage.setItem('pitchLevel', value); if (pitchWorkletNode) { const pitchShiftFactor = 1.0 - settings.pitchLevel; pitchWorkletNode.port.postMessage(pitchShiftFactor); } else if (globalStream) { createPitchShiftedStream(micStream || globalStream).then(newStream => { globalStream.getAudioTracks().forEach(track => track.stop()); globalStream = newStream; if (settings.enableLoopback) enableSelfListening(newStream); }); } } // ### Улучшенная автогромкость function setupAutoVolume(stream) { if (!settings.autoVolume || !stream) return; if (remoteAudioContext) remoteAudioContext.close(); if (volumeCheckIntervalId) clearInterval(volumeCheckIntervalId); remoteAudioContext = new AudioContext(); const source = remoteAudioContext.createMediaStreamSource(stream); volumeAnalyser = remoteAudioContext.createAnalyser(); volumeAnalyser.fftSize = 256; source.connect(volumeAnalyser); const bufferLength = volumeAnalyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); const audioElement = document.querySelector('audio#audioStream'); volumeHistory = []; lastAdjustedVolume = TARGET_VOLUME; function adjustVolume() { if (!settings.autoVolume || !volumeAnalyser || !audioElement) return; volumeAnalyser.getByteTimeDomainData(dataArray); let sum = 0; for (let i = 0; i < bufferLength; i++) { const value = (dataArray[i] - 128) / 128; sum += value * value; } const rms = Math.sqrt(sum / bufferLength); const volumeLevel = Math.min(1, rms * 10) * 100; volumeHistory.push(volumeLevel); if (volumeHistory.length > HISTORY_SIZE) volumeHistory.shift(); const avgVolume = volumeHistory.reduce((a, b) => a + b, 0) / volumeHistory.length; const volumeSlider = document.querySelector('.volume_slider input.slider-input'); if (!volumeSlider) return; let targetValue; const currentTime = Date.now(); if (avgVolume > TARGET_VOLUME + 20) { targetValue = Math.max(MIN_VOLUME, TARGET_VOLUME - (avgVolume - TARGET_VOLUME)); lastLoudTime = currentTime; lastAdjustedVolume = targetValue; } else if (currentTime - lastLoudTime < HOLD_DURATION || avgVolume < SILENCE_THRESHOLD) { targetValue = lastAdjustedVolume; } else if (avgVolume < TARGET_VOLUME - 20) { targetValue = Math.min(MAX_VOLUME, lastAdjustedVolume + (TARGET_VOLUME - avgVolume) / 2); lastAdjustedVolume = targetValue; } else { targetValue = lastAdjustedVolume; } const startValue = parseInt(volumeSlider.value) || TARGET_VOLUME; if (Math.abs(startValue - targetValue) > 5) { smoothTransition(volumeSlider, startValue, targetValue, audioElement); } } function smoothTransition(slider, startValue, targetValue, audio) { const startTime = performance.now(); function step(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / TRANSITION_DURATION, 1); const newValue = startValue + (targetValue - startValue) * progress; slider.value = newValue; slider.dispatchEvent(new Event('input', { bubbles: true })); slider.dispatchEvent(new Event('change', { bubbles: true })); updateSliderVisuals(newValue); audio.volume = newValue / 100; if (progress < 1) requestAnimationFrame(step); } requestAnimationFrame(step); } function updateSliderVisuals(value) { const sliderDot = document.querySelector('.slider-dot'); const sliderProcess = document.querySelector('.slider-process'); const tooltip = document.querySelector('.slider-tooltip'); if (sliderDot && sliderProcess && tooltip) { const maxTranslate = 48; const translateX = (value / 100) * maxTranslate; sliderDot.style.transform = `translateX(${translateX}px)`; sliderProcess.style.width = `${translateX + 4}px`; tooltip.textContent = Math.round(value); } } volumeCheckIntervalId = setInterval(adjustVolume, VOLUME_CHECK_INTERVAL); } // ### Голосовое управление async function initSpeechRecognition() { if (!('webkitSpeechRecognition' in window)) return; if (!micStream) await getMicStream(); recognition = new webkitSpeechRecognition(); recognition.continuous = true; recognition.interimResults = false; recognition.lang = 'ru-RU'; recognition.onresult = (event) => { if (!isVoiceControlEnabled) return; const transcript = event.results[event.results.length - 1][0].transcript.trim().toLowerCase(); if (VOICE_COMMANDS.skip.some(cmd => transcript.includes(cmd))) { skipConversation(); } else if (VOICE_COMMANDS.stop.some(cmd => transcript.includes(cmd))) { toggleAutoMode(false); skipConversation(); } else if (VOICE_COMMANDS.start.some(cmd => transcript.includes(cmd))) { toggleAutoMode(true); checkAndClickButton(); } }; recognition.onerror = (event) => { if (event.error !== 'aborted' && isVoiceControlEnabled) setTimeout(() => recognition.start(), 100); }; recognition.onend = () => { if (isVoiceControlEnabled) setTimeout(() => recognition.start(), 100); }; if (settings.voiceControl) { isVoiceControlEnabled = true; recognition.start(); if (voiceHintElement) voiceHintElement.style.display = 'inline-block'; setInterval(() => { if (isVoiceControlEnabled) { recognition.stop(); setTimeout(() => recognition.start(), 200); } }, 10000); } } // ### Счетчик разговоров function updateConversationStats(duration) { settings.conversationCount++; if (duration >= 300) settings.conversationStats.over5min++; if (duration >= 900) settings.conversationStats.over15min++; if (duration >= 1800) settings.conversationStats.over30min++; if (duration >= 3600) settings.conversationStats.over1hour++; if (duration >= 7200) settings.conversationStats.over2hours++; if (duration >= 10800) settings.conversationStats.over3hours++; if (duration >= 18000) settings.conversationStats.over5hours++; saveSetting('conversationCount', settings.conversationCount); saveSetting('conversationStats', settings.conversationStats); const counter = document.querySelector('#conversation-counter span'); if (counter) counter.textContent = `Разговоров: ${settings.conversationCount}`; } function startConversationTimer() { if (conversationTimer) clearInterval(conversationTimer); currentConversationStart = Date.now(); playNotificationOnStart(); conversationTimer = setInterval(() => { const timerElement = document.querySelector('.timer-label'); if (!timerElement || timerElement.textContent === '00:00') { stopConversationTimer(); } }, 1000); } function stopConversationTimer() { if (conversationTimer && currentConversationStart) { clearInterval(conversationTimer); const duration = Math.floor((Date.now() - currentConversationStart) / 1000); updateConversationStats(duration); playNotificationOnEnd(); conversationTimer = null; currentConversationStart = null; } } // ### Новые функции для управления микрофоном и наушниками function toggleMic() { isMicMuted = !isMicMuted; if (globalStream) { globalStream.getAudioTracks().forEach(track => { track.enabled = !isMicMuted; }); } updateButtonStyles(); } function toggleHeadphones() { isHeadphonesMuted = !isHeadphonesMuted; const audio = document.querySelector('audio#audioStream'); if (audio) { audio.muted = isHeadphonesMuted; } if (isHeadphonesMuted && !isMicMuted) { toggleMic(); // Выключение наушников мутит микрофон } updateButtonStyles(); } function updateButtonStyles() { const micButton = document.querySelector('#mic-toggle'); const headphoneButton = document.querySelector('#headphone-toggle'); if (micButton) { const micState = globalStream && globalStream.getAudioTracks().length > 0 ? !globalStream.getAudioTracks()[0].enabled : isMicMuted; isMicMuted = micState; micButton.style.background = isMicMuted ? '#ff4d4d' : '#00ff9d'; micButton.style.textDecoration = isMicMuted ? 'line-through' : 'none'; micButton.style.boxShadow = `0 0 10px ${isMicMuted ? '#ff4d4d' : '#00ff9d'}`; } if (headphoneButton) { headphoneButton.style.background = isHeadphonesMuted ? '#ff4d4d' : '#00ff9d'; headphoneButton.style.textDecoration = isHeadphonesMuted ? 'line-through' : 'none'; headphoneButton.style.boxShadow = `0 0 10px ${isHeadphonesMuted ? '#ff4d4d' : '#00ff9d'}`; } } // ### UI элементы function createVoiceHints() { const wrapper = document.createElement('div'); wrapper.className = 'voice-hint-wrapper'; wrapper.style.cssText = `margin-left: 5px; display: ${isVoiceControlEnabled ? 'inline-block' : 'none'};`; const trigger = document.createElement('span'); trigger.textContent = 'Подсказка'; trigger.style.cssText = `font-size: 12px; color: #bbb; cursor: help; padding: 2px 5px; background: #444; border-radius: 5px; transition: color 0.2s ease;`; const content = document.createElement('div'); content.style.cssText = `position: absolute; background: rgba(43, 43, 43, 0.95); color: #fff; padding: 8px; border-radius: 6px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4); font-size: 11px; display: none; transform: translateY(5px); opacity: 0; transition: all 0.3s ease; z-index: 1000;`; content.innerHTML = `<b>Голосовые команды:</b><br><b>пропустить: </b>${VOICE_COMMANDS.skip.join('/')}<br><b>начать: </b>${VOICE_COMMANDS.start.join('/')}<br><b>остановить: </b>${VOICE_COMMANDS.stop.join('/')}`; wrapper.appendChild(trigger); wrapper.appendChild(content); trigger.addEventListener('mouseenter', () => { trigger.style.color = '#fff'; content.style.display = 'block'; setTimeout(() => { content.style.opacity = '1'; content.style.transform = 'translateY(0)'; }, 10); }); trigger.addEventListener('mouseleave', () => { trigger.style.color = '#bbb'; content.style.opacity = '0'; content.style.transform = 'translateY(5px)'; setTimeout(() => content.style.display = 'none', 300); }); voiceHintElement = wrapper; return wrapper; } function createConversationCounter() { const counterDiv = document.createElement('div'); counterDiv.id = 'conversation-counter'; counterDiv.style.cssText = ` margin-bottom: 15px; text-align: center; position: relative; `; const counterSpan = document.createElement('span'); counterSpan.textContent = `Разговоров: ${settings.conversationCount}`; counterSpan.style.cssText = ` color: #00ff9d; font-size: 16px; font-weight: bold; text-shadow: 0 0 5px #00ff9d, 0 0 10px #00ff9d; padding: 5px 10px; background: rgba(0, 0, 0, 0.7); border-radius: 8px; cursor: default; display: inline-block; `; const tooltip = document.createElement('div'); tooltip.style.cssText = ` position: absolute; top: calc(100% + 5px); left: 50%; transform: translateX(-50%); background: rgba(43, 43, 43, 0.95); color: #fff; padding: 10px; border-radius: 6px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4); font-size: 12px; display: none; opacity: 0; transition: opacity 0.3s ease; z-index: 1000; white-space: nowrap; `; tooltip.innerHTML = ` Из них дольше:<br> 5 минут: ${settings.conversationStats.over5min}<br> 15 минут: ${settings.conversationStats.over15min}<br> 30 минут: ${settings.conversationStats.over30min}<br> 1 часа: ${settings.conversationStats.over1hour}<br> 2 часов: ${settings.conversationStats.over2hours}<br> 3 часов: ${settings.conversationStats.over3hours}<br> 5 часов: ${settings.conversationStats.over5hours} `; counterDiv.appendChild(counterSpan); counterDiv.appendChild(tooltip); counterSpan.addEventListener('mouseenter', () => { tooltip.style.display = 'block'; setTimeout(() => { tooltip.style.opacity = '1'; }, 10); }); counterSpan.addEventListener('mouseleave', () => { tooltip.style.opacity = '0'; setTimeout(() => { tooltip.style.display = 'none'; }, 300); }); return counterDiv; } function createSettingsUI() { const container = document.createElement('div'); container.id = 'settings-container'; container.style.position = 'fixed'; container.style.top = '20px'; container.style.right = '20px'; container.style.zIndex = '9999'; container.style.background = 'linear-gradient(135deg, #2b2b2b, #1f1f1f)'; container.style.padding = '20px'; container.style.borderRadius = '15px'; container.style.boxShadow = '0 5px 20px rgba(0, 0, 0, 0.7)'; container.style.border = '2px solid #ff007a'; container.style.width = '250px'; container.style.color = '#fff'; container.style.fontFamily = "'Segoe UI', Arial, sans-serif"; container.style.transition = 'transform 0.3s ease'; const header = document.createElement('h3'); header.textContent = 'Настройки'; header.style.margin = '0 0 15px'; header.style.fontSize = '20px'; header.style.color = '#ff007a'; header.style.textAlign = 'center'; header.style.textTransform = 'uppercase'; header.style.letterSpacing = '2px'; container.appendChild(header); container.appendChild(createConversationCounter()); const audioControls = document.createElement('div'); audioControls.style.display = 'flex'; audioControls.style.gap = '10px'; audioControls.style.marginBottom = '20px'; audioControls.style.justifyContent = 'center'; const micButton = document.createElement('button'); micButton.id = 'mic-toggle'; micButton.innerHTML = '🎤'; micButton.style.width = '40px'; micButton.style.height = '40px'; micButton.style.borderRadius = '50%'; micButton.style.background = '#00ff9d'; micButton.style.border = 'none'; micButton.style.cursor = 'pointer'; micButton.style.fontSize = '20px'; micButton.style.display = 'flex'; micButton.style.alignItems = 'center'; micButton.style.justifyContent = 'center'; micButton.style.boxShadow = '0 0 10px #00ff9d'; micButton.style.transition = 'all 0.3s ease'; micButton.addEventListener('click', toggleMic); const headphoneButton = document.createElement('button'); headphoneButton.id = 'headphone-toggle'; headphoneButton.innerHTML = '🎧'; headphoneButton.style.width = '40px'; headphoneButton.style.height = '40px'; headphoneButton.style.borderRadius = '50%'; headphoneButton.style.background = '#00ff9d'; headphoneButton.style.border = 'none'; headphoneButton.style.cursor = 'pointer'; headphoneButton.style.fontSize = '20px'; headphoneButton.style.display = 'flex'; headphoneButton.style.alignItems = 'center'; headphoneButton.style.justifyContent = 'center'; headphoneButton.style.boxShadow = '0 0 10px #00ff9d'; headphoneButton.style.transition = 'all 0.3s ease'; headphoneButton.addEventListener('click', toggleHeadphones); audioControls.appendChild(micButton); audioControls.appendChild(headphoneButton); container.appendChild(audioControls); const toggleWrapper = document.createElement('div'); toggleWrapper.style.display = 'flex'; toggleWrapper.style.alignItems = 'center'; toggleWrapper.style.gap = '15px'; toggleWrapper.style.marginBottom = '20px'; const label = document.createElement('label'); label.style.position = 'relative'; label.style.display = 'inline-block'; label.style.width = '70px'; label.style.height = '34px'; const toggleInput = document.createElement('input'); toggleInput.type = 'checkbox'; toggleInput.style.display = 'none'; toggleInput.checked = isAutoModeEnabled; const slider = document.createElement('span'); slider.className = 'slider'; slider.style.position = 'absolute'; slider.style.cursor = 'pointer'; slider.style.top = '0'; slider.style.left = '0'; slider.style.right = '0'; slider.style.bottom = '0'; slider.style.background = isAutoModeEnabled ? '#00ff9d' : '#555'; slider.style.transition = 'background 0.4s'; slider.style.borderRadius = '34px'; slider.style.boxShadow = 'inset 0 2px 5px rgba(0,0,0,0.5)'; const sliderCircle = document.createElement('span'); sliderCircle.className = 'slider-circle'; sliderCircle.style.position = 'absolute'; sliderCircle.style.height = '26px'; sliderCircle.style.width = '26px'; sliderCircle.style.left = isAutoModeEnabled ? '40px' : '4px'; sliderCircle.style.bottom = '4px'; sliderCircle.style.background = '#fff'; sliderCircle.style.transition = 'left 0.4s'; sliderCircle.style.borderRadius = '50%'; sliderCircle.style.boxShadow = '0 2px 5px rgba(0,0,0,0.3)'; slider.appendChild(sliderCircle); label.appendChild(toggleInput); label.appendChild(slider); const toggleLabel = document.createElement('span'); toggleLabel.className = 'toggle-label'; toggleLabel.textContent = `Авторежим ${isAutoModeEnabled ? 'ВКЛ' : 'ВЫКЛ'}`; toggleLabel.style.color = isAutoModeEnabled ? '#00ff9d' : '#ff4d4d'; toggleLabel.style.fontSize = '16px'; toggleLabel.style.fontWeight = 'bold'; toggleLabel.style.textShadow = `0 0 5px ${isAutoModeEnabled ? '#00ff9d' : '#ff4d4d'}`; toggleWrapper.appendChild(label); toggleWrapper.appendChild(toggleLabel); container.appendChild(toggleWrapper); const audioSettings = document.createElement('div'); audioSettings.style.display = 'flex'; audioSettings.style.flexDirection = 'column'; audioSettings.style.gap = '15px'; function createToggle(labelText, key) { const div = document.createElement('div'); const toggleDiv = document.createElement('div'); toggleDiv.style.display = 'flex'; toggleDiv.style.alignItems = 'center'; toggleDiv.style.gap = '10px'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.style.appearance = 'none'; checkbox.style.width = '20px'; checkbox.style.height = '20px'; checkbox.style.background = settings[key] ? '#00ff9d' : '#555'; checkbox.style.borderRadius = '5px'; checkbox.style.cursor = 'pointer'; checkbox.style.transition = 'background 0.3s'; checkbox.style.boxShadow = 'inset 0 2px 5px rgba(0,0,0,0.5)'; checkbox.checked = settings[key]; const labelSpan = document.createElement('span'); labelSpan.textContent = labelText; labelSpan.style.fontSize = '14px'; labelSpan.style.color = '#fff'; labelSpan.style.fontWeight = 'bold'; labelSpan.style.textShadow = '0 0 3px rgba(255,255,255,0.5)'; toggleDiv.appendChild(checkbox); toggleDiv.appendChild(labelSpan); div.appendChild(toggleDiv); let volumeContainer = null; let pitchContainer = null; if (key === 'enableLoopback') { volumeContainer = document.createElement('div'); volumeContainer.style.display = settings.enableLoopback ? 'block' : 'none'; volumeContainer.style.marginTop = '10px'; const volumeLabel = document.createElement('span'); volumeLabel.textContent = `Громкость самопрослушивания: ${settings.gainValue.toFixed(1)}`; volumeLabel.style.fontSize = '14px'; volumeLabel.style.color = '#fff'; volumeLabel.style.fontWeight = 'bold'; volumeLabel.style.textShadow = '0 0 3px rgba(255,255,255,0.5)'; const volumeSlider = document.createElement('input'); volumeSlider.type = 'range'; volumeSlider.min = '0.1'; volumeSlider.max = '3.0'; volumeSlider.step = '0.1'; volumeSlider.value = settings.gainValue; volumeSlider.style.width = '100%'; volumeSlider.style.height = '8px'; volumeSlider.style.background = `linear-gradient(to right, #ff007a ${((settings.gainValue - 0.1) / 2.9) * 100}%, #555 0%)`; volumeSlider.style.borderRadius = '5px'; volumeSlider.style.outline = 'none'; volumeSlider.style.cursor = 'pointer'; volumeSlider.style.appearance = 'none'; volumeContainer.appendChild(volumeLabel); volumeContainer.appendChild(volumeSlider); volumeSlider.addEventListener('input', () => { settings.gainValue = parseFloat(volumeSlider.value); volumeLabel.textContent = `Громкость самопрослушивания: ${settings.gainValue.toFixed(1)}`; saveSetting('gainValue', settings.gainValue); if (gainNode) gainNode.gain.value = settings.gainValue; volumeSlider.style.background = `linear-gradient(to right, #ff007a ${((settings.gainValue - 0.1) / 2.9) * 100}%, #555 0%)`; }); } if (key === 'voicePitch') { pitchContainer = document.createElement('div'); pitchContainer.style.display = settings.voicePitch ? 'block' : 'none'; pitchContainer.style.marginTop = '10px'; const pitchLabel = document.createElement('span'); pitchLabel.textContent = `0 - обычный голос, 0.40 - очень низкий: ${settings.pitchLevel.toFixed(2)}`; pitchLabel.style.fontSize = '14px'; pitchLabel.style.color = '#fff'; pitchLabel.style.fontWeight = 'bold'; pitchLabel.style.textShadow = '0 0 3px rgba(255,255,255,0.5)'; const pitchSlider = document.createElement('input'); pitchSlider.type = 'range'; pitchSlider.min = '0'; pitchSlider.max = '0.4'; pitchSlider.step = '0.01'; pitchSlider.value = settings.pitchLevel; pitchSlider.style.width = '100%'; pitchSlider.style.height = '8px'; pitchSlider.style.background = `linear-gradient(to right, #ff007a ${(settings.pitchLevel / 0.4) * 100}%, #555 0%)`; pitchSlider.style.borderRadius = '5px'; pitchSlider.style.outline = 'none'; pitchSlider.style.cursor = 'pointer'; pitchSlider.style.appearance = 'none'; pitchContainer.appendChild(pitchLabel); pitchContainer.appendChild(pitchSlider); pitchSlider.addEventListener('input', () => { settings.pitchLevel = parseFloat(pitchSlider.value); pitchLabel.textContent = `0 - обычный голос, 0.40 - очень низкий: ${settings.pitchLevel.toFixed(2)}`; saveSetting('pitchLevel', settings.pitchLevel); updatePitchLevel(settings.pitchLevel); pitchSlider.style.background = `linear-gradient(to right, #ff007a ${(settings.pitchLevel / 0.4) * 100}%, #555 0%)`; }); } checkbox.addEventListener('change', () => { settings[key] = checkbox.checked; saveSetting(key, checkbox.checked); checkbox.style.background = checkbox.checked ? '#00ff9d' : '#555'; if (key === 'enableLoopback') { if (checkbox.checked && globalStream) enableSelfListening(globalStream); else if (audioContext) audioContext.close(); if (volumeContainer) volumeContainer.style.display = checkbox.checked ? 'block' : 'none'; } else if (key === 'voiceControl') { isVoiceControlEnabled = checkbox.checked; if (voiceHintElement) voiceHintElement.style.display = checkbox.checked ? 'inline-block' : 'none'; if (checkbox.checked) { if (!recognition) initSpeechRecognition(); recognition.start(); } else if (recognition) recognition.stop(); } else if (key === 'autoVolume') { if (checkbox.checked) { const audio = document.querySelector('audio#audioStream'); if (audio && audio.srcObject) setupAutoVolume(audio.srcObject); } else { if (remoteAudioContext) remoteAudioContext.close(); if (volumeCheckIntervalId) clearInterval(volumeCheckIntervalId); } } else if (key === 'voicePitch') { updatePitchEffect(checkbox.checked); if (pitchContainer) pitchContainer.style.display = checkbox.checked ? 'block' : 'none'; } }); if (key === 'voiceControl') div.appendChild(createVoiceHints()); if (key === 'enableLoopback' && volumeContainer) div.appendChild(volumeContainer); if (key === 'voicePitch' && pitchContainer) div.appendChild(pitchContainer); return div; } audioSettings.appendChild(createToggle('Самопрослушивание', 'enableLoopback')); audioSettings.appendChild(createToggle('Автогромкость микрофона', 'autoGainControl')); audioSettings.appendChild(createToggle('Автогромкость собеседника', 'autoVolume')); audioSettings.appendChild(createToggle('Шумоподавление', 'noiseSuppression')); audioSettings.appendChild(createToggle('Эхоподавление', 'echoCancellation')); audioSettings.appendChild(createToggle('Низкий голос', 'voicePitch')); audioSettings.appendChild(createToggle('Голосовое управление', 'voiceControl')); container.appendChild(audioSettings); container.appendChild(createThemeSelector()); document.body.appendChild(container); const styleSheet = document.createElement('style'); styleSheet.textContent = ` input[type="range"]::-webkit-slider-thumb { appearance: none; width: 16px; height: 16px; background: #ff007a; border-radius: 50%; cursor: pointer; box-shadow: 0 0 5px #ff007a; } select:hover { background: #333; } select:focus { border-color: #00ff9d; } `; document.head.appendChild(styleSheet); container.addEventListener('mouseover', () => container.style.transform = 'scale(1.02)'); container.addEventListener('mouseout', () => container.style.transform = 'scale(1)'); toggleInput.addEventListener('change', (e) => toggleAutoMode(e.target.checked)); } // ### Инициализация function initObserver() { observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes.length) { checkAndClickButton(); const audio = document.querySelector('audio#audioStream'); if (audio && audio.srcObject && settings.autoVolume) setupAutoVolume(audio.srcObject); const timerElement = document.querySelector('.timer-label'); if (timerElement && timerElement.textContent === '00:00' && !conversationTimer) { startConversationTimer(); } if (!timerElement && conversationTimer) { stopConversationTimer(); } const stopButton = document.querySelector('button.btn.btn-lg.stop-talk-button'); if (stopButton && !stopButton.dataset.listenerAdded) { stopButton.addEventListener('click', () => { setTimeout(() => { const confirmButton = document.querySelector('button.swal2-confirm.swal2-styled'); if (confirmButton && !confirmButton.dataset.listenerAdded) { confirmButton.addEventListener('click', playNotificationOnEnd); confirmButton.dataset.listenerAdded = 'true'; } }, 500); }); stopButton.dataset.listenerAdded = 'true'; } } }); }); observer.observe(document.body, { childList: true, subtree: true }); } navigator.mediaDevices.getUserMedia = ((original) => { return async (constraints) => { if (constraints?.audio) { constraints.audio = { ...constraints.audio, autoGainControl: settings.autoGainControl, noiseSuppression: settings.noiseSuppression, echoCancellation: settings.echoCancellation }; } const stream = await original.call(navigator.mediaDevices, constraints); micStream = stream; const processedStream = await createPitchShiftedStream(stream); globalStream = processedStream; if (globalStream && isMicMuted) { globalStream.getAudioTracks().forEach(track => { track.enabled = false; }); } if (settings.enableLoopback) enableSelfListening(processedStream); return processedStream; }; })(navigator.mediaDevices.getUserMedia); const originalSet = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'srcObject').set; Object.defineProperty(HTMLMediaElement.prototype, 'srcObject', { set: function(stream) { originalSet.call(this, stream); if (this.id === 'audioStream' && stream && settings.autoVolume) setupAutoVolume(stream); } }); async function init() { console.log('Инициализация скрипта...'); createSettingsUI(); applyTheme(settings.selectedTheme); checkAndClickButton(); initObserver(); await initSpeechRecognition(); console.log('Инициализация завершена'); } window.addEventListener('load', () => { console.log('Страница загружена, запускаем init'); init(); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址