您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Simulates slow, natural typing to automatically generate content in Google Docs over time
// ==UserScript== // @name TypeLapse // @namespace https://solanaceae.xyz/ // @version 1.3 // @description Simulates slow, natural typing to automatically generate content in Google Docs over time // @author Solanaceae // @match https://docs.google.com/* // @license GPLv3 // @icon https://raw.githubusercontent.com/0xSolanaceae/TypeLapse/refs/heads/main/assets/favicon/favicon.svg // ==/UserScript== (function() { 'use strict'; if ( window.location.href.includes("docs.google.com/document/d") || window.location.href.includes("docs.google.com/presentation/d") ) { console.log("TypeLapse loaded!"); const TypeLapseButton = createButton("TypeLapse", "type-lapse-button"); const stopButton = createButton("Stop", "stop-button", "red"); const pauseButton = createButton("Pause", "pause-button", "orange"); const resumeButton = createButton("Resume", "resume-button", "green"); stopButton.style.display = "none"; pauseButton.style.display = "none"; resumeButton.style.display = "none"; stopButton.style.verticalAlign = "middle"; stopButton.style.lineHeight = "normal"; pauseButton.style.verticalAlign = "middle"; pauseButton.style.lineHeight = "normal"; resumeButton.style.verticalAlign = "middle"; resumeButton.style.lineHeight = "normal"; const helpMenu = document.getElementById("docs-help-menu"); if (helpMenu) { helpMenu.parentNode.appendChild(TypeLapseButton); TypeLapseButton.parentNode.appendChild(stopButton); TypeLapseButton.parentNode.appendChild(pauseButton); TypeLapseButton.parentNode.appendChild(resumeButton); } else { console.error("Help menu not found"); return; } let cancelTyping = false; let typingInProgress = false; let typingPaused = false; let lowerBoundValue = 60; let upperBoundValue = 120; let breakFrequency = 100; let breakDuration = 5000; let enableBreaks = false; let simulateErrors = false; TypeLapseButton.addEventListener("click", async () => { if (typingInProgress) { console.log("Typing in progress, please wait..."); return; } cancelTyping = false; typingPaused = false; stopButton.style.display = "none"; pauseButton.style.display = "none"; resumeButton.style.display = "none"; const { userInput } = await showOverlay(); if (userInput !== "") { const input = document.querySelector(".docs-texteventtarget-iframe") .contentDocument.activeElement; if (input) { typeStringWithRandomDelay(input, userInput); } else { console.error("Input element not found"); } } }); stopButton.addEventListener("click", () => { cancelTyping = true; stopButton.style.display = "none"; pauseButton.style.display = "none"; resumeButton.style.display = "none"; console.log("Typing cancelled"); }); pauseButton.addEventListener("click", () => { typingPaused = true; pauseButton.style.display = "none"; resumeButton.style.display = "inline"; console.log("Typing paused"); }); resumeButton.addEventListener("click", () => { typingPaused = false; resumeButton.style.display = "none"; pauseButton.style.display = "inline"; console.log("Typing resumed"); }); window.addEventListener("beforeunload", () => { cancelTyping = true; typingInProgress = false; }); window.addEventListener("load", () => { cancelTyping = false; typingInProgress = false; typingPaused = false; enableBreaks = false; simulateErrors = false; const enableBreaksCheckbox = document.querySelector("#type-lapse-overlay input[type='checkbox']"); if (enableBreaksCheckbox) { enableBreaksCheckbox.checked = false; } }); function createButton(text, id, color = "black") { const button = document.createElement("div"); button.textContent = text; button.classList.add("menu-button", "goog-control", "goog-inline-block"); button.style.userSelect = "none"; button.style.color = color; button.style.cursor = "pointer"; button.style.transition = "color 0.3s"; button.id = id; return button; } async function showOverlay() { const existingOverlay = document.getElementById("type-lapse-overlay"); if (existingOverlay) { document.body.removeChild(existingOverlay); return; } const overlay = document.createElement("div"); overlay.id = "type-lapse-overlay"; overlay.style = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #fff; padding: 20px; border-radius: 12px; box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.2); z-index: 9999; display: flex; flex-direction: column; align-items: center; width: 400px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; `; let isDragging = false; let offsetX, offsetY; overlay.addEventListener("mousedown", (e) => { if (e.target === overlay) { isDragging = true; offsetX = e.clientX - overlay.getBoundingClientRect().left; offsetY = e.clientY - overlay.getBoundingClientRect().top; } }); document.addEventListener("mousemove", (e) => { if (isDragging) { overlay.style.left = `${e.clientX - offsetX}px`; overlay.style.top = `${e.clientY - offsetY}px`; overlay.style.transform = "none"; } }); document.addEventListener("mouseup", () => { isDragging = false; }); const closeButton = document.createElement("button"); closeButton.textContent = "✖"; closeButton.style = ` position: absolute; top: 10px; right: 10px; background: none; border: none; font-size: 18px; cursor: pointer; color: #333; `; closeButton.addEventListener("click", () => { document.body.removeChild(overlay); }); const heading = document.createElement("h2"); heading.textContent = "TypeLapse"; heading.style = ` margin-bottom: 15px; font-size: 24px; color: #333; `; const textField = document.createElement("textarea"); textField.rows = "5"; textField.cols = "40"; textField.placeholder = "Enter your text..."; textField.style = ` margin-bottom: 10px; width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 8px; resize: vertical; font-size: 14px; `; const description = document.createElement("p"); description.textContent = "Keep this tab open; otherwise, the script will pause and resume when you return. Each character has a random typing delay between the lower (min) and upper (max) bounds in milliseconds. Lower values are faster."; description.style = ` font-size: 14px; margin-bottom: 15px; text-align: center; `; const randomDelayLabel = document.createElement("div"); randomDelayLabel.style.marginBottom = "5px"; const lowerBoundLabel = createLabel("Lower Bound (ms): ", lowerBoundValue); const upperBoundLabel = createLabel("Upper Bound (ms): ", upperBoundValue); const simulateErrorsLabel = createCheckboxLabel("Simulate Errors: ", simulateErrors); const enableBreaksLabel = createCheckboxLabel("Enable Breaks: ", enableBreaks); const breakOptionsContainer = document.createElement("div"); breakOptionsContainer.style.display = "none"; const breakDescription = document.createElement("p"); breakDescription.textContent = "Breaks will pause typing for a specified duration after a certain number of characters."; breakDescription.style = ` font-size: 14px; margin-bottom: 10px; text-align: center; `; const breakFrequencyLabel = createLabel("Break Frequency (chars): ", breakFrequency); const breakDurationLabel = createLabel("Break Duration (ms): ", breakDuration); breakOptionsContainer.append( breakDescription, breakFrequencyLabel, breakDurationLabel, ); enableBreaksLabel.querySelector("input").addEventListener("change", () => { breakOptionsContainer.style.display = enableBreaksLabel.querySelector("input").checked ? "block" : "none"; }); const confirmButton = document.createElement("button"); confirmButton.textContent = "Cancel"; confirmButton.style = ` padding: 10px 20px; background-color: #4d82f4; color: white; border: none; border-radius: 8px; cursor: pointer; transition: background-color 0.3s; font-size: 16px; margin-top: 10px; `; confirmButton.addEventListener("mouseover", () => { confirmButton.style.backgroundColor = "#3b6bd6"; }); confirmButton.addEventListener("mouseout", () => { confirmButton.style.backgroundColor = "#4d82f4"; }); overlay.append( closeButton, heading, description, textField, randomDelayLabel, lowerBoundLabel, upperBoundLabel, simulateErrorsLabel, enableBreaksLabel, breakOptionsContainer, confirmButton, ); document.body.appendChild(overlay); return new Promise((resolve) => { const updateRandomDelayLabel = () => { const charCount = textField.value.length; const etaLowerBound = Math.ceil( (charCount * parseInt(lowerBoundLabel.querySelector("input").value)) / 60000, ); const etaUpperBound = Math.ceil( (charCount * parseInt(upperBoundLabel.querySelector("input").value)) / 60000, ); randomDelayLabel.textContent = `ETA: ${etaLowerBound} - ${etaUpperBound} minutes`; }; confirmButton.addEventListener("click", () => { const userInput = textField.value.trim(); lowerBoundValue = parseInt( lowerBoundLabel.querySelector("input").value, ); upperBoundValue = parseInt( upperBoundLabel.querySelector("input").value, ); breakFrequency = parseInt( breakFrequencyLabel.querySelector("input").value, ); breakDuration = parseInt( breakDurationLabel.querySelector("input").value, ); enableBreaks = enableBreaksLabel.querySelector("input").checked; simulateErrors = simulateErrorsLabel.querySelector("input").checked; if (userInput === "") { document.body.removeChild(overlay); return; } if (isNaN(lowerBoundValue) || isNaN(upperBoundValue) || lowerBoundValue < 0 || upperBoundValue < lowerBoundValue || isNaN(breakFrequency) || breakFrequency <= 0 || isNaN(breakDuration) || breakDuration < 0) { return; } typingInProgress = true; stopButton.style.display = "inline"; pauseButton.style.display = "inline"; document.body.removeChild(overlay); resolve({ userInput, }); }); textField.addEventListener("input", () => { confirmButton.textContent = textField.value.trim() === "" ? "Cancel" : "Confirm"; updateRandomDelayLabel(); }); lowerBoundLabel .querySelector("input") .addEventListener("input", updateRandomDelayLabel); upperBoundLabel .querySelector("input") .addEventListener("input", updateRandomDelayLabel); }); } function createLabel(text, value) { return createInputLabel(text, "number", value); } function createCheckboxLabel(text, checked) { return createInputLabel(text, "checkbox", checked); } function createInputLabel(text, type, value) { const label = document.createElement("label"); label.textContent = text; label.style = ` display: flex; align-items: center; justify-content: space-between; width: 100%; margin-bottom: 10px; `; const input = document.createElement("input"); input.type = type; if (type === "number") { input.min = "0"; input.value = value; input.style = ` margin-left: 10px; padding: 6px; border: 1px solid #ccc; border-radius: 4px; width: 100px; `; } else if (type === "checkbox") { input.checked = value; input.style = ` margin-left: 10px; transform: scale(1.5); `; } label.appendChild(input); return label; } async function typeStringWithRandomDelay(inputElement, string) { const keyboardLayout = { a: ['q', 's', 'w', 'z'], b: ['v', 'n', 'g', 'h'], c: ['x', 'v', 'd', 'f'], d: ['s', 'f', 'e', 'r'], e: ['w', 'r', 'd', 'f'], f: ['d', 'g', 'r', 't'], g: ['f', 'h', 't', 'y'], h: ['g', 'j', 'y', 'u'], i: ['u', 'o', 'j', 'k'], j: ['h', 'k', 'u', 'i'], k: ['j', 'l', 'i', 'o'], l: ['k', 'o', 'p'], m: ['n', 'j', 'k'], n: ['b', 'm', 'h', 'j'], o: ['i', 'p', 'k', 'l'], p: ['o', 'l'], q: ['a', 'w', 's'], r: ['e', 't', 'd', 'f'], s: ['a', 'd', 'q', 'w'], t: ['r', 'y', 'f', 'g'], u: ['y', 'i', 'h', 'j'], v: ['c', 'b', 'f', 'g'], w: ['q', 'e', 'a', 's'], x: ['z', 'c', 's', 'd'], y: ['t', 'u', 'g', 'h'], z: ['a', 's', 'x'], }; for (let i = 0; i < string.length; i++) { if (cancelTyping) { stopButton.style.display = "none"; pauseButton.style.display = "none"; resumeButton.style.display = "none"; console.log("Typing cancelled"); break; } if (typingPaused) { await new Promise(resolve => { const interval = setInterval(() => { if (!typingPaused) { clearInterval(interval); resolve(); } }, 100); }); } const char = string[i]; const randomDelay = Math.floor(Math.random() * (upperBoundValue - lowerBoundValue + 1)) + lowerBoundValue; if (simulateErrors && Math.random() < 0.1) { const errorType = Math.random(); if (errorType < 0.33 && keyboardLayout[char]) { const neighbors = keyboardLayout[char]; const errorChar = neighbors[Math.floor(Math.random() * neighbors.length)]; await simulateTyping(inputElement, errorChar, randomDelay); await simulateTyping(inputElement, "\b", randomDelay); } else if (errorType < 0.66) { await simulateTyping(inputElement, char, randomDelay); await simulateTyping(inputElement, char, randomDelay); await simulateTyping(inputElement, "\b", randomDelay); } else { continue; } } await simulateTyping(inputElement, char, randomDelay); if (enableBreaks && (i + 1) % breakFrequency === 0) { console.log(`Taking a break for ${breakDuration}ms`); await new Promise(resolve => setTimeout(resolve, breakDuration)); } } typingInProgress = false; stopButton.style.display = "none"; pauseButton.style.display = "none"; resumeButton.style.display = "none"; } function simulateTyping(inputElement, char, delay) { return new Promise((resolve) => { if (cancelTyping) { stopButton.style.display = "none"; pauseButton.style.display = "none"; resumeButton.style.display = "none"; console.log("Typing cancelled"); resolve(); return; } setTimeout(() => { let eventObj; if (char === "\n") { eventObj = new KeyboardEvent("keydown", { bubbles: true, key: "Enter", code: "Enter", keyCode: 13, which: 13, charCode: 13, }); } else if (char === "\b") { eventObj = new KeyboardEvent("keydown", { bubbles: true, key: "Backspace", code: "Backspace", keyCode: 8, which: 8, charCode: 8, }); } else { eventObj = new KeyboardEvent("keypress", { bubbles: true, key: char, charCode: char.charCodeAt(0), keyCode: char.charCodeAt(0), which: char.charCodeAt(0), }); } inputElement.dispatchEvent(eventObj); console.log(`Typed: ${char}, Delay: ${delay}ms`); resolve(); }, delay); }); } } else { console.log("TypeLapse cannot find document"); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址