Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
- 📝 Emulate typing by pasting clipboard contents into text input fields.
- ⏹️ Ability to stop the emulation.
- 🕹️ Realistic typing emulation with random intervals between key presses.
- ⏳ Settings to adjust keystroke speed
- ⌨️⌨️ Hotkey support: Ctrl/Command+Shift+Y (default)

## 🛠️ Installation

Expand Down
76 changes: 60 additions & 16 deletions background.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,67 @@
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: "pasteHuman",
title: "PasteHuman: Emulate typing",
contexts: ["editable"],
});
chrome.contextMenus.create({
id: "pasteHuman",
title: "PasteHuman: Emulate typing",
contexts: ["editable"],
});

chrome.contextMenus.create({
id: "stopPasteHuman",
title: "PasteHuman: Stop typing",
contexts: ["editable"],
});
chrome.contextMenus.create({
id: "stopPasteHuman",
title: "PasteHuman: Stop typing",
contexts: ["editable"],
});

// Set default settings
const defaultSettings = {
minDelay: 10,
maxDelay: 75,
extraDelayChance: 0.05,
extraDelayMin: 20,
extraDelayMax: 100,
};

chrome.storage.sync.set(defaultSettings, function () {
console.log("Default settings saved");
});
});

chrome.contextMenus.onClicked.addListener((info, tab) => {
console.log('background.js: Context menu clicked:', info.menuItemId); // Log context menu item clicked
console.log("background.js: Context menu clicked:", info.menuItemId); // Log context menu item clicked

if (info.menuItemId === "pasteHuman") {
// Get settings from chrome.storage
chrome.storage.sync.get(defaultSettings, function (items) {
// Send settings along with action
chrome.tabs.sendMessage(tab.id, { action: "emulateTyping", settings: items });
});
} else if (info.menuItemId === "stopPasteHuman") {
chrome.tabs.sendMessage(tab.id, { action: "stopTyping" });
}
});

chrome.commands.onCommand.addListener(function (command) {
console.log("Command:", command);

if (info.menuItemId === "pasteHuman") {
chrome.tabs.sendMessage(tab.id, { action: "emulateTyping" });
} else if (info.menuItemId === "stopPasteHuman") {
chrome.tabs.sendMessage(tab.id, { action: "stopTyping" });
}
if (command === "emulateTyping") {
// Get settings from chrome.storage
chrome.storage.local.get(
{
minDelay: 50,
maxDelay: 200,
extraDelayChance: 0.05,
extraDelayMin: 200,
extraDelayMax: 700,
},
function (items) {
// Send settings along with action
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, { action: "emulateTyping", settings: items });
});
}
);
} else if (command === "stopTyping") {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, { action: "stopTyping" });
});
}
});
37 changes: 21 additions & 16 deletions content.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@


let currentTypingSession = null;

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
console.log('content.js: Message received:', request.action); // Log message received
//console.log("content.js: Message received:", request.action); // Log message received

if (request.action === "emulateTyping") {
console.log("content.js: Action received: emulateTyping");
//console.log("content.js: Action received: emulateTyping");
let settings = request.settings;
currentTypingSession = Math.random().toString(); // Create a new unique typing session identifier
navigator.clipboard
.readText()
.then((clipText) => {
console.log("content.js: Clipboard text read:", clipText); // Log clipboard text
emulateTyping(clipText, currentTypingSession, request.delayedStart);
});
navigator.clipboard.readText().then((clipText) => {
//console.log("content.js: Clipboard text read:", clipText); // Log clipboard text
emulateTyping(clipText, currentTypingSession, request.delayedStart, settings);
});
} else if (request.action === "stopTyping") {
currentTypingSession = null; // Invalidate the current typing session
}
Expand All @@ -20,18 +21,18 @@ chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
// Listen for any keydown event at the window level
window.addEventListener("keydown", function () {
currentTypingSession = null; // Invalidate the current typing session
console.log('content.js: Keydown event detected'); // Log keydown event
//console.log("content.js: Keydown event detected"); // Log keydown event
});

function emulateTyping(text, session, delayedStart) {
function emulateTyping(text, session, delayedStart, settings) {
const activeElement = document.activeElement;
console.log('content.js: Active element:', activeElement); // Log the active element
//console.log("content.js: Active element:", activeElement); // Log the active element

let i = 0;

const startTyping = function () {
function typeNextCharacter() {
console.log('content.js: Typing character', text[i]); // Log character being typed
// console.log("content.js: Typing character", text[i]); // Log character being typed

if (i < text.length && session === currentTypingSession) {
// Create a KeyboardEvent instance
Expand All @@ -49,10 +50,14 @@ function emulateTyping(text, session, delayedStart) {
// Attempt to insert the text using document.execCommand
document.execCommand("insertText", false, text[i++]);

let delay = Math.random() * (200 - 50) + 50;
// Set the initial delay to a random value between MIN_DELAY and MAX_DELAY.
// This simulates the natural variation in the time it takes a human to press each key.
let delay = Math.random() * (settings.maxDelay - settings.minDelay) + settings.minDelay;

if (Math.random() < 0.05) {
delay += Math.random() * (700 - 200) + 200;
// With a 5% chance, add an additional delay to the base delay.
// This simulates the occasional longer pauses a human might take while typing, such as when thinking or moving from one part of the keyboard to another.
if (Math.random() < settings.extraDelayChance) {
delay += Math.random() * (settings.extraDelayMax - settings.extraDelayMin) + settings.extraDelayMin;
}

setTimeout(typeNextCharacter, delay);
Expand All @@ -63,7 +68,7 @@ function emulateTyping(text, session, delayedStart) {
};

if (delayedStart) {
console.log('content.js: Starting typing with a delay');
//console.log("content.js: Starting typing with a delay");
setTimeout(startTyping, 0); // Delay is in milliseconds
} else {
startTyping();
Expand Down
23 changes: 20 additions & 3 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"manifest_version": 3,
"name": "PasteHuman",
"description": "Transforming plain text into human-like typing. This extension emulates typing in textboxes.",
"version": "1.0",
"permissions": ["contextMenus", "activeTab", "clipboardRead"],
"version": "1.1",
"permissions": ["contextMenus", "activeTab", "clipboardRead", "storage"],
"background": {
"service_worker": "background.js"
},
Expand All @@ -15,6 +15,22 @@
"128": "icons/icon128.png"
}
},
"commands": {
"emulateTyping": {
"suggested_key": {
"default": "Ctrl+Shift+Y",
"mac": "Command+Shift+Y"
},
"description": "Emulate typing"
},
"stopTyping": {
"suggested_key": {
"default": "Ctrl+Shift+U",
"mac": "Command+Shift+U"
},
"description": "Stop typing"
}
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
Expand All @@ -25,5 +41,6 @@
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
],
"options_page": "options.html"
}
46 changes: 46 additions & 0 deletions options.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<title>PasteHuman Settings</title>
<style>
body {
background-color: #EFEAE4;
color: #000000;
padding: 15px;
width: 300px;
}
h1, label, input[type="submit"] {
text-align: center;
width: 100%;
}
input[type="submit"] {
background-color: #000000;
border: none;
color: #ffffff;
padding: 15px 32px;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px auto;
cursor: pointer;
}
</style>
<script src="options.js"></script>
</head>
<body>
<h1>PasteHuman Settings</h1>
<form id="settingsForm">
<label for="minDelay">Min Delay:</label><br>
<input type="number" id="minDelay" name="minDelay"><br>
<label for="maxDelay">Max Delay:</label><br>
<input type="number" id="maxDelay" name="maxDelay"><br>
<label for="extraDelayChance">Extra Delay Chance:</label><br>
<input type="number" id="extraDelayChance" name="extraDelayChance" step="0.01" min="0" max="1"><br>
<label for="extraDelayMin">Extra Delay Min:</label><br>
<input type="number" id="extraDelayMin" name="extraDelayMin"><br>
<label for="extraDelayMax">Extra Delay Max:</label><br>
<input type="number" id="extraDelayMax" name="extraDelayMax"><br>
<input type="submit" value="Save">
</form>
</body>
</html>
30 changes: 30 additions & 0 deletions options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
document.addEventListener('DOMContentLoaded', function () {
// Load settings
chrome.storage.sync.get({
minDelay: 10,
maxDelay: 75,
extraDelayChance: 0.05,
extraDelayMin: 20,
extraDelayMax: 100,
}, function (items) {
document.getElementById('minDelay').value = items.minDelay;
document.getElementById('maxDelay').value = items.maxDelay;
document.getElementById('extraDelayChance').value = items.extraDelayChance;
document.getElementById('extraDelayMin').value = items.extraDelayMin;
document.getElementById('extraDelayMax').value = items.extraDelayMax;
});

// Save settings
document.getElementById('settingsForm').addEventListener('submit', function (e) {
e.preventDefault();
chrome.storage.sync.set({
minDelay: parseInt(document.getElementById('minDelay').value),
maxDelay: parseInt(document.getElementById('maxDelay').value),
extraDelayChance: parseFloat(document.getElementById('extraDelayChance').value),
extraDelayMin: parseInt(document.getElementById('extraDelayMin').value),
extraDelayMax: parseInt(document.getElementById('extraDelayMax').value),
}, function () {
alert('Settings saved');
});
});
});
6 changes: 3 additions & 3 deletions popup.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
document.getElementById('startTyping').addEventListener('click', function() {
console.log("popup.js: Start typing button clicked");
//console.log("popup.js: Start typing button clicked");

// Using a timeout function to delay the message being sent by 5 seconds
setTimeout(function() {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var currentTab = tabs[0]; // There should be only one in this array
console.log(`popup.js: Sending message to tab ${currentTab.id}`);
//console.log(`popup.js: Sending message to tab ${currentTab.id}`);
chrome.tabs.sendMessage(currentTab.id, {
action: 'emulateTyping',
delayedStart: true,
}, function(response) {
console.log("popup.js: Message sent: emulateTyping");
//console.log("popup.js: Message sent: emulateTyping");
});
});
}, 5000); // 5000 milliseconds (5 seconds) delay
Expand Down