Human-Typer (Enhanced) - Google Docs & Slides

Types text in a human-like manner with adjustable speed, typos, and duration.

当前为 2024-09-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Human-Typer (Enhanced) - Google Docs & Slides
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.5
  5. // @description Types text in a human-like manner with adjustable speed, typos, and duration.
  6. // @author ∫(Ace)³dx
  7. // @match https://docs.google.com/*
  8. // @icon https://i.imgur.com/z2gxKWZ.png
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. if (window.location.href.includes("docs.google.com/document/d") || window.location.href.includes("docs.google.com/presentation/d")) {
  14. console.log("Document opened, Human-Typer available!");
  15.  
  16. // Create the "Human-Typer" button
  17. const humanTyperButton = document.createElement("div");
  18. humanTyperButton.textContent = "Human-Typer";
  19. humanTyperButton.classList.add("menu-button", "goog-control", "goog-inline-block");
  20. humanTyperButton.style.userSelect = "none";
  21. humanTyperButton.style.cursor = "pointer";
  22. humanTyperButton.style.padding = "10px";
  23. humanTyperButton.style.backgroundColor = "#1a73e8";
  24. humanTyperButton.style.color = "white";
  25. humanTyperButton.style.borderRadius = "4px";
  26. humanTyperButton.id = "human-typer-button";
  27.  
  28. // Create the "Stop" button
  29. const stopButton = document.createElement("div");
  30. stopButton.textContent = "Stop";
  31. stopButton.classList.add("menu-button", "goog-control", "goog-inline-block");
  32. stopButton.style.userSelect = "none";
  33. stopButton.style.color = "red";
  34. stopButton.style.cursor = "pointer";
  35. stopButton.style.padding = "10px";
  36. stopButton.style.borderRadius = "4px";
  37. stopButton.id = "stop-button";
  38. stopButton.style.display = "none";
  39.  
  40. // Insert the buttons into the page
  41. const helpMenu = document.getElementById("docs-help-menu");
  42. if (helpMenu) {
  43. helpMenu.parentNode.insertBefore(humanTyperButton, helpMenu);
  44. humanTyperButton.parentNode.insertBefore(stopButton, humanTyperButton.nextSibling);
  45. } else {
  46. console.error("Help menu not found.");
  47. }
  48.  
  49. let cancelTyping = false;
  50. let typingInProgress = false;
  51. let lowerBoundWPM = 30; // Default lower bound WPM
  52. let upperBoundWPM = 60; // Default upper bound WPM
  53. let typingDuration = 1; // Default duration in hours
  54. let introduceTypos = false;
  55. let fluctuateSpeed = false;
  56.  
  57. // Function to create and show the overlay
  58. function showOverlay() {
  59. return new Promise((resolve) => {
  60. const overlay = document.createElement("div");
  61. overlay.style.position = "fixed";
  62. overlay.style.top = "50%";
  63. overlay.style.left = "50%";
  64. overlay.style.transform = "translate(-50%, -50%)";
  65. overlay.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
  66. overlay.style.padding = "20px";
  67. overlay.style.borderRadius = "8px";
  68. overlay.style.boxShadow = "0px 2px 10px rgba(0, 0, 0, 0.1)";
  69. overlay.style.zIndex = "9999";
  70. overlay.style.display = "flex";
  71. overlay.style.flexDirection = "column";
  72. overlay.style.alignItems = "center";
  73. overlay.style.width = "320px";
  74.  
  75. const textField = document.createElement("textarea");
  76. textField.rows = "5";
  77. textField.cols = "40";
  78. textField.placeholder = "Enter your text...";
  79. textField.style.marginBottom = "10px";
  80. textField.style.width = "100%";
  81. textField.style.padding = "8px";
  82. textField.style.border = "1px solid #ccc";
  83. textField.style.borderRadius = "4px";
  84. textField.style.resize = "vertical";
  85.  
  86. const description = document.createElement("p");
  87. description.textContent = "Configure typing options below:";
  88. description.style.fontSize = "14px";
  89. description.style.marginBottom = "15px";
  90.  
  91. const durationLabel = document.createElement("label");
  92. durationLabel.textContent = "Duration (hours): ";
  93. const durationSelect = document.createElement("select");
  94. [1, 2, 3, 4].forEach((val) => {
  95. const option = document.createElement("option");
  96. option.value = val;
  97. option.textContent = `${val} hr${val > 1 ? 's' : ''}`;
  98. durationSelect.appendChild(option);
  99. });
  100. durationSelect.value = typingDuration;
  101.  
  102. const randomDelayLabel = document.createElement("div");
  103. randomDelayLabel.style.marginBottom = "5px";
  104.  
  105. const lowerBoundLabel = document.createElement("label");
  106. lowerBoundLabel.textContent = "Lower Bound (WPM): ";
  107. const lowerBoundInput = document.createElement("input");
  108. lowerBoundInput.type = "number";
  109. lowerBoundInput.min = "0";
  110. lowerBoundInput.value = lowerBoundWPM;
  111. lowerBoundInput.style.marginRight = "10px";
  112. lowerBoundInput.style.padding = "6px";
  113. lowerBoundInput.style.border = "1px solid #ccc";
  114. lowerBoundInput.style.borderRadius = "4px";
  115.  
  116. const upperBoundLabel = document.createElement("label");
  117. upperBoundLabel.textContent = "Upper Bound (WPM): ";
  118. const upperBoundInput = document.createElement("input");
  119. upperBoundInput.type = "number";
  120. upperBoundInput.min = "0";
  121. upperBoundInput.value = upperBoundWPM;
  122. upperBoundInput.style.marginRight = "10px";
  123. upperBoundInput.style.padding = "6px";
  124. upperBoundInput.style.border = "1px solid #ccc";
  125. upperBoundInput.style.borderRadius = "4px";
  126.  
  127. const typoCheckbox = document.createElement("input");
  128. typoCheckbox.type = "checkbox";
  129. typoCheckbox.id = "typo-checkbox";
  130. const typoLabel = document.createElement("label");
  131. typoLabel.textContent = "Introduce typos";
  132. typoLabel.htmlFor = "typo-checkbox";
  133. typoLabel.style.marginRight = "10px";
  134.  
  135. const fluctuationCheckbox = document.createElement("input");
  136. fluctuationCheckbox.type = "checkbox";
  137. fluctuationCheckbox.id = "fluctuation-checkbox";
  138. const fluctuationLabel = document.createElement("label");
  139. fluctuationLabel.textContent = "Fluctuate speed";
  140. fluctuationLabel.htmlFor = "fluctuation-checkbox";
  141. fluctuationLabel.style.marginRight = "10px";
  142.  
  143. const confirmButton = document.createElement("button");
  144. confirmButton.textContent = "Confirm";
  145. confirmButton.style.padding = "8px 16px";
  146. confirmButton.style.backgroundColor = "#1a73e8";
  147. confirmButton.style.color = "white";
  148. confirmButton.style.border = "none";
  149. confirmButton.style.borderRadius = "4px";
  150. confirmButton.style.cursor = "pointer";
  151. confirmButton.style.transition = "background-color 0.3s";
  152.  
  153. overlay.appendChild(description);
  154. overlay.appendChild(textField);
  155. overlay.appendChild(durationLabel);
  156. overlay.appendChild(durationSelect);
  157. overlay.appendChild(randomDelayLabel);
  158. overlay.appendChild(lowerBoundLabel);
  159. overlay.appendChild(lowerBoundInput);
  160. overlay.appendChild(upperBoundLabel);
  161. overlay.appendChild(upperBoundInput);
  162. overlay.appendChild(document.createElement("br"));
  163. overlay.appendChild(typoCheckbox);
  164. overlay.appendChild(typoLabel);
  165. overlay.appendChild(fluctuationCheckbox);
  166. overlay.appendChild(fluctuationLabel);
  167. overlay.appendChild(document.createElement("br"));
  168. overlay.appendChild(confirmButton);
  169. document.body.appendChild(overlay);
  170.  
  171. const updateRandomDelayLabel = () => {
  172. const charCount = textField.value.length;
  173. const etaLowerBound = Math.ceil((charCount * 60) / (parseInt(lowerBoundInput.value) * 5));
  174. const etaUpperBound = Math.ceil((charCount * 60) / (parseInt(upperBoundInput.value) * 5));
  175. randomDelayLabel.textContent = `ETA: ${etaLowerBound} - ${etaUpperBound} minutes`;
  176. };
  177.  
  178. const handleConfirmClick = () => {
  179. const userInput = textField.value.trim();
  180. lowerBoundWPM = parseInt(lowerBoundInput.value);
  181. upperBoundWPM = parseInt(upperBoundInput.value);
  182. typingDuration = parseInt(durationSelect.value);
  183. introduceTypos = typoCheckbox.checked;
  184. fluctuateSpeed = fluctuationCheckbox.checked;
  185.  
  186. if (userInput === "" || isNaN(lowerBoundWPM) || isNaN(upperBoundWPM) || lowerBoundWPM < 0 || upperBoundWPM < lowerBoundWPM) {
  187. document.body.removeChild(overlay);
  188. return;
  189. }
  190.  
  191. typingInProgress = true; // Typing has started
  192. stopButton.style.display = "inline"; // Show the stop button
  193. document.body.removeChild(overlay);
  194. resolve({ userInput });
  195. };
  196.  
  197. confirmButton.addEventListener("click", handleConfirmClick);
  198. textField.addEventListener("input", updateRandomDelayLabel);
  199. lowerBoundInput.addEventListener("input", updateRandomDelayLabel);
  200. upperBoundInput.addEventListener("input", updateRandomDelayLabel);
  201. });
  202. }
  203.  
  204. humanTyperButton.addEventListener("mouseenter", () => {
  205. humanTyperButton.classList.add("goog-control-hover");
  206. });
  207.  
  208. humanTyperButton.addEventListener("mouseleave", () => {
  209. humanTyperButton.classList.remove("goog-control-hover");
  210. });
  211.  
  212. stopButton.addEventListener("mouseenter", () => {
  213. stopButton.classList.add("goog-control-hover");
  214. });
  215.  
  216. stopButton.addEventListener("mouseleave", () => {
  217. stopButton.classList.remove("goog-control-hover");
  218. });
  219.  
  220. humanTyperButton.addEventListener("click", async () => {
  221. if (typingInProgress) {
  222. console.log("Typing in progress, please wait...");
  223. return;
  224. }
  225.  
  226. cancelTyping = false;
  227. stopButton.style.display = "none"; // Hide the stop button
  228.  
  229. const { userInput } = await showOverlay();
  230.  
  231. if (userInput !== "") {
  232. const input = document.querySelector(".docs-texteventtarget-iframe").contentDocument.activeElement;
  233.  
  234. async function simulateTyping(inputElement, char, delay) {
  235. return new Promise((resolve) => {
  236. if (cancelTyping) {
  237. stopButton.style.display = "none";
  238. console.log("Typing cancelled");
  239. resolve();
  240. return;
  241. }
  242.  
  243. setTimeout(() => {
  244. let eventObj;
  245. if (char === "\n") {
  246. eventObj = new KeyboardEvent("keydown", {
  247. bubbles: true,
  248. key: "Enter",
  249. code: "Enter",
  250. keyCode: 13,
  251. which: 13,
  252. charCode: 13,
  253. });
  254. } else {
  255. eventObj = new KeyboardEvent("keypress", {
  256. bubbles: true,
  257. key: char,
  258. charCode: char.charCodeAt(0),
  259. keyCode: char.charCodeAt(0),
  260. which: char.charCodeAt(0),
  261. });
  262. }
  263.  
  264. inputElement.dispatchEvent(eventObj);
  265. console.log(`Typed: ${char}, Delay: ${delay}ms`);
  266. resolve();
  267. }, delay);
  268. });
  269. }
  270.  
  271. async function typeStringWithRandomDelay(inputElement, string) {
  272. const words = string.split(" ");
  273. const totalChars = string.length;
  274. const startTime = Date.now();
  275. const totalDuration = typingDuration * 3600000; // Convert hours to milliseconds
  276.  
  277. for (let i = 0; i < words.length; i++) {
  278. if (cancelTyping) {
  279. stopButton.style.display = "none";
  280. console.log("Typing cancelled");
  281. return;
  282. }
  283.  
  284. const word = words[i];
  285. const baseWPM = (Math.random() * (upperBoundWPM - lowerBoundWPM)) + lowerBoundWPM;
  286. const delayPerWord = (60 / baseWPM) * 60000; // Convert WPM to milliseconds
  287.  
  288. for (let j = 0; j < word.length; j++) {
  289. const char = word[j];
  290. const typoChar = introduceTypos && Math.random() < 0.05 ? String.fromCharCode(Math.random() * 26 + 65) : char; // Random typo
  291. const randomDelay = fluctuateSpeed ? delayPerWord * (0.5 + Math.random()) : delayPerWord;
  292.  
  293. await simulateTyping(inputElement, typoChar, randomDelay);
  294. }
  295.  
  296. if (i < words.length - 1) {
  297. await simulateTyping(inputElement, " ", 0); // Space between words
  298. }
  299.  
  300. // Check if typing exceeds the duration
  301. if (Date.now() - startTime > totalDuration) {
  302. cancelTyping = true;
  303. break;
  304. }
  305. }
  306.  
  307. typingInProgress = false; // Typing has finished
  308. stopButton.style.display = "none"; // Hide the stop button
  309. }
  310.  
  311. typeStringWithRandomDelay(input, userInput);
  312. }
  313. });
  314.  
  315. } else {
  316. console.log("Document not open, Human-Typer not available.");
  317. }