// ==UserScript== // @name 键盘输入模拟器 // @namespace https://github.com/GeLith/keyboard-simulator-web // @version 1.2.5 // @description 模拟键盘输入,解决网页不支持粘贴的问题。支持学习通 UEditor。 // @author GeLith // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @license MIT // @icon https://felixdd.top/img/1.ico // @homepage https://felixdd.top/keyboard.html // @supportURL https://github.com/GeLith/keyboard-simulator-web/issues // ==/UserScript== (function() { 'use strict'; var isTyping = false; var shouldStop = false; var ball = null; var panel = null; var VERSION = '1.2.5'; var isTopFrame = (window.top === window); if (!isTopFrame) return; var style = document.createElement('style'); style.textContent = '#ks-ball{position:fixed;top:20px;right:20px;width:44px;height:44px;background:#fff;border-radius:50%;box-shadow:0 4px 15px rgba(0,0,0,0.2);z-index:9999999;cursor:pointer;display:flex;align-items:center;justify-content:center;user-select:none;transition:transform .2s ease,box-shadow .2s ease;border:2px solid rgba(0,0,0,0.08)}#ks-ball:hover{transform:scale(1.1);box-shadow:0 6px 25px rgba(0,0,0,0.3)}#ks-ball:active{transform:scale(0.95)}#ks-ball-icon{font-size:20px;color:#667eea;line-height:1;pointer-events:none}#keyboard-simulator-panel{position:fixed;top:80px;right:20px;width:320px;background:#fff;border-radius:10px;box-shadow:0 6px 30px rgba(0,0,0,0.3);z-index:9999998;font-family:"Microsoft YaHei","Segoe UI",sans-serif;font-size:13px;overflow:hidden;border:1px solid #ddd;display:none;animation:ks-fadeIn .2s ease}@keyframes ks-fadeIn{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}#keyboard-simulator-panel.ks-show{display:block}#ks-panel-header{display:flex;align-items:center;gap:8px;padding:10px 14px;background:#fff;color:#333;user-select:none;cursor:move}#ks-panel-header span:first-child{font-size:16px;color:#667eea}#ks-panel-title{flex:1;font-weight:bold}#ks-close{background:none;border:none;color:#999;font-size:18px;cursor:pointer;padding:0;line-height:1;width:22px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:background .15s}#ks-close:hover{background:rgba(0,0,0,0.08)}#ks-panel-body{padding:14px}#ks-input{width:100%;height:80px;border:1px solid #ccc;border-radius:4px;padding:8px;font-size:13px;resize:vertical;margin-bottom:12px;font-family:inherit;box-sizing:border-box}#ks-input:focus{outline:none;border-color:#66afe9}.ks-settings{margin-bottom:12px}.ks-row{display:flex;align-items:center;margin-bottom:8px}.ks-row label{width:110px;font-size:12px;color:#555;flex-shrink:0}.ks-row input[type=number]{flex:1;padding:5px 8px;border:1px solid #ccc;border-radius:4px;font-size:13px;box-sizing:border-box}.ks-row input[type=number]:focus{outline:none;border-color:#66afe9}.ks-buttons{display:flex;gap:8px;margin-bottom:10px}.ks-buttons button{flex:1;padding:7px 12px;border:none;border-radius:5px;font-size:13px;cursor:pointer;font-weight:bold;transition:background .15s}#ks-start{background:#5cb85c;color:#fff}#ks-start:hover{background:#449d44}#ks-stop{background:#d9534f;color:#fff}#ks-stop:hover{background:#c9302c}#ks-stop:disabled{background:#ccc;cursor:not-allowed}.ks-btn-clear{width:100%;padding:8px 12px;margin-bottom:10px;background:#f0f0f0;color:#666;border:1px dashed #ccc;border-radius:5px;font-size:13px;cursor:pointer;transition:background .15s,color .15s;box-sizing:border-box}.ks-btn-clear:hover{background:#ffe6e6;color:#d9534f;border-color:#d9534f}#ks-souti{display:block;text-align:center;margin-bottom:10px;font-size:12px;color:#337ab7;text-decoration:none;transition:color .15s}#ks-souti:hover{color:#23527c;text-decoration:underline}#ks-status{font-size:12px;color:#888;text-align:center;padding:5px;background:#f5f5f5;border-radius:4px}#ks-panel-footer{padding:6px 14px;border-top:1px solid #eee;text-align:center;font-size:11px;color:#aaa}#ks-panel-footer a{color:#337ab7;text-decoration:none}#ks-panel-footer a:hover{text-decoration:underline}'; document.head.appendChild(style); createBall(); function createBall() { if (ball) return; ball = document.createElement('div'); ball.id = 'ks-ball'; ball.innerHTML = ''; document.body.appendChild(ball); ball.addEventListener('mousedown', function(e) { e.preventDefault(); var sx = e.clientX, sy = e.clientY, st = ball.offsetTop, sl = ball.offsetLeft, mv = false; function mm(ev) { if (Math.abs(ev.clientX - sx) > 3 || Math.abs(ev.clientY - sy) > 3) mv = true; if (mv) { ball.style.transition = 'none'; ball.style.right = 'auto'; ball.style.top = (st + ev.clientY - sy) + 'px'; ball.style.left = (sl + ev.clientX - sx) + 'px'; } } function mu() { document.removeEventListener('mousemove', mm); document.removeEventListener('mouseup', mu); if (!mv) togglePanel(); } document.addEventListener('mousemove', mm); document.addEventListener('mouseup', mu); }); } function togglePanel() { if (!panel) createPanel(); panel.classList.toggle('ks-show'); } function createPanel() { if (panel) return; panel = document.createElement('div'); panel.id = 'keyboard-simulator-panel'; panel.innerHTML = [ '
键盘输入模拟器
', '
', '', '
', '
', '
', '
', '
', '', '一键安装大学搜题酱', '
就绪 - 点击球图标可收起
', '
', '' ].join(''); document.body.appendChild(panel); var savedDelay = GM_getValue('ks-delay', '2'); var savedInterval = GM_getValue('ks-interval', '0.05'); document.getElementById('ks-delay').value = savedDelay; document.getElementById('ks-interval').value = savedInterval; document.getElementById('ks-delay').addEventListener('change', function() { GM_setValue('ks-delay', this.value); }); document.getElementById('ks-interval').addEventListener('change', function() { GM_setValue('ks-interval', this.value); }); document.getElementById('ks-close').addEventListener('click', function() { panel.classList.remove('ks-show'); }); var hdr = document.getElementById('ks-panel-header'); hdr.style.cursor = 'move'; hdr.addEventListener('mousedown', function(e) { if (e.target.id === 'ks-close') return; e.preventDefault(); var sx = e.clientX, sy = e.clientY, st = panel.offsetTop, sl = panel.offsetLeft, mv = false; function mv2(ev) { if (Math.abs(ev.clientX - sx) > 3 || Math.abs(ev.clientY - sy) > 3) mv = true; if (mv) { panel.style.transition = 'none'; panel.style.right = 'auto'; panel.style.top = (st + ev.clientY - sy) + 'px'; panel.style.left = (sl + ev.clientX - sx) + 'px'; } } function up() { document.removeEventListener('mousemove', mv2); document.removeEventListener('mouseup', up); } document.addEventListener('mousemove', mv2); document.addEventListener('mouseup', up); }); document.getElementById('ks-start').addEventListener('click', function() { var txt = document.getElementById('ks-input').value; if (!txt.trim()) { document.getElementById('ks-status').textContent = '请输入文字'; return; } isTyping = true; shouldStop = false; document.getElementById('ks-start').disabled = true; document.getElementById('ks-stop').disabled = false; document.getElementById('ks-status').textContent = '准备输入...'; startTyping({ text: txt, startDelay: parseFloat(document.getElementById('ks-delay').value) * 1000, charInterval: parseFloat(document.getElementById('ks-interval').value) * 1000 }); }); document.getElementById('ks-stop').addEventListener('click', function() { shouldStop = true; document.getElementById('ks-status').textContent = '正在停止...'; }); document.getElementById('ks-clear').addEventListener('click', function() { document.getElementById('ks-input').value = ''; document.getElementById('ks-input').focus(); }); } function findUeditorIframe() { var iframes = document.querySelectorAll('iframe'); for (var i = 0; i < iframes.length; i++) { try { var doc = iframes[i].contentDocument; if (!doc || !doc.body) continue; if (doc.body.getAttribute('contenteditable') === 'true' || doc.querySelector('.edui-body-container')) { return iframes[i]; } } catch(e) {} } return null; } function startTyping(settings) { var ueFrame = findUeditorIframe(); if (ueFrame) { typeViaExecCommand(ueFrame, settings); } else { typeLocally(settings); } } function typeViaExecCommand(iframe, settings) { var doc = iframe.contentDocument; var body = doc.body; body.focus(); var statusEl = document.getElementById('ks-status'); async function run() { await sleep(settings.startDelay); for (var i = 0; i < settings.text.length; i++) { if (shouldStop) { finish('已停止'); return; } if (statusEl) statusEl.textContent = '正在输入 (' + (i + 1) + '/' + settings.text.length + ')...'; var char = settings.text[i]; body.focus(); var sel = doc.getSelection(); sel.collapse(body, body.childNodes.length); doc.execCommand('insertText', false, char); if (i < settings.text.length - 1) await sleep(settings.charInterval); } finish('输入完成'); } function finish(msg) { if (statusEl) { statusEl.textContent = msg; document.getElementById('ks-start').disabled = false; document.getElementById('ks-stop').disabled = true; isTyping = false; } } run(); } function typeLocally(settings) { var statusEl = document.getElementById('ks-status'); async function run() { await sleep(settings.startDelay); for (var i = 0; i < settings.text.length; i++) { if (shouldStop) { if (statusEl) { statusEl.textContent = '已停止'; document.getElementById('ks-start').disabled = false; document.getElementById('ks-stop').disabled = true; isTyping = false; } return; } if (statusEl) statusEl.textContent = '正在输入 (' + (i + 1) + '/' + settings.text.length + ')...'; var char = settings.text[i]; var active = document.activeElement; if (active) { var tag = active.tagName ? active.tagName.toLowerCase() : ''; if (tag === 'input' || tag === 'textarea') { var start = active.selectionStart || active.value.length; active.value = active.value.substring(0, start) + char + active.value.substring(active.selectionEnd || start); active.selectionStart = active.selectionEnd = start + 1; active.dispatchEvent(new Event('input', { bubbles: true })); } else { active.focus(); try { document.execCommand('insertText', false, char); } catch(e) {} } } else { try { document.execCommand('insertText', false, char); } catch(e) {} } if (i < settings.text.length - 1) await sleep(settings.charInterval); } if (statusEl) { statusEl.textContent = '输入完成'; document.getElementById('ks-start').disabled = false; document.getElementById('ks-stop').disabled = true; isTyping = false; } } run(); } function sleep(ms) { return new Promise(function(r) { setTimeout(r, ms); }); } })();