您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Добавляет кнопку записи гс на страницах создания тем и в других местах
// ==UserScript== // @name Голосовые сообщения для лолза // @namespace http://tampermonkey.net/ // @version 1.9 // @description Добавляет кнопку записи гс на страницах создания тем и в других местах // @author eretly // @match https://zelenka.guru/* // @match https://lolz.guru/* // @match https://lolz.live/* // @license MIT // @grant GM_addStyle // @grant GM_getUserMedia // ==/UserScript== (function() { 'use strict'; const CHAT_ID = '-'; // Замените на айди вашего канала const BOT_TOKEN = ''; // Замените на токен тг бота const ENABLE_TRANSCRIPTION = true; // Переключатель для функции транскрипции const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)(); recognition.lang = 'ru-RU'; recognition.continuous = true; recognition.interimResults = false; let mediaRecorder; let audioChunks = []; let isRecording = false; let transcriptText = ''; recognition.onresult = function(event) { if (ENABLE_TRANSCRIPTION) { const last = event.results.length - 1; transcriptText += event.results[last][0].transcript + ' '; } }; recognition.onerror = function(event) { console.error('Ошибка распознавания речи:', event.error); }; function writeString(view, offset, string) { for (let i = 0; i < string.length; i++) { view.setUint8(offset + i, string.charCodeAt(i)); } } function bufferToWav(audioBuffer) { const numOfChannels = audioBuffer.numberOfChannels; const sampleRate = audioBuffer.sampleRate; const samples = audioBuffer.getChannelData(0); const buffer = new ArrayBuffer(44 + samples.length * 2); const view = new DataView(buffer); writeString(view, 0, 'RIFF'); view.setUint32(4, 36 + samples.length * 2, true); writeString(view, 8, 'WAVE'); writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, numOfChannels, true); view.setUint32(24, sampleRate, true); view.setUint32(28, sampleRate * numOfChannels * 2, true); view.setUint16(32, numOfChannels * 2, true); view.setUint16(34, 16, true); writeString(view, 36, 'data'); view.setUint32(40, samples.length * 2, true); let offset = 44; for (let i = 0; i < samples.length; i++) { view.setInt16(offset, samples[i] * 0x7FFF, true); offset += 2; } return new Blob([view], { type: 'audio/wav' }); } GM_addStyle(` .lzt-fe-se-extraButton[data-cmd="voiceRecord"] { width: 24px; height: 24px; padding: 0; display: flex; align-items: center; justify-content: center; background: none; cursor: pointer; } .lzt-fe-se-extraButton[data-cmd="voiceRecord"] svg { width: 20px; height: 20px; stroke: #8c8c8c; transition: stroke 0.3s ease; } .lzt-fe-se-extraButton[data-cmd="voiceRecord"]:hover svg { stroke: #d6d6d6; } .lzt-fe-se-extraButton[data-cmd="voiceRecord"].recording svg { stroke: #cc0000; } .fr-command[data-cmd="voiceRecord"] { position: relative; } .fr-command[data-cmd="voiceRecord"].recording i { color: #cc0000; animation: pulse 1.5s infinite; } @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; }с 100% { opacity: 1; } } `); function createRecordButton(isToolbarButton = false) { const recordButton = document.createElement(isToolbarButton ? 'button' : 'div'); recordButton.type = 'button'; recordButton.tabIndex = -1; recordButton.setAttribute('role', 'button'); recordButton.setAttribute('aria-disabled', 'false'); recordButton.className = isToolbarButton ? 'fr-command fr-btn' : 'lzt-fe-se-extraButton'; recordButton.setAttribute('data-cmd', 'voiceRecord'); recordButton.title = 'Начать / Остановить Запись голоса'; if (isToolbarButton) { recordButton.innerHTML = ` <i class="fal fa-microphone" aria-hidden="true"></i> <span class="fr-sr-only">Запись голоса</span> `; } else { recordButton.innerHTML = ` <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M19 10V12C19 15.866 15.866 19 12 19M5 10V12C5 15.866 8.13401 19 12 19M12 19V22M8 22H16M12 15C10.3431 15 9 13.6569 9 12V5C9 3.34315 10.3431 2 12 2C13.6569 2 15 3.34315 15 5V12C15 13.6569 13.6569 15 12 15Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> `; } recordButton.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); console.log('Кнопка нажата (прямой обработчик)'); handleRecordButtonClick(this); }); return recordButton; } function addRecordButton() { const buttonContainers = [ { container: document.querySelector('.lzt-fe-se-extraButtonsContainer'), isToolbar: false }, { container: document.querySelector('.js-lzt-fe-extraButtons'), isToolbar: false }, { container: document.querySelector('.fr-toolbar .fr-btn-grp.fr-float-left'), isToolbar: true } ]; buttonContainers.forEach(({ container, isToolbar }) => { if (container) { const existingButton = container.querySelector('[data-cmd="voiceRecord"]'); if (!existingButton) { const recordButton = createRecordButton(isToolbar); container.appendChild(recordButton); console.log('Кнопка записи добавлена в контейнер', container); } } }); } function handleRecordButtonClick(button) { if (!isRecording) { startRecording(button); } else { stopRecording(button); } } async function startRecording(button) { if (!button || !button.classList) { return; } try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); if (mediaRecorder && mediaRecorder.state === 'recording') { console.warn('MediaRecorder уже записывает'); return; } mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' }); audioChunks = []; transcriptText = ''; mediaRecorder.addEventListener('dataavailable', event => { audioChunks.push(event.data); }); mediaRecorder.addEventListener('stop', () => { const audioBlob = new Blob(audioChunks, { type: 'audio/webm' }); convertToOgg(audioBlob, button); stream.getTracks().forEach(track => track.stop()); if (ENABLE_TRANSCRIPTION) { recognition.stop(); } }); setTimeout(() => { mediaRecorder.start(); if (ENABLE_TRANSCRIPTION) { recognition.start(); } isRecording = true; button.classList.add('recording'); }, 10); } catch (err) { console.error('Ошибка при получении доступа к микрофону:', err); alert('Не удалось получить доступ к микрофону: ' + err.message); } } function stopRecording(button) { if (!button || !button.classList) { return; } if (!mediaRecorder || mediaRecorder.state === 'inactive' || mediaRecorder.state === 'paused') { console.warn('Запись уже остановлена или не была запущена.'); return; } setTimeout(() => { mediaRecorder.stop(); isRecording = false; button.classList.remove('recording'); console.log('Запись остановлена'); }, 10); } function convertToOgg(audioBlob, button) { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const reader = new FileReader(); reader.onload = async function() { const audioBuffer = await audioContext.decodeAudioData(reader.result); const duration = audioBuffer.duration; const offlineContext = new OfflineAudioContext(1, audioBuffer.length, audioBuffer.sampleRate); const source = offlineContext.createBufferSource(); source.buffer = audioBuffer; source.connect(offlineContext.destination); source.start(); offlineContext.startRendering().then(function(renderedBuffer) { const wavBlob = bufferToWav(renderedBuffer); const oggBlob = convertWavToOgg(wavBlob); sendAudioToTelegram(oggBlob, duration, button); }); }; reader.readAsArrayBuffer(audioBlob); } function convertWavToOgg(wavBlob) { return wavBlob; } function sendAudioToTelegram(audioBlob, duration, button) { const formData = new FormData(); formData.append('chat_id', CHAT_ID); formData.append('voice', audioBlob, 'voice.mp3'); formData.append('duration', Math.round(duration)); const xhr = new XMLHttpRequest(); xhr.open('POST', `https://api.telegram.org/bot${BOT_TOKEN}/sendVoice`, true); xhr.onload = function() { if (xhr.status === 200) { const responseData = JSON.parse(xhr.responseText); if (responseData.ok && responseData.result && responseData.result.message_id) { const telegramPostLink = `https://t.me/${responseData.result.chat.username}/${responseData.result.message_id}`; insertTextAndLinkIntoInput(button, transcriptText, telegramPostLink); } else { alert('Не удалось получить ссылку на пост в Telegram'); } } else { alert('Ошибка при отправке аудио в Telegram'); } }; xhr.onerror = function() { alert('Ошибка при отправке аудио в Telegram'); }; xhr.send(formData); } function insertTextAndLinkIntoInput(button, transcription, telegramLink) { const inputElement = button.closest('.fr-wrapper')?.querySelector('.fr-element.fr-view[contenteditable="true"]') || document.querySelector('.fr-element.fr-view'); if (inputElement) { const linkElement = document.createElement('a'); linkElement.href = telegramLink; linkElement.target = '_blank'; linkElement.textContent = telegramLink; inputElement.appendChild(linkElement); inputElement.appendChild(document.createElement('br')); if (ENABLE_TRANSCRIPTION && transcription.trim()) { const transcriptionText = document.createTextNode(`Транскрипция: [${transcription.trim()}]`); inputElement.appendChild(transcriptionText); inputElement.appendChild(document.createElement('br')); } const inputEvent = new Event('input', { bubbles: true }); inputElement.dispatchEvent(inputEvent); inputElement.scrollTop = inputElement.scrollHeight; } else { console.error('Не удалось найти поле ввода'); } } addRecordButton(); document.addEventListener('click', function(e) { if (e.target && e.target.closest('.lzt-fe-se-extraButton[data-cmd="voiceRecord"]')) { console.log('Кнопка нажата через делегирование'); handleRecordButtonClick(e); } }); function init() { addRecordButton(); document.addEventListener('click', function(e) { const recordButton = e.target.closest('[data-cmd="voiceRecord"]'); if (recordButton) { console.log('Кнопка нажата (делегирование)'); handleRecordButtonClick(recordButton); } }); recognition.onresult = function(event) { if (ENABLE_TRANSCRIPTION) { const last = event.results.length - 1; transcriptText += event.results[last][0].transcript + ' '; } }; recognition.onerror = function(event) { console.error('Ошибка распознавания речи:', event.error); }; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList') { addRecordButton(); } }); }); observer.observe(document.body, { childList: true, subtree: true }); })();