Отправляет команды !дис, !тг, !ютуб в чат Twitch по нажатию горячей клавиши!
// ==UserScript==
// @name Twitch HeLLSocials
// @namespace http://tampermonkey.net/
// @version 0.21
// @description Отправляет команды !дис, !тг, !ютуб в чат Twitch по нажатию горячей клавиши!
// @author dear_lesberk
// @match https://www.twitch.tv/dear_hellgirl
// @icon https://www.google.com/s2/favicons?sz=64&domain=twitch.tv
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const BIND_KEY = 'F2';
const COMMANDS = ['!дис', '!тг', '!ютуб'];
const BASE_DELAY = 2000;
let slowModeTimer = 0;
let slowModeDuration = 0;
let lastSlowModeCheck = 0;
function isTyping() {
const activeElement = document.activeElement;
if (!activeElement) return false;
if (activeElement.getAttribute('data-slate-editor') === 'true') return true;
if (activeElement.isContentEditable) return true;
const tagName = activeElement.tagName.toLowerCase();
if (tagName === 'input' || tagName === 'textarea') return true;
if (activeElement.getAttribute('data-a-target') === 'chat-input') return true;
return false;
}
function getSlowModeDuration() {
const now = Date.now();
if (now - lastSlowModeCheck < 10000 && slowModeTimer > 0) return slowModeDuration;
lastSlowModeCheck = now;
const slowModeSelectors = [
'.chat-room__slow-mode-notice',
'[data-test-selector="slow-mode-notice"]',
'.slow-mode-notice',
'.chat-slow-mode-notice',
'[data-a-target="chat-slow-mode-notice"]'
];
for (const selector of slowModeSelectors) {
const element = document.querySelector(selector);
if (element && element.isConnected) {
const text = element.textContent || '';
const match = text.match(/(\d+)\s*(?:секунд|сек|second|sec|s)/i);
if (match) {
slowModeDuration = parseInt(match[1]) * 1000;
slowModeTimer = now;
return slowModeDuration;
}
}
}
try {
const chatContainer = document.querySelector('.chat-room, [data-a-target="chat-pane"]');
if (chatContainer) {
const fiberKey = Object.keys(chatContainer).find(key =>
key.startsWith('__reactFiber') || key.startsWith('__reactInternalInstance')
);
if (fiberKey) {
let fiber = chatContainer[fiberKey];
while (fiber) {
if (fiber.memoizedProps?.slowModeDuration) {
slowModeDuration = fiber.memoizedProps.slowModeDuration * 1000;
slowModeTimer = now;
return slowModeDuration;
}
if (fiber.memoizedState?.slowModeDuration) {
slowModeDuration = fiber.memoizedState.slowModeDuration * 1000;
slowModeTimer = now;
return slowModeDuration;
}
fiber = fiber.return;
}
}
}
} catch (e) {}
return 0;
}
function getOptimalDelay(baseDelay) {
const smDuration = getSlowModeDuration();
if (smDuration > 0) return smDuration + 500;
return getRandomDelay(baseDelay);
}
function simulateTextarea(inputElement, text) {
inputElement.focus();
inputElement.value = text;
inputElement.dispatchEvent(new InputEvent('input', {
bubbles: true, cancelable: true, inputType: 'insertText', data: text
}));
inputElement.dispatchEvent(new Event('change', { bubbles: true }));
}
function simulateSlateEditor(inputElement, text) {
inputElement.focus();
// Выделяем всё содержимое
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(inputElement);
selection.removeAllRanges();
selection.addRange(range);
// Вставляем новый текст — Slate заменит выделенное
inputElement.dispatchEvent(new InputEvent('beforeinput', {
bubbles: true, cancelable: true,
inputType: 'insertText', data: text
}));
inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
}
function simulateTyping(inputElement, text) {
if (inputElement.tagName.toLowerCase() === 'textarea' ||
inputElement.tagName.toLowerCase() === 'input') {
simulateTextarea(inputElement, text);
} else {
simulateSlateEditor(inputElement, text);
}
}
function findChatInput() {
const textarea = document.querySelector('textarea[data-a-target="chat-input"]');
if (textarea && textarea.isConnected) return textarea;
const input = document.querySelector('input[data-a-target="chat-input"]');
if (input && input.isConnected) return input;
const slateEditor = document.querySelector('[data-a-target="chat-input"][data-slate-editor="true"]');
if (slateEditor && slateEditor.isConnected) return slateEditor;
const fallback = document.querySelector('[data-a-target="chat-input"][contenteditable="true"]');
if (fallback && fallback.isConnected) return fallback;
const chatSection = document.querySelector('.chat-room, .chat-room__content, [data-a-target="chat-pane"], .stream-chat');
if (chatSection) {
const elem = chatSection.querySelector('textarea[data-a-target="chat-input"], [data-slate-editor="true"]');
if (elem) return elem;
}
return null;
}
function findSendButton() {
const button = document.querySelector('[data-a-target="chat-send-button"]');
if (button && !button.disabled && button.isConnected) return button;
return null;
}
function sendViaEnter(inputElement) {
['keydown', 'keypress', 'keyup'].forEach(type => {
inputElement.dispatchEvent(new KeyboardEvent(type, {
key: 'Enter', code: 'Enter', keyCode: 13, which: 13,
bubbles: true, cancelable: true, composed: true
}));
});
}
function getRandomDelay(baseDelay) {
const variation = baseDelay * 0.25;
return Math.max(400, Math.floor(baseDelay + Math.random() * variation * 2 - variation));
}
function sendMessage(text) {
return new Promise((resolve) => {
const inputElement = findChatInput();
if (!inputElement || !inputElement.isConnected) {
console.error('[Twitch Commands] Поле ввода не найдено');
resolve(false);
return;
}
// Вставляем текст (выделение + замена для Slate)
simulateTyping(inputElement, text);
// Отправляем с небольшой задержкой для обработки ввода
setTimeout(() => {
const sendButton = findSendButton();
if (sendButton) {
sendButton.click();
console.log(`[Twitch Commands] Отправлено: ${text}`);
} else {
sendViaEnter(inputElement);
console.log(`[Twitch Commands] Отправлено через Enter: ${text}`);
}
resolve(true);
}, 150); // увеличено до 150мс для стабильности
});
}
async function sendAllCommands() {
console.log(`[Twitch Commands] Отправка команд: ${COMMANDS.join(', ')}`);
for (let i = 0; i < COMMANDS.length; i++) {
const command = COMMANDS[i];
await sendMessage(command);
if (i < COMMANDS.length - 1) {
const delay = getOptimalDelay(BASE_DELAY);
const slowModeInfo = getSlowModeDuration() > 0
? ` (медленный режим: ${getSlowModeDuration()}мс)`
: '';
console.log(`[Twitch Commands] Ожидание ${delay}мс...${slowModeInfo}`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
console.log('[Twitch Commands] Все команды отправлены!');
}
function isChatVisible() {
const chatElement = document.querySelector('.chat-room, .stream-chat, [data-a-target="chat-pane"]');
if (!chatElement) return false;
const rect = chatElement.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function onKeyDown(event) {
if ((event.key === BIND_KEY || event.code === BIND_KEY) &&
!event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey) {
if (isTyping()) return;
if (!isChatVisible()) return;
event.preventDefault();
event.stopPropagation();
sendAllCommands();
}
}
function waitForChat() {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (findChatInput() && findSendButton() && isChatVisible()) {
clearInterval(checkInterval);
const sm = getSlowModeDuration();
if (sm > 0) console.log(`[Twitch Commands] Медленный режим: ${sm}мс`);
console.log('[Twitch Commands] Чат готов!');
resolve(true);
}
}, 500);
setTimeout(() => { clearInterval(checkInterval); resolve(false); }, 30000);
});
}
async function init() {
await waitForChat();
document.addEventListener('keydown', onKeyDown, true);
window.addEventListener('keydown', onKeyDown, true);
console.log(`[Twitch Commands] Жми ${BIND_KEY} для: ${COMMANDS.join(', ')}`);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();