Human-Typer (Enhanced v1.8 - Advanced Algo) - Google Docs & Slides

Types text human-like with draggable UI, info popup, realistic typos, and advanced rhythm algorithm.

  1. // ==UserScript==
  2. // @name Human-Typer (Enhanced v1.8 - Advanced Algo) - Google Docs & Slides
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.8
  5. // @description Types text human-like with draggable UI, info popup, realistic typos, and advanced rhythm algorithm.
  6. // @author ∫(Ace)³dx (Enhanced by Claude)
  7. // @match https://docs.google.com/*
  8. // @icon https://i.imgur.com/z2gxKWZ.png
  9. // @grant GM_addStyle
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. /* globals GM_addStyle */
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // --- Configuration ---
  19. const DEFAULT_LOWER_BOUND = 60;
  20. const DEFAULT_UPPER_BOUND = 140;
  21. const DEFAULT_TYPO_RATE_PERCENT = 5;
  22. const DEFAULT_ENABLE_TYPOS = true;
  23. const DEFAULT_USE_ADVANCED_ALGORITHM = true; // Enable advanced by default
  24.  
  25. // --- Basic Typo Config ---
  26. const MAX_TYPO_LENGTH = 3;
  27. const BASIC_TYPO_CHAR_DELAY_MS = 50;
  28. const BASIC_TYPO_PRE_BACKSPACE_DELAY_MS = 150;
  29. const BASIC_BACKSPACE_DELAY_MS = 90;
  30.  
  31. // --- Advanced Algorithm Config ---
  32. const ADV_SPACE_MULTIPLIER_MIN = 1.8; // Multiplier for delay after a space
  33. const ADV_SPACE_MULTIPLIER_MAX = 2.8;
  34. const ADV_WORD_END_MULTIPLIER_MIN = 1.1; // Slight pause at end of word (before space/punctuation)
  35. const ADV_WORD_END_MULTIPLIER_MAX = 1.5;
  36. const ADV_PUNCTUATION_MULTIPLIER = 1.3; // Extra delay after typing punctuation like . , ! ?
  37. const ADV_RANDOM_PAUSE_CHANCE = 0.02; // Chance (2%) of a brief extra pause mid-typing
  38. const ADV_RANDOM_PAUSE_MIN_MS = 150;
  39. const ADV_RANDOM_PAUSE_MAX_MS = 400;
  40. // Advanced Typo Correction Delays
  41. const ADV_TYPO_RECOGNITION_MIN_MS = 250; // Time between finishing wrong chars and starting backspace
  42. const ADV_TYPO_RECOGNITION_MAX_MS = 800;
  43. const ADV_BACKSPACE_DELAY_MS = 100; // Slightly slower backspacing in advanced mode
  44.  
  45. // --- State Variables ---
  46. let cancelTyping = false;
  47. let typingInProgress = false;
  48. let lowerBoundValue = DEFAULT_LOWER_BOUND;
  49. let upperBoundValue = DEFAULT_UPPER_BOUND;
  50. let enableTypos = DEFAULT_ENABLE_TYPOS;
  51. let typoRatePercentValue = DEFAULT_TYPO_RATE_PERCENT;
  52. let useAdvancedAlgorithm = DEFAULT_USE_ADVANCED_ALGORITHM; // State for advanced algo
  53. let overlayElement = null;
  54. let infoPopupElement = null;
  55.  
  56. // --- CSS Styles ---
  57. GM_addStyle(`
  58. /* ... (Previous styles remain largely the same) ... */
  59. .human-typer-overlay {
  60. position: fixed; background-color: rgba(255, 255, 255, 0.95); padding: 0;
  61. border-radius: 8px; box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.2); z-index: 10000;
  62. display: flex; flex-direction: column; width: 360px; /* Slightly wider */
  63. border: 1px solid #ccc; font-family: sans-serif; font-size: 14px; color: #333;
  64. }
  65. .human-typer-header {
  66. background-color: #f1f1f1; padding: 8px 12px; cursor: move; border-bottom: 1px solid #ccc;
  67. border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex;
  68. justify-content: space-between; align-items: center; user-select: none;
  69. }
  70. .human-typer-header-title { font-weight: bold; }
  71. .human-typer-info-icon {
  72. cursor: pointer; font-style: normal; font-weight: bold; color: #d93025; border: 1px solid #d93025;
  73. border-radius: 50%; width: 18px; height: 18px; display: inline-flex; justify-content: center;
  74. align-items: center; font-size: 12px; margin-left: 10px; background-color: white;
  75. }
  76. .human-typer-info-icon:hover { background-color: #fce8e6; }
  77. .human-typer-content { padding: 15px; display: flex; flex-direction: column; gap: 12px; }
  78. .human-typer-overlay textarea {
  79. width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; resize: vertical;
  80. box-sizing: border-box; min-height: 80px; font-family: inherit;
  81. }
  82. .human-typer-label { font-size: 13px; color: #555; }
  83. .human-typer-input-group { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
  84. .human-typer-input-group label {
  85. flex-basis: 115px; /* Adjusted label width */ text-align: right; flex-shrink: 0;
  86. }
  87. .human-typer-input-group input[type="number"] {
  88. width: 60px; padding: 6px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;
  89. }
  90. .human-typer-options-group { /* Group checkboxes */
  91. display: flex;
  92. flex-direction: column; /* Stack checkboxes */
  93. gap: 8px;
  94. margin-top: 5px;
  95. padding-left: 10px; /* Indent options */
  96. }
  97. .human-typer-checkbox-item { /* Style each checkbox line */
  98. display: flex;
  99. align-items: center;
  100. gap: 8px;
  101. }
  102. .human-typer-checkbox-item label {
  103. /* Labels next to checkbox don't need fixed width */
  104. flex-basis: auto;
  105. text-align: left;
  106. }
  107. .human-typer-checkbox-item input[type="number"] {
  108. width: 55px; padding: 6px; box-sizing: border-box;
  109. }
  110. .human-typer-checkbox-item .rate-label { /* Specific label for rate */
  111. margin-left: 10px;
  112. white-space: nowrap; /* Prevent wrapping */
  113. }
  114. .human-typer-eta { font-size: 12px; color: #777; min-height: 1.2em; text-align: center; }
  115. .human-typer-buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 10px; }
  116. .human-typer-button {
  117. padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer;
  118. transition: background-color 0.3s, color 0.3s; font-size: 14px;
  119. }
  120. .human-typer-confirm-button { background-color: #1a73e8; color: white; }
  121. .human-typer-confirm-button:hover:not(:disabled) { background-color: #1765cc; }
  122. .human-typer-confirm-button:disabled { opacity: 0.6; cursor: not-allowed; }
  123. .human-typer-cancel-button { background-color: #e0e0e0; color: #333; }
  124. .human-typer-cancel-button:hover { background-color: #d5d5d5; }
  125. .human-typer-info-popup {
  126. position: absolute; background-color: #fff; border: 1px solid #ccc; border-radius: 5px;
  127. box-shadow: 0 2px 5px rgba(0,0,0,0.2); padding: 12px; font-size: 13px;
  128. max-width: 340px; /* Slightly wider for more text */ z-index: 10001; color: #333;
  129. }
  130. .human-typer-info-popup p { margin-top: 0; margin-bottom: 0.7em; line-height: 1.4; }
  131. .human-typer-info-popup p:last-child { margin-bottom: 0; }
  132. .human-typer-info-popup strong { color: #111; }
  133. .human-typer-info-popup code { background-color: #f0f0f0; padding: 1px 3px; border-radius: 3px; font-size: 12px;}
  134. `);
  135.  
  136. // --- Main Logic ---
  137. function initializeScript() { console.log("Human-Typer initializing..."); insertButtons(); }
  138. function insertButtons() { /* ... (same as v1.6) ... */
  139. const helpMenu = document.getElementById("docs-help-menu");
  140. if (!helpMenu || document.getElementById("human-typer-button")) return;
  141. const humanTyperButton = createButton("Human-Typer", "human-typer-button");
  142. humanTyperButton.addEventListener("click", handleHumanTyperClick);
  143. const stopButton = createButton("Stop", "stop-button", true);
  144. stopButton.style.color = "red";
  145. stopButton.addEventListener("click", handleStopClick);
  146. helpMenu.parentNode.insertBefore(humanTyperButton, helpMenu);
  147. humanTyperButton.parentNode.insertBefore(stopButton, humanTyperButton.nextSibling);
  148. console.log("Human-Typer buttons inserted.");
  149. }
  150. function createButton(text, id, hidden = false) { /* ... (same as v1.6) ... */
  151. const button = document.createElement("div");
  152. button.textContent = text;
  153. button.classList.add("menu-button", "goog-control", "goog-inline-block");
  154. button.style.userSelect = "none"; button.style.cursor = "pointer"; button.style.transition = "background-color 0.2s, box-shadow 0.2s";
  155. button.id = id; if (hidden) button.style.display = "none";
  156. button.addEventListener("mouseenter", () => button.classList.add("goog-control-hover"));
  157. button.addEventListener("mouseleave", () => button.classList.remove("goog-control-hover"));
  158. return button;
  159. }
  160. function handleHumanTyperClick() { /* ... (same as v1.6, includes flash effect) ... */
  161. if (typingInProgress) {
  162. console.log("Typing already in progress.");
  163. const stopButton = document.getElementById("stop-button");
  164. if (stopButton) {
  165. stopButton.style.opacity = '0.5'; setTimeout(() => { stopButton.style.opacity = '1'; }, 150);
  166. setTimeout(() => { stopButton.style.opacity = '0.5'; }, 300); setTimeout(() => { stopButton.style.opacity = '1'; }, 450);
  167. } return;
  168. }
  169. if (!overlayElement) showOverlay(); else overlayElement.style.display = 'flex';
  170. }
  171. function handleStopClick() { /* ... (same as v1.6) ... */
  172. if (typingInProgress) {
  173. console.log("Stop requested."); cancelTyping = true;
  174. const stopButton = document.getElementById("stop-button");
  175. if (stopButton) { stopButton.textContent = "Stopping..."; stopButton.style.cursor = "default"; stopButton.classList.remove("goog-control-hover"); }
  176. }
  177. }
  178.  
  179. function showOverlay() {
  180. if (overlayElement) { overlayElement.style.display = 'flex'; return; }
  181.  
  182. overlayElement = document.createElement("div");
  183. overlayElement.classList.add("human-typer-overlay");
  184.  
  185. // --- Header (same) ---
  186. const header = document.createElement("div"); header.classList.add("human-typer-header");
  187. const title = document.createElement("span"); title.classList.add("human-typer-header-title"); title.textContent = "Human-Typer Settings";
  188. const infoIcon = document.createElement("i"); infoIcon.classList.add("human-typer-info-icon"); infoIcon.textContent = "i"; infoIcon.title = "Show Instructions";
  189. infoIcon.addEventListener("click", toggleInfoPopup);
  190. header.appendChild(title); header.appendChild(infoIcon); overlayElement.appendChild(header);
  191.  
  192. // --- Content Area ---
  193. const content = document.createElement("div"); content.classList.add("human-typer-content");
  194. const textField = document.createElement("textarea"); textField.placeholder = "Paste or type your text here...";
  195. const etaLabel = document.createElement("div"); etaLabel.classList.add("human-typer-eta");
  196.  
  197. // --- Delay Settings ---
  198. const delayGroup = document.createElement("div");
  199. delayGroup.classList.add("human-typer-input-group");
  200.  
  201. const lowerBoundContainer = document.createElement("div");
  202. const lowerBoundLabel = document.createElement("label");
  203. lowerBoundLabel.textContent = "Min Delay (ms):";
  204. const lowerBoundInput = document.createElement("input");
  205. lowerBoundInput.type = "number";
  206. lowerBoundInput.min = "0";
  207. lowerBoundInput.value = lowerBoundValue;
  208. lowerBoundContainer.appendChild(lowerBoundLabel);
  209. lowerBoundContainer.appendChild(lowerBoundInput);
  210.  
  211. const upperBoundContainer = document.createElement("div");
  212. const upperBoundLabel = document.createElement("label");
  213. upperBoundLabel.textContent = "Max Delay (ms):";
  214. const upperBoundInput = document.createElement("input");
  215. upperBoundInput.type = "number";
  216. upperBoundInput.min = "0";
  217. upperBoundInput.value = upperBoundValue;
  218. upperBoundContainer.appendChild(upperBoundLabel);
  219. upperBoundContainer.appendChild(upperBoundInput);
  220.  
  221. delayGroup.appendChild(lowerBoundContainer);
  222. delayGroup.appendChild(upperBoundContainer);
  223.  
  224.  
  225. // --- Options Group (Typos + Advanced Algo) ---
  226. const optionsGroup = document.createElement("div"); optionsGroup.classList.add("human-typer-options-group");
  227.  
  228. // --- Typo Settings ---
  229. const typoItem = document.createElement("div"); typoItem.classList.add("human-typer-checkbox-item");
  230. const typoCheckbox = document.createElement("input"); typoCheckbox.type = "checkbox"; typoCheckbox.id = "human-typer-typo-enable"; typoCheckbox.checked = enableTypos;
  231. const typoLabel = document.createElement("label"); typoLabel.textContent = "Enable Typos & Auto Correction"; typoLabel.htmlFor = "human-typer-typo-enable";
  232. const typoRateLabel = document.createElement("label"); typoRateLabel.textContent = "Typo Rate (%):"; typoRateLabel.htmlFor = "human-typer-typo-rate"; typoRateLabel.classList.add("rate-label");
  233. const typoRateInput = document.createElement("input"); typoRateInput.type = "number"; typoRateInput.id = "human-typer-typo-rate"; typoRateInput.min = "0"; typoRateInput.max = "100"; typoRateInput.step = "1"; typoRateInput.value = typoRatePercentValue; typoRateInput.disabled = !enableTypos;
  234.  
  235. typoCheckbox.addEventListener('change', () => { typoRateInput.disabled = !typoCheckbox.checked; enableTypos = typoCheckbox.checked; updateEta(); });
  236. typoItem.appendChild(typoCheckbox); typoItem.appendChild(typoLabel); typoItem.appendChild(typoRateLabel); typoItem.appendChild(typoRateInput);
  237. optionsGroup.appendChild(typoItem);
  238.  
  239. // --- Advanced Algorithm Setting ---
  240. const advancedItem = document.createElement("div"); advancedItem.classList.add("human-typer-checkbox-item");
  241. const advancedCheckbox = document.createElement("input"); advancedCheckbox.type = "checkbox"; advancedCheckbox.id = "human-typer-advanced-algo"; advancedCheckbox.checked = useAdvancedAlgorithm;
  242. const advancedLabel = document.createElement("label"); advancedLabel.textContent = "Use Advanced Algorithm (Rhythm/Pauses)"; advancedLabel.htmlFor = "human-typer-advanced-algo";
  243.  
  244. advancedCheckbox.addEventListener('change', () => { useAdvancedAlgorithm = advancedCheckbox.checked; updateEta(); });
  245. advancedItem.appendChild(advancedCheckbox); advancedItem.appendChild(advancedLabel);
  246. optionsGroup.appendChild(advancedItem);
  247.  
  248.  
  249. // --- Buttons ---
  250. const buttonContainer = document.createElement("div"); buttonContainer.classList.add("human-typer-buttons");
  251. const confirmButton = document.createElement("button"); confirmButton.textContent = "Start Typing"; confirmButton.classList.add("human-typer-button", "human-typer-confirm-button");
  252. const cancelButton = document.createElement("button"); cancelButton.textContent = "Cancel"; cancelButton.classList.add("human-typer-button", "human-typer-cancel-button");
  253.  
  254. // --- Assemble Content ---
  255. content.appendChild(textField);
  256. content.appendChild(etaLabel);
  257. content.appendChild(delayGroup);
  258. content.appendChild(optionsGroup); // Add the group of options
  259. content.appendChild(buttonContainer);
  260. buttonContainer.appendChild(cancelButton); buttonContainer.appendChild(confirmButton);
  261. overlayElement.appendChild(content);
  262.  
  263. document.body.appendChild(overlayElement);
  264.  
  265. // --- Center Overlay ---
  266. overlayElement.style.left = `${Math.max(0, (window.innerWidth - overlayElement.offsetWidth) / 2)}px`;
  267. overlayElement.style.top = `${Math.max(0, (window.innerHeight - overlayElement.offsetHeight) / 2)}px`;
  268.  
  269. // --- Event Listeners & ETA ---
  270. const updateEta = () => {
  271. const charCount = textField.value.length;
  272. const low = parseInt(lowerBoundInput.value) || 0;
  273. const high = parseInt(upperBoundInput.value) || 0;
  274. if (charCount > 0 && low >= 0 && high >= low) {
  275. let baseMs = charCount * ((low + high) / 2); // Average base time
  276. let factor = 1.0;
  277. if (enableTypos) {
  278. // Estimate typo overhead (typing wrong + backspacing)
  279. // Average typo length ~MAX_TYPO_LENGTH/2. Delay per wrong char + backspace.
  280. const avgTypoLen = MAX_TYPO_LENGTH / 2;
  281. const typoTimePerOccur = avgTypoLen * (BASIC_TYPO_CHAR_DELAY_MS + (useAdvancedAlgorithm ? ADV_BACKSPACE_DELAY_MS : BASIC_BACKSPACE_DELAY_MS))
  282. + (useAdvancedAlgorithm ? (ADV_TYPO_RECOGNITION_MIN_MS + ADV_TYPO_RECOGNITION_MAX_MS)/2 : BASIC_TYPO_PRE_BACKSPACE_DELAY_MS);
  283. baseMs += charCount * (typoRatePercentValue / 100) * typoTimePerOccur;
  284. }
  285. if (useAdvancedAlgorithm) {
  286. // Estimate overhead from pauses (spaces, ends, random) - very approximate
  287. const spaceCount = (textField.value.match(/ /g) || []).length;
  288. const avgSpacePauseIncrease = ((ADV_SPACE_MULTIPLIER_MIN + ADV_SPACE_MULTIPLIER_MAX) / 2 - 1) * ((low + high) / 2);
  289. baseMs += spaceCount * avgSpacePauseIncrease;
  290. // Add small fudge factor for other pauses
  291. factor += 0.1;
  292. }
  293.  
  294. const etaMinutes = Math.ceil(baseMs / 60000);
  295. etaLabel.textContent = `ETA: ~${etaMinutes} minutes ${useAdvancedAlgorithm ? '(Advanced)' : ''} ${enableTypos ? '(incl. typos)' : ''}`;
  296.  
  297. } else {
  298. etaLabel.textContent = "";
  299. }
  300. // Validate inputs
  301. const currentTypoRate = parseInt(typoRateInput.value);
  302. const typoRateValid = !enableTypos || (!isNaN(currentTypoRate) && currentTypoRate >= 0 && currentTypoRate <= 100); // Valid if typos disabled or rate is ok
  303. confirmButton.disabled = textField.value.trim() === "" || low < 0 || high < low || !typoRateValid;
  304. };
  305.  
  306. textField.addEventListener("input", updateEta);
  307. lowerBoundInput.addEventListener("input", updateEta);
  308. upperBoundInput.addEventListener("input", updateEta);
  309. typoCheckbox.addEventListener("change", updateEta); // Handled above
  310. advancedCheckbox.addEventListener("change", updateEta); // Handled above
  311. typoRateInput.addEventListener("input", () => {
  312. const rate = parseInt(typoRateInput.value); if (!isNaN(rate)) typoRatePercentValue = rate;
  313. updateEta(); // Recalculate
  314. });
  315.  
  316. cancelButton.addEventListener("click", () => { overlayElement.style.display = 'none'; hideInfoPopup(); });
  317.  
  318. confirmButton.addEventListener("click", () => {
  319. const userInput = textField.value;
  320. const newLower = parseInt(lowerBoundInput.value); const newUpper = parseInt(upperBoundInput.value);
  321. const newTypoRatePercent = parseInt(typoRateInput.value);
  322. const newEnableTypos = typoCheckbox.checked;
  323. const newUseAdvanced = advancedCheckbox.checked;
  324.  
  325. if (userInput.trim() === "" || isNaN(newLower) || isNaN(newUpper) || newLower < 0 || newUpper < newLower || (newEnableTypos && (isNaN(newTypoRatePercent) || newTypoRatePercent < 0 || newTypoRatePercent > 100))) {
  326. console.warn("Invalid input or settings."); return;
  327. }
  328.  
  329. lowerBoundValue = newLower; upperBoundValue = newUpper;
  330. enableTypos = newEnableTypos; typoRatePercentValue = newTypoRatePercent;
  331. useAdvancedAlgorithm = newUseAdvanced; // Store advanced setting
  332.  
  333. overlayElement.style.display = 'none'; hideInfoPopup();
  334. startTypingProcess(userInput);
  335. });
  336.  
  337. makeDraggable(overlayElement, header); // Make draggable
  338. updateEta(); // Initial calculation
  339. }
  340.  
  341. function toggleInfoPopup(event) { /* ... (same as v1.6) ... */ if (infoPopupElement) hideInfoPopup(); else showInfoPopup(event.target); event.stopPropagation(); }
  342.  
  343. function showInfoPopup(iconElement) {
  344. hideInfoPopup();
  345. infoPopupElement = document.createElement('div');
  346. infoPopupElement.classList.add('human-typer-info-popup');
  347. infoPopupElement.innerHTML = `
  348. <p><strong>Instructions:</strong></p>
  349. <p>- Paste text into the area.</p>
  350. <p>- Set <strong>Min/Max Delay</strong> (ms) for base character typing speed.</p>
  351. <p>- Enable <strong>Typos</strong> & set <strong>% Rate</strong>. Typos involve typing adjacent keys, pausing, then auto-correcting with Backspace.</p>
  352. <p>- Enable <strong>Advanced Algorithm</strong> for more human-like rhythm:</p>
  353. <p style="margin-left: 15px; margin-bottom: 0.3em;">• Longer pauses after spaces.</p>
  354. <p style="margin-left: 15px; margin-bottom: 0.3em;">• Slight pauses before punctuation/end-of-word.</p>
  355. <p style="margin-left: 15px; margin-bottom: 0.3em;">• Longer, variable pause before correcting typos (<code>${ADV_TYPO_RECOGNITION_MIN_MS}-${ADV_TYPO_RECOGNITION_MAX_MS}ms</code>).</p>
  356. <p style="margin-left: 15px; margin-bottom: 0.7em;">• Occasional random brief pauses.</p>
  357. <p>- Click <strong>'Start Typing'</strong> (ensure cursor is in Doc/Slide).</p>
  358. <p>- Keep tab active. Use <strong>'Stop'</strong> button to cancel.</p>
  359. <p>- Drag window header to move.</p>
  360. `;
  361. document.body.appendChild(infoPopupElement);
  362. // Position the popup (same logic as v1.6)
  363. const iconRect = iconElement.getBoundingClientRect(); const popupRect = infoPopupElement.getBoundingClientRect();
  364. let top = iconRect.bottom + window.scrollY + 5; let left = iconRect.left + window.scrollX - (popupRect.width / 2) + (iconRect.width / 2);
  365. const margin = 10; if (left < margin) left = margin; if (left + popupRect.width > window.innerWidth - margin) left = window.innerWidth - popupRect.width - margin;
  366. if (top + popupRect.height > window.innerHeight - margin) top = iconRect.top + window.scrollY - popupRect.height - 5; if (top < margin) top = margin;
  367. infoPopupElement.style.top = `${top}px`; infoPopupElement.style.left = `${left}px`;
  368. setTimeout(() => { document.addEventListener('click', handleClickOutsideInfoPopup, true); }, 0);
  369. }
  370.  
  371. function hideInfoPopup() { /* ... (same as v1.6) ... */ if (infoPopupElement) { infoPopupElement.remove(); infoPopupElement = null; document.removeEventListener('click', handleClickOutsideInfoPopup, true); } }
  372. function handleClickOutsideInfoPopup(event) { /* ... (same as v1.6) ... */ if (infoPopupElement && !infoPopupElement.contains(event.target) && !event.target.classList.contains('human-typer-info-icon')) hideInfoPopup(); }
  373. function makeDraggable(element, handle) { /* ... (same as v1.6, includes boundary checks) ... */
  374. let isDragging = false; let offsetX, offsetY;
  375. const onMouseDown = (e) => { if (e.button !== 0) return; if (!handle || handle.contains(e.target)) { isDragging = true; const rect = element.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; element.style.cursor = 'grabbing'; document.body.style.userSelect = 'none'; e.preventDefault(); } };
  376. const onMouseMove = (e) => { if (!isDragging) return; let newX = e.clientX - offsetX; let newY = e.clientY - offsetY; const vw = window.innerWidth; const vh = window.innerHeight; const elemWidth = element.offsetWidth; const elemHeight = element.offsetHeight; const margin = 5; newX = Math.max(margin, Math.min(newX, vw - elemWidth - margin)); newY = Math.max(margin, Math.min(newY, vh - elemHeight - margin)); element.style.left = `${newX}px`; element.style.top = `${newY}px`; };
  377. const onMouseUp = (e) => { if (isDragging && e.button === 0) { isDragging = false; element.style.cursor = ''; document.body.style.userSelect = ''; if (handle) handle.style.cursor = 'move'; } };
  378. const target = handle || element; target.addEventListener('mousedown', onMouseDown); document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); if (handle) handle.style.cursor = 'move';
  379. }
  380.  
  381. // --- Typing Simulation ---
  382.  
  383. async function startTypingProcess(textToType) { /* ... (same setup as v1.6) ... */
  384. const inputElement = findInputElement(); if (!inputElement) { alert("Could not find the Google Docs/Slides input area. Ensure cursor is active."); return; }
  385. typingInProgress = true; cancelTyping = false; const stopButton = document.getElementById("stop-button");
  386. if (stopButton) { stopButton.textContent = "Stop"; stopButton.style.display = "inline-block"; stopButton.style.cursor = "pointer"; }
  387. await typeStringWithLogic(inputElement, textToType);
  388. typingInProgress = false; if (stopButton) stopButton.style.display = "none";
  389. console.log(cancelTyping ? "Typing stopped by user." : "Typing finished.");
  390. }
  391.  
  392. function findInputElement() { /* ... (same as v1.6) ... */
  393. try { const iframe = document.querySelector(".docs-texteventtarget-iframe"); if (iframe && iframe.contentDocument) { return iframe.contentDocument.activeElement && iframe.contentDocument.activeElement.nodeName !== 'HTML' ? iframe.contentDocument.activeElement : iframe.contentDocument.body; } } catch (e) { console.error("Error accessing iframe content:", e); } console.error("Could not find target input element."); return null;
  394. }
  395.  
  396. // Helper function for delays, cancellable
  397. async function delay(ms) { /* ... (same as v1.6) ... */
  398. return new Promise(resolve => { if (ms <= 0) { resolve(!cancelTyping); return; } const timeoutId = setTimeout(() => resolve(!cancelTyping), ms); const checkCancel = () => { if (cancelTyping) { clearTimeout(timeoutId); resolve(false); } else if (typingInProgress) { requestAnimationFrame(checkCancel); } }; requestAnimationFrame(checkCancel); });
  399. }
  400.  
  401. // Calculates delay based on mode and context
  402. function calculateDelay(currentChar, prevChar, nextChar) {
  403. let baseDelay = Math.random() * (upperBoundValue - lowerBoundValue) + lowerBoundValue;
  404. let finalDelay = baseDelay;
  405.  
  406. if (useAdvancedAlgorithm) {
  407. const isSpace = (char) => char === ' ';
  408. const isPunctuation = (char) => /[.,!?;:]/.test(char); // Basic punctuation
  409. const isEndOfWord = (current, next) => !isSpace(current) && (isSpace(next) || next === null || next === '\n');
  410.  
  411. // Pause after space
  412. if (prevChar && isSpace(prevChar)) {
  413. finalDelay *= Math.random() * (ADV_SPACE_MULTIPLIER_MAX - ADV_SPACE_MULTIPLIER_MIN) + ADV_SPACE_MULTIPLIER_MIN;
  414. }
  415. // Pause after punctuation
  416. else if (prevChar && isPunctuation(prevChar)) {
  417. finalDelay *= ADV_PUNCTUATION_MULTIPLIER;
  418. }
  419. // Slight pause at end of word (before space/newline/end)
  420. else if (isEndOfWord(currentChar, nextChar)) {
  421. finalDelay *= Math.random() * (ADV_WORD_END_MULTIPLIER_MAX - ADV_WORD_END_MULTIPLIER_MIN) + ADV_WORD_END_MULTIPLIER_MIN;
  422. }
  423.  
  424. // Occasional random longer pause
  425. if (Math.random() < ADV_RANDOM_PAUSE_CHANCE) {
  426. const pause = Math.random() * (ADV_RANDOM_PAUSE_MAX_MS - ADV_RANDOM_PAUSE_MIN_MS) + ADV_RANDOM_PAUSE_MIN_MS;
  427. console.log(`-- Random pause: ${pause.toFixed(0)}ms --`);
  428. finalDelay += pause;
  429. }
  430. }
  431.  
  432. // Ensure delay isn't negative or excessively small
  433. return Math.max(10, finalDelay);
  434. }
  435.  
  436.  
  437. async function simulateKey(inputElement, charOrCode, keyDelay) {
  438. // Use the delay helper BEFORE dispatching
  439. const proceed = await delay(keyDelay);
  440. if (!proceed) return false; // Stop if cancelled during delay
  441.  
  442. const eventProps = { bubbles: true, cancelable: true };
  443. let eventType; let keyEventProps; let logChar = charOrCode;
  444.  
  445. if (charOrCode === '\n') {
  446. eventType = 'keydown'; keyEventProps = { key: 'Enter', code: 'Enter', keyCode: 13, which: 13 }; logChar = '\\n';
  447. } else if (charOrCode === '\b') { // Backspace
  448. eventType = 'keydown'; keyEventProps = { key: 'Backspace', code: 'Backspace', keyCode: 8, which: 8 }; logChar = 'Backspace';
  449. }
  450. else { // Regular character
  451. eventType = 'keypress'; keyEventProps = { key: charOrCode, charCode: charOrCode.charCodeAt(0), keyCode: charOrCode.charCodeAt(0), which: charOrCode.charCodeAt(0) };
  452. }
  453.  
  454. Object.assign(eventProps, keyEventProps);
  455. const eventObj = new KeyboardEvent(eventType, eventProps);
  456.  
  457. try {
  458. inputElement.dispatchEvent(eventObj);
  459. console.log(`Key: ${logChar}, Delay: ${keyDelay.toFixed(0)}ms`);
  460. } catch (e) {
  461. console.error(`Error dispatching event for key "${logChar}":`, e);
  462. return false; // Indicate failure
  463. }
  464. return true; // Indicate success
  465. }
  466.  
  467. // Main typing loop using the logic/delay calculation
  468. async function typeStringWithLogic(inputElement, string) {
  469. for (let i = 0; i < string.length; i++) {
  470. if (cancelTyping) break;
  471.  
  472. const char = string[i];
  473. const prevChar = i > 0 ? string[i - 1] : null;
  474. const nextChar = i < string.length - 1 ? string[i + 1] : null;
  475.  
  476. let proceed = true;
  477.  
  478. // --- Typo Simulation ---
  479. if (enableTypos && char.match(/\S/) && char !== '\n' && Math.random() < (typoRatePercentValue / 100)) {
  480. const typoLength = Math.floor(Math.random() * MAX_TYPO_LENGTH) + 1;
  481. let wrongSequence = "";
  482. for (let j = 0; j < typoLength; j++) {
  483. wrongSequence += getNearbyKey(char); // Use original char for adjacency basis
  484. }
  485.  
  486. console.log(`-> Simulating ${typoLength}-char typo for '${char}', typing '${wrongSequence}'`);
  487.  
  488. // 1. Type the wrong sequence
  489. for (let j = 0; j < wrongSequence.length; j++) {
  490. proceed = await simulateKey(inputElement, wrongSequence[j], BASIC_TYPO_CHAR_DELAY_MS); // Quick typing for wrong chars
  491. if (!proceed) break;
  492. }
  493. if (!proceed) break;
  494.  
  495. // 2. Pause before correcting (variable if advanced)
  496. const recognitionDelay = useAdvancedAlgorithm
  497. ? Math.random() * (ADV_TYPO_RECOGNITION_MAX_MS - ADV_TYPO_RECOGNITION_MIN_MS) + ADV_TYPO_RECOGNITION_MIN_MS
  498. : BASIC_TYPO_PRE_BACKSPACE_DELAY_MS;
  499. console.log(`-- Typo recognition pause: ${recognitionDelay.toFixed(0)}ms --`);
  500. proceed = await delay(recognitionDelay);
  501. if (!proceed) break;
  502.  
  503. // 3. Delete the wrong sequence
  504. const backspaceDelay = useAdvancedAlgorithm ? ADV_BACKSPACE_DELAY_MS : BASIC_BACKSPACE_DELAY_MS;
  505. for (let j = 0; j < wrongSequence.length; j++) {
  506. proceed = await simulateKey(inputElement, '\b', backspaceDelay); // Use backspace delay
  507. if (!proceed) break;
  508. }
  509. if (!proceed) break;
  510.  
  511. console.log(`<- Typo for '${char}' corrected.`);
  512. }
  513. // --- End Typo Simulation ---
  514.  
  515. // Calculate delay for the *correct* character using context
  516. const typingDelay = calculateDelay(char, prevChar, nextChar);
  517.  
  518. // Type the correct character
  519. proceed = await simulateKey(inputElement, char, typingDelay);
  520. if (!proceed) break;
  521. }
  522. }
  523.  
  524. // --- Typo Helper ---
  525. function getNearbyKey(char) { /* ... (same as v1.6, includes number/symbol attempt) ... */
  526. const keyboardLayout={'q':'wa','w':'qase','e':'wsdr','r':'edft','t':'rfgy','y':'tghu','u':'yhji','i':'ujko','o':'iklp','p':'ol[','a':'qwsz','s':'awedxz','d':'erfcxs','f':'rtgvcd','g':'tyhbvf','h':'yujnbg','j':'uikmnh','k':'iolmj','l':'opk;','z':'asx','x':'zsdc','c':'xdfv','v':'cfgb','b':'vghn','n':'bhjm','m':'njk,','1':'2q`','2':'1qw3','3':'2we4','4':'3er5','5':'4rt6','6':'5ty7','7':'6yu8','8':'7ui9','9':'8io0','0':'9op-','-':'0p[=','=':'-[]','[':'=p]o',']':'[\\;p','\\':']=',';':'lkp\'[]',"'":';l/',',':'mkj.','m':'.', '/':'\'l;.,', '`':'1', ' ':' '}; // Added space mapping to itself
  527. const lowerChar = char.toLowerCase(); const adjacent = keyboardLayout[lowerChar];
  528. if (!adjacent || adjacent.length === 0) return char;
  529. let attempts = 0; let nearbyChar;
  530. do { const randomIndex = Math.floor(Math.random() * adjacent.length); nearbyChar = adjacent[randomIndex]; attempts++; } while (nearbyChar === lowerChar && attempts < 5 && adjacent.length > 1)
  531. return char === char.toUpperCase() && char !== lowerChar ? nearbyChar.toUpperCase() : nearbyChar;
  532. }
  533.  
  534. // --- Initialization ---
  535. const initInterval = setInterval(() => { /* ... (same robust check as v1.6) ... */
  536. if (document.getElementById("docs-help-menu") && document.querySelector(".docs-texteventtarget-iframe")) {
  537. try { if (document.querySelector(".docs-texteventtarget-iframe").contentDocument) { clearInterval(initInterval); initializeScript(); } else { console.log("Human-Typer: Waiting for iframe content access..."); } } catch (e) { console.log("Human-Typer: Waiting for iframe content access (error)..."); }
  538. } }, 500);
  539.  
  540. })(); // End userscript

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址