您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Голосовые сообщения и голосовой ввод
// ==UserScript== // @name VoiceTools // @namespace Android/lolz // @version 0.1 // @license MIT // @description Голосовые сообщения и голосовой ввод // @author Android // @match *://zelenka.guru/threads/* // @icon https://www.google.com/s2/favicons?sz=64&domain=zelenka.guru // @grant none // ==/UserScript== const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition || null; if (!SpeechRecognition) { console.error("Ваш браузер не поддерживает голосовой ввод"); } (async function () { 'use strict'; const COLORS = { GRAY: '#8c8c8c', GREEN: '#2BAD72', LIGHT_GREEN: '#33cc87', LIGHT_GRAY: '#D6D6D6', RED: '#fa3939' }; const SELECTORS = { PARAGRAPH: '.fr-element p', WRAPPER: '.fr-wrapper', BOX: '.fr-box', BUTTON: '.lzt-fe-se-extraButton', }; const BUTTON_SIZES = { WIDTH: '24px', HEIGHT: '24px', }; const TIMEOUT_DURATION = 10 * 1000; let token = ''; const inputElement = document.querySelector('input[name="_xfToken"]'); const xfToken = inputElement.value; let isListening = false; const button = createButton(); let mediaRecorder; let recognition; let timeoutId; let isMouseDown = false; let pressTimeout; let holdTimeout; function createButton() { const button = document.createElement('button'); button.style.width = BUTTON_SIZES.WIDTH; button.style.height = BUTTON_SIZES.HEIGHT; button.style.color = COLORS.GRAY; button.style.borderRadius = '100%' button.classList.add('main__block-color_taupe'); button.type = 'button'; button.style.cursor = 'pointer'; button.style.backgroundColor = 'transparent'; button.style.border = 'none'; button.style.display = 'flex'; button.style.alignItems = 'center'; button.style.justifyContent = 'center'; const SVG_SIZES = { WIDTH: 20, HEIGHT: 20, }; const microphoneSvg = ` <svg xmlns="http://www.w3.org/2000/svg" width="${SVG_SIZES.WIDTH}" height="${SVG_SIZES.HEIGHT}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mic"> <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path> <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path> <line x1="12" y1="19" x2="12" y2="23"></line> <line x1="8" y1="23" x2="16" y2="23"></line> </svg> `; button.innerHTML = microphoneSvg; const element = document.querySelector(SELECTORS.BUTTON); element.parentNode.insertBefore(button, element); button.addEventListener('mousedown', function () { pressTimeout = setTimeout(function () { if (!isListening) { startRecording() clearTimeout(pressTimeout); isMouseDown = true; } }, 500); }); button.addEventListener('mouseup', function () { clearTimeout(pressTimeout); clearTimeout(holdTimeout); if (!isMouseDown) { toggleListening() } else { stopRecording() isMouseDown = false; } }); button.addEventListener('mouseleave', function () { clearTimeout(pressTimeout); clearTimeout(holdTimeout); }); return button; } function startListening() { button.style.color = COLORS.GREEN; isListening = true; recognition = new SpeechRecognition(); recognition.lang = 'ru-RU'; recognition.continuous = true; recognition.onstart = () => { clearTimeout(timeoutId); }; recognition.onresult = (event) => { clearTimeout(timeoutId); const result = event.results[event.results.length - 1][0].transcript; const paragraphElement = document.querySelector(SELECTORS.PARAGRAPH); const frWrapperElement = document.querySelector(SELECTORS.WRAPPER); const frBoxElement = document.querySelector(SELECTORS.BOX); paragraphElement.textContent = paragraphElement.textContent.replace(/\u200B/g, '').replace(/\u00A0/g, ''); const shouldAppendSpace = paragraphElement.textContent.trim() !== ''; paragraphElement.textContent += shouldAppendSpace ? ' ' + result : result; frWrapperElement.classList.remove('show-placeholder'); frBoxElement.classList.add('is-focused'); timeoutId = setTimeout(() => { stopListening(); }, TIMEOUT_DURATION); }; recognition.onend = () => { if (isListening) { timeoutId = setTimeout(() => { stopListening(); }, TIMEOUT_DURATION); } }; recognition.start(); } function stopListening() { recognition.stop(); isListening = false; button.style.color = COLORS.GRAY; }; function toggleListening() { if (isListening) { stopListening(); } else { startListening(); } }; async function initMediaRecorder() { const streamOptions = { audio: true }; const stream = await navigator.mediaDevices.getUserMedia(streamOptions); const options = { mimeType: 'video/webm; codecs=h264' }; mediaRecorder = new MediaRecorder(stream, options); let recordedBlobs = []; mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { recordedBlobs.push(event.data); } }; mediaRecorder.onstop = async () => { const blob = new Blob(recordedBlobs, { type: 'video/webm' }); try { const file = await uploadVideoFile(token, blob); addVoiceMessageReply(file) } catch (uploadError) { console.error('Error uploading the video file:', uploadError); } recordedBlobs = []; }; } function addVoiceMessageReply(link) { const message = '[MEDIA=cdn]' + link + '[/MEDIA]' const threadUrl = document.querySelector('link[rel="canonical"]').getAttribute('href') const addReplyUrl = threadUrl + 'add-reply' const regex = /\/threads\/\d+\//; const match = threadUrl.match(regex); const threadAndNum = match[0] let formdata = new FormData; formdata.append("message_html", message) formdata.append("_xfRelativeResolver", threadUrl) formdata.append("last_date", Math.floor(Date.now() / 1e3)) formdata.append("last_known_date", Math.floor(Date.now() / 1e3)) formdata.append("_xfRequestUri", threadAndNum) formdata.append("_xfNoRedirect", 1) formdata.append("_xfResponseType", "json") formdata.append("_xfToken", xfToken) let addReplyRequestOptions = { method: 'POST', headers: { 'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36' }, body: formdata, }; fetch(addReplyUrl, addReplyRequestOptions) } async function startRecording() { button.style.color = COLORS.RED; mediaRecorder.start(); } function stopRecording() { button.style.color = COLORS.GRAY; mediaRecorder.stop(); } async function getToken() { try { const getTokenUrl = 'https://zelenka.guru/editor/cdn'; const tokenData = { _xfToken: xfToken, _xfResponseType: 'json', }; const tokenFormData = new URLSearchParams(tokenData).toString(); const tokenRequestOptions = { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', }, body: tokenFormData, }; const response = await fetch(getTokenUrl, tokenRequestOptions); const data = await response.json(); token = data.token; } catch (error) { console.error('Error while getting token:', error); } } async function uploadVideoFile(token, blob) { const formData = new FormData(); formData.append('file', blob); const baseCdnUrl = 'https://lztcdn.com/'; const uploadUrl = baseCdnUrl + 'upload/'; const filesUrl = baseCdnUrl + 'files/'; const requestOptions = { method: 'POST', headers: { 'X-Cdn-Filename': 'File', 'X-Cdn-Token': token, }, body: formData, }; try { const response = await fetch(uploadUrl, requestOptions); const data = await response.json(); return data.key; } catch (error) { console.error('Error while uploading mp4 file:', error); return null; } } initMediaRecorder(); getToken(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址