Голосовые сообщения для лолза

Добавляет кнопку записи гс везде - отправляет эту запись в тг канал и вставляет транскрипцию и ссылку

目前為 2025-01-05 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Голосовые сообщения для лолза
// @namespace    http://tampermonkey.net/
// @version      1.7
// @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 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;
        }
    `);

    let mediaRecorder;
    let audioChunks = [];
    let isRecording = false;

    function createRecordButton() {
        const recordButton = document.createElement('div');
        recordButton.id = 'lzt-fe-eb-voiceRecord';
        recordButton.setAttribute('type', 'button');
        recordButton.setAttribute('tabindex', '-1');
        recordButton.setAttribute('role', 'button');
        recordButton.setAttribute('aria-disabled', 'false');
        recordButton.className = 'lzt-fe-se-extraButton';
        recordButton.setAttribute('data-cmd', 'voiceRecord');
        recordButton.title = 'Начать / Остановить Запись голоса';
        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>
        `;
        return recordButton;
    }

    function addRecordButton() {
        const buttonContainers = [
            document.querySelector('.lzt-fe-se-extraButtonsContainer'),
            document.querySelector('.js-lzt-fe-extraButtons')
        ];

        buttonContainers.forEach((buttonContainer) => {
            if (buttonContainer) {
                const existingButton = buttonContainer.querySelector('[data-cmd="voiceRecord"]');
                if (!existingButton) {
                    const recordButton = createRecordButton();
                    buttonContainer.appendChild(recordButton);
                    console.log('Кнопка записи добавлена в контейнер');
                }
            } else {
                console.error('Контейнер для кнопки не найден');
            }
        });
    }

    function handleRecordButtonClick(e) {
        console.log('Кнопка нажата');
        e.preventDefault();
        e.stopPropagation();
        if (!isRecording) {
            console.log('Начинаем запись');
            startRecording(e.target.closest('.lzt-fe-se-extraButton'));
        } else {
            console.log('Останавливаем запись');
            stopRecording(e.target.closest('.lzt-fe-se-extraButton'));
        }
    }

    async function startRecording(button) {
        try {
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            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 (mediaRecorder && mediaRecorder.state !== 'inactive') {
            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) {
            if (ENABLE_TRANSCRIPTION && transcription.trim()) {
                const transcriptionText = document.createTextNode(`Транскрипция: [${transcription.trim()}]`);
                inputElement.appendChild(transcriptionText);
                inputElement.appendChild(document.createElement('br'));
            }

            const linkElement = document.createElement('a');
            linkElement.href = telegramLink;
            linkElement.target = '_blank';
            linkElement.textContent = telegramLink;
            inputElement.appendChild(linkElement);

            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);
        }
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址