Human Auto Typer

Works anywhere! Type like a pro with "start-now" trigger, stylish UI, manual controls & Shift x3 to stop. Try it!

// ==UserScript==
// @name         Human Auto Typer
// @namespace    http://tampermonkey.net/
// @version      4.5
// @description  Works anywhere! Type like a pro with "start-now" trigger, stylish UI, manual controls & Shift x3 to stop. Try it!
// @author       Koolkars (Felix)
// @match        *://*/*
// @grant        none
// @icon         https://static.vecteezy.com/system/resources/previews/010/750/505/original/typing-icon-design-free-vector.jpg
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    let typingInterval = 300;
    let typingText = '';
    let typingTarget = null;
    let typingInProgress = false;
    let typingTimeoutId = null;
    let shiftPressTimes = [];

    // Create toggle icon
    const toggleIcon = document.createElement('div');
    toggleIcon.id = 'typingToggleIcon';
    toggleIcon.title = 'Simulated Typing Panel';
    toggleIcon.style.position = 'fixed';
    toggleIcon.style.top = '50%';
    toggleIcon.style.right = '0';
    toggleIcon.style.transform = 'translateY(-50%)';
    toggleIcon.style.width = '40px';
    toggleIcon.style.height = '40px';
    toggleIcon.style.background = 'linear-gradient(135deg, #6ab7ff, #4285f4)';
    toggleIcon.style.borderTopLeftRadius = '20px';
    toggleIcon.style.borderBottomLeftRadius = '20px';
    toggleIcon.style.cursor = 'pointer';
    toggleIcon.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
    toggleIcon.style.display = 'flex';
    toggleIcon.style.alignItems = 'center';
    toggleIcon.style.justifyContent = 'center';
    toggleIcon.style.zIndex = '999999';
    toggleIcon.style.userSelect = 'none';


    toggleIcon.innerHTML = `
        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="white" viewBox="0 0 24 24">
            <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1.003 1.003 0 0 0 0-1.42l-2.34-2.34a1.003 1.003 0 0 0-1.42 0L14.13 4.94l3.75 3.75 2.83-2.83z"/>
        </svg>`;

    document.body.appendChild(toggleIcon);

    const ui = document.createElement('div');
    ui.id = 'typingUI';
    ui.style.position = 'fixed';
    ui.style.top = '50%';
    ui.style.right = '-360px';
    ui.style.transform = 'translateY(-50%)';
    ui.style.width = '350px';
    ui.style.background = 'white';
    ui.style.borderRadius = '20px 0 0 20px';
    ui.style.boxShadow = '0 8px 24px rgba(66, 133, 244, 0.3)';
    ui.style.padding = '20px 25px 25px 25px';
    ui.style.fontFamily = "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif";
    ui.style.color = '#1a1a1a';
    ui.style.zIndex = '999998';
    ui.style.transition = 'right 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
    ui.style.userSelect = 'none';
    ui.style.display = 'flex';
    ui.style.flexDirection = 'column';
    ui.style.gap = '15px';

    ui.innerHTML = `
        <h2 style="margin:0; font-weight: 600; color: #4285f4;">Human Auto Typer</h2>

        <label for="pasteArea" style="font-weight: 500;">Paste Text:</label>
        <textarea id="pasteArea" rows="6" placeholder="Paste text here..." style="width: 100%; font-size: 14px; padding: 10px; border: 1.8px solid #ddd; border-radius: 10px; resize: vertical; outline-offset: 2px; outline-color: #4285f4;"></textarea>

        <label for="wpmInput" style="font-weight: 500;">Words Per Minute (WPM):</label>
        <input id="wpmInput" type="number" min="1" value="40" style="width: 70px; padding: 6px 8px; font-size: 14px; border-radius: 8px; border: 1.5px solid #ddd; outline-offset: 2px; outline-color: #4285f4;">

        <div style="display: flex; justify-content: flex-start; gap: 12px; margin-top: 10px;">
            <button id="startTypingBtn" style="background: #4285f4; border: none; color: white; padding: 10px 18px; border-radius: 12px; font-weight: 600; cursor: pointer; box-shadow: 0 2px 8px rgba(66, 133, 244, 0.5); transition: background 0.3s ease;">
                Start
            </button>
            <button id="stopTypingBtn" style="background: #f44336; border: none; color: white; padding: 10px 18px; border-radius: 12px; font-weight: 600; cursor: pointer; box-shadow: 0 2px 8px rgba(244, 67, 54, 0.5); transition: background 0.3s ease;">
                Stop
            </button>
        </div>

        <small style="color: #666;">Type <code>start-now</code> in an input or editable field to trigger typing.<br>Press <b>Shift</b> 3 times quickly to stop typing.</small>

        <div style="font-size: 10px; color: #999; text-align: right; margin-top: 10px;">made by koolkars</div>
    `;

    document.body.appendChild(ui);

    let panelOpen = false;
    toggleIcon.addEventListener('click', () => {
        panelOpen = !panelOpen;
        ui.style.right = panelOpen ? '0' : '-360px';
        toggleIcon.style.background = panelOpen
            ? 'linear-gradient(135deg, #2a6dfd, #1a4ed8)'
            : 'linear-gradient(135deg, #6ab7ff, #4285f4)';
    });

    function getIntervalFromWPM(wpm) {
        const charsPerMinute = wpm * 5;
        return 60000 / charsPerMinute;
    }

    function simulateTyping(text, element, interval) {
        let i = 0;
        typingInProgress = true;

        function typeNextChar() {
            if (!typingInProgress) return;
            if (i >= text.length) {
                typingInProgress = false;
                typingTimeoutId = null;
                return;
            }

            const char = text[i++];
            if (element.isContentEditable) {
                const sel = window.getSelection();
                if (!sel.rangeCount) {
                    typingInProgress = false;
                    typingTimeoutId = null;
                    return;
                }
                const range = sel.getRangeAt(0);
                range.deleteContents();
                range.insertNode(document.createTextNode(char));
                range.collapse(false);
                sel.removeAllRanges();
                sel.addRange(range);
            } else {
                const start = element.selectionStart;
                const end = element.selectionEnd;
                const before = element.value.slice(0, start);
                const after = element.value.slice(end);
                element.value = before + char + after;
                element.selectionStart = element.selectionEnd = start + 1;
            }

            typingTimeoutId = setTimeout(typeNextChar, interval);
        }

        typeNextChar();
    }

    function stopTyping() {
        if (typingInProgress) {
            typingInProgress = false;
            if (typingTimeoutId) {
                clearTimeout(typingTimeoutId);
                typingTimeoutId = null;
            }
            console.log('Typing stopped.');
        }
    }

    document.getElementById('startTypingBtn').addEventListener('click', () => {
        if (typingInProgress) {
            alert('Typing already in progress!');
            return;
        }

        const text = document.getElementById('pasteArea').value.trim();
        if (!text) {
            alert('Please paste some text first!');
            return;
        }

        const wpm = parseInt(document.getElementById('wpmInput').value);
        if (isNaN(wpm) || wpm < 1) {
            alert('Please enter a valid WPM (1 or more).');
            return;
        }

        const interval = getIntervalFromWPM(wpm);
        const activeEl = document.activeElement;
        if (!activeEl ||
            (!activeEl.isContentEditable &&
                activeEl.tagName !== 'TEXTAREA' &&
                !(activeEl.tagName === 'INPUT' && ['text', 'search', 'email', 'url', 'tel', 'password'].includes(activeEl.type)))) {
            alert('Please focus a text input, textarea, or editable element to start typing.');
            return;
        }

        typingText = text;
        typingInterval = interval;
        typingTarget = activeEl;
        simulateTyping(typingText, typingTarget, typingInterval);
    });

    document.getElementById('stopTypingBtn').addEventListener('click', () => {
        stopTyping();
    });

    const triggerPhrase = 'start-now';
    const triggerLength = triggerPhrase.length;

    function attachInputListener(el) {
        if (!el || el._hasTypingListener) return;
        el._hasTypingListener = true;

        el.addEventListener('input', () => {
            if (typingInProgress) return;
            let content = el.isContentEditable ? el.textContent || '' : el.value || '';
            if (content.length < triggerLength) return;

            if (content.slice(-triggerLength) === triggerPhrase) {
                if (el.isContentEditable) {
                    el.textContent = content.slice(0, -triggerLength);
                    const sel = window.getSelection();
                    const range = document.createRange();
                    range.selectNodeContents(el);
                    range.collapse(false);
                    sel.removeAllRanges();
                    sel.addRange(range);
                } else {
                    el.value = content.slice(0, -triggerLength);
                    el.selectionStart = el.selectionEnd = el.value.length;
                }

                const text = document.getElementById('pasteArea').value.trim();
                if (!text) return alert('Please paste some text in the panel first!');
                const wpm = parseInt(document.getElementById('wpmInput').value);
                if (isNaN(wpm) || wpm < 1) return alert('Please enter a valid WPM (1 or more) in the panel.');

                typingText = text;
                typingInterval = getIntervalFromWPM(wpm);
                typingTarget = el;
                simulateTyping(typingText, typingTarget, typingInterval);
            }
        });
    }

    function attachListenersToAllInputs() {
        const inputs = [...document.querySelectorAll('input[type=text], input[type=search], input[type=email], input[type=url], input[type=tel], input[type=password], textarea')];
        inputs.forEach(attachInputListener);
        const editables = [...document.querySelectorAll('[contenteditable="true"], [contenteditable=""]')];
        editables.forEach(attachInputListener);
    }

    attachListenersToAllInputs();

    const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach((node) => {
                    if (!(node instanceof HTMLElement)) return;
                    if (node.matches('input[type=text], input[type=search], input[type=email], input[type=url], input[type=tel], input[type=password], textarea')) {
                        attachInputListener(node);
                    }
                    if (node.isContentEditable) attachInputListener(node);
                    node.querySelectorAll('input[type=text], input[type=search], input[type=email], input[type=url], input[type=tel], input[type=password], textarea').forEach(attachInputListener);
                    node.querySelectorAll('[contenteditable="true"], [contenteditable=""]').forEach(attachInputListener);
                });
            }
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });

    window.addEventListener('keydown', (e) => {
        if (e.key === 'Shift') {
            const now = Date.now();
            shiftPressTimes = shiftPressTimes.filter(t => now - t < 1000);
            shiftPressTimes.push(now);
            if (shiftPressTimes.length >= 3) {
                stopTyping();
                shiftPressTimes = [];
            }
        }
    });

})();