您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Auto-send "!join" on Twitch chat (modern UI) using clipboard paste for full compatibility
// ==UserScript== // @name Twitch Auto !join Command (SlateJS Clipboard Injection) // @namespace http://tampermonkey.net/ // @version 2024.05.09 // @description Auto-send "!join" on Twitch chat (modern UI) using clipboard paste for full compatibility // @author XDWOLF // @match https://www.twitch.tv/yourtwitchchannel // @grant none // @run-at document-idle // @license MIT // ==/UserScript== (function() { 'use strict'; const JOIN_COMMAND = "!join"; const INTERVAL_MS = 15 * 60 * 1000; const CHAT_INPUT_SELECTOR = 'div[contenteditable="true"][role="textbox"][data-a-target="chat-input"]'; const SEND_BUTTON_SELECTOR = 'button[data-a-target="chat-send-button"]'; let sentOnce = false, interval = null; async function simulatePaste(element, value) { // Store old clipboard data let originalClipboard = ''; if (navigator.clipboard && navigator.clipboard.readText) { try { originalClipboard = await navigator.clipboard.readText(); } catch (e) {} } // Place value onto clipboard (if permissions allow) try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(value); } } catch (e) {} element.focus(); // Create a real paste event const pasteEvent = new ClipboardEvent('paste', { bubbles: true, cancelable: true, clipboardData: new DataTransfer() }); pasteEvent.clipboardData.setData('text/plain', value); element.dispatchEvent(pasteEvent); // Sometimes execCommand is still needed for full compatibility try { document.execCommand('paste'); } catch (e) {} // Restore clipboard if (originalClipboard && navigator.clipboard && navigator.clipboard.writeText) { setTimeout(() => { navigator.clipboard.writeText(originalClipboard); }, 100); } // Wait for paste and UI update await new Promise(res => setTimeout(res, 200)); return element.innerText.trim() === value; } function findEnabledChatInput() { return Array.from(document.querySelectorAll(CHAT_INPUT_SELECTOR)).find(el => el.offsetParent && el.getAttribute('aria-disabled') !== 'true' && el.getAttribute('aria-readonly') !== 'true' && !el.classList.contains('disabled') ); } function findEnabledSendButton() { let btn = document.querySelector(SEND_BUTTON_SELECTOR); return btn && !btn.disabled ? btn : null; } async function sendJoinCommand(initial = false) { if (initial && sentOnce) return; const chatInput = findEnabledChatInput(); const sendBtn = findEnabledSendButton(); if (!chatInput || !sendBtn) return; // Paste !join const ok = await simulatePaste(chatInput, JOIN_COMMAND); if (!ok) return; await new Promise(res => setTimeout(res, 200)); const btn = findEnabledSendButton(); if (btn) btn.click(); if (initial && !interval) { interval = setInterval(() => sendJoinCommand(false), INTERVAL_MS); } sentOnce = true; } function waitForChatThenSend() { let observer = new MutationObserver(() => { let i = findEnabledChatInput(), b = findEnabledSendButton(); if (i && b) { observer.disconnect(); sendJoinCommand(true); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); sendJoinCommand(true); }, 25000); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', waitForChatThenSend); } else { waitForChatThenSend(); } })();