Twitch HeLLSocials

Отправляет команды !дис, !тг, !ютуб в чат Twitch по нажатию горячей клавиши!

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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

})();