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