Greasy Fork is available in English.
Automatically accept studies on Prolific with control menu
// ==UserScript==
// @name Prolific Auto-Accept Studies
// @namespace http://tampermonkey.net/
// @version 2.5
// @description Automatically accept studies on Prolific with control menu
// @author You
// @match https://app.prolific.com/studies*
// @match https://app.prolific.co/*
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// Settings
let isEnabled = GM_getValue('autoAcceptEnabled', true);
let acceptCount = GM_getValue('acceptCount', 0);
let failedCount = GM_getValue('failedCount', 0);
let soundEnabled = GM_getValue('soundEnabled', true);
let lastClickTime = 0;
const CLICK_COOLDOWN = 3000; // 3 seconds between accept attempts
// Screen-aware position storage
const screenKey = `prolific_screen_${window.innerWidth}x${window.innerHeight}`;
let menuX = GM_getValue(screenKey, window.innerWidth - 295);
let menuY = GM_getValue(`${screenKey}_y`, 70);
// Audio URL - your custom sound
const AUDIO_URL = 'https://github.com/user-attachments/files/28318322/audio_alert.mp3';
// Function to play the notification sound
function playNotificationSound() {
if (!soundEnabled) return;
try {
const audio = new Audio(AUDIO_URL);
audio.volume = 0.5;
audio.play().catch(err => {
console.log('[Prolific] Could not play audio:', err.message);
});
} catch (e) {
console.log('[Prolific] Error loading audio:', e.message);
}
}
// Clamp position to screen bounds
function clampPosition(x, y) {
const menuWidth = 300;
const menuHeight = 400;
x = Math.max(0, Math.min(x, window.innerWidth - menuWidth));
y = Math.max(0, Math.min(y, window.innerHeight - menuHeight));
return { x, y };
}
function addDebugLog(msg) {
const debugEl = document.getElementById('prolific-debug');
if (debugEl) {
const time = new Date().toLocaleTimeString();
debugEl.innerHTML = `[${time}] ${msg}<br>` + debugEl.innerHTML;
debugEl.scrollTop = 0;
}
console.log(`[Prolific] ${msg}`);
}
// Reset menu position to default
function resetMenuPosition() {
menuX = window.innerWidth - 295;
menuY = 70;
GM_setValue(screenKey, menuX);
GM_setValue(`${screenKey}_y`, menuY);
const container = document.getElementById('prolific-auto-accept-menu');
if (container) {
container.style.left = menuX + 'px';
container.style.top = menuY + 'px';
}
addDebugLog('Menu position reset!');
}
// Create menu container
function createMenu() {
// Remove if already exists
const existing = document.getElementById('prolific-auto-accept-menu');
if (existing) existing.remove();
// Clamp position
const pos = clampPosition(menuX, menuY);
menuX = pos.x;
menuY = pos.y;
const container = document.createElement('div');
container.id = 'prolific-auto-accept-menu';
container.style.cssText = `
position: fixed;
top: ${menuY}px;
left: ${menuX}px;
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.15);
padding: 0;
min-width: 300px;
user-select: none;
`;
container.innerHTML = `
<div id="prolific-menu-header" style="padding: 15px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; cursor: move; background: linear-gradient(to right, #f9f9f9, #ffffff);">
<span style="font-weight: 600; font-size: 14px;">Auto-Accept</span>
<button id="prolific-menu-toggle" style="background: none; border: none; cursor: pointer; font-size: 16px; padding: 0; width: 20px; height: 20px;">▼</button>
</div>
<div id="prolific-menu-content" style="padding: 15px;">
<div style="margin-bottom: 12px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="prolific-auto-accept-toggle" ${isEnabled ? 'checked' : ''} style="margin-right: 8px; cursor: pointer;">
<span style="font-size: 13px;">Enable auto-accept</span>
</label>
</div>
<div style="margin-bottom: 12px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="prolific-sound-toggle" ${soundEnabled ? 'checked' : ''} style="margin-right: 8px; cursor: pointer;">
<span style="font-size: 13px;">Enable sound alert</span>
</label>
</div>
<div style="background: #f5f5f5; padding: 10px; border-radius: 4px; margin-bottom: 12px;">
<div style="font-size: 12px; color: #666; margin-bottom: 4px;">Studies Accepted</div>
<div style="font-size: 20px; font-weight: 600; color: #2563eb;" id="prolific-accept-count">${acceptCount}</div>
</div>
<div style="background: #fff5f5; padding: 10px; border-radius: 4px; margin-bottom: 12px;">
<div style="font-size: 12px; color: #666; margin-bottom: 4px;">Failed to Accept</div>
<div style="font-size: 20px; font-weight: 600; color: #dc2626;" id="prolific-failed-count">${failedCount}</div>
</div>
<button id="prolific-test-sound" style="width: 100%; padding: 8px; background: #e0e0ff; border: 1px solid #b0b0ff; border-radius: 4px; cursor: pointer; font-size: 12px; margin-bottom: 12px; transition: background 0.2s;">Test Sound</button>
<button id="prolific-reset-position" style="width: 100%; padding: 8px; background: #ffe0e0; border: 1px solid #ffb0b0; border-radius: 4px; cursor: pointer; font-size: 12px; margin-bottom: 12px; transition: background 0.2s;">Reset Position</button>
<div id="prolific-debug" style="background: #f9f9f9; padding: 8px; border-radius: 4px; margin-bottom: 12px; font-size: 10px; color: #666; max-height: 150px; overflow-y: auto; font-family: monospace; border: 1px solid #eee; line-height: 1.4;"></div>
<button id="prolific-reset-count" style="width: 100%; padding: 8px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; font-size: 12px; transition: background 0.2s;">Reset All</button>
</div>
`;
document.body.appendChild(container);
// Make header draggable with clamping
const header = document.getElementById('prolific-menu-header');
let isDragging = false;
let offsetX = 0;
let offsetY = 0;
header.addEventListener('mousedown', (e) => {
isDragging = true;
offsetX = e.clientX - container.offsetLeft;
offsetY = e.clientY - container.offsetTop;
header.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
let newX = e.clientX - offsetX;
let newY = e.clientY - offsetY;
// Clamp to screen bounds
const clamped = clampPosition(newX, newY);
newX = clamped.x;
newY = clamped.y;
menuX = newX;
menuY = newY;
container.style.left = menuX + 'px';
container.style.top = menuY + 'px';
}
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
header.style.cursor = 'move';
// Save position for this specific screen size
GM_setValue(screenKey, menuX);
GM_setValue(`${screenKey}_y`, menuY);
}
});
// Toggle menu
document.getElementById('prolific-menu-toggle').addEventListener('click', function() {
const content = document.getElementById('prolific-menu-content');
const toggle = this;
if (content.style.display === 'none') {
content.style.display = 'block';
toggle.textContent = '▼';
} else {
content.style.display = 'none';
toggle.textContent = '▶';
}
});
// Toggle auto-accept
document.getElementById('prolific-auto-accept-toggle').addEventListener('change', function() {
isEnabled = this.checked;
GM_setValue('autoAcceptEnabled', isEnabled);
addDebugLog(`Auto-accept ${isEnabled ? 'enabled' : 'disabled'}`);
});
// Toggle sound
document.getElementById('prolific-sound-toggle').addEventListener('change', function() {
soundEnabled = this.checked;
GM_setValue('soundEnabled', soundEnabled);
addDebugLog(`Sound ${soundEnabled ? 'enabled' : 'disabled'}`);
});
// Test sound button
document.getElementById('prolific-test-sound').addEventListener('click', function() {
addDebugLog('Testing sound...');
playNotificationSound();
});
// Reset position button
const resetBtn = document.getElementById('prolific-reset-position');
if (resetBtn) {
resetBtn.addEventListener('click', function() {
resetMenuPosition();
});
}
// Reset count
document.getElementById('prolific-reset-count').addEventListener('click', function() {
acceptCount = 0;
failedCount = 0;
GM_setValue('acceptCount', 0);
GM_setValue('failedCount', 0);
document.getElementById('prolific-accept-count').textContent = '0';
document.getElementById('prolific-failed-count').textContent = '0';
addDebugLog('All counts reset');
});
}
// Function to check if study is full
function isStudyFull() {
const pageText = document.body.innerText.toLowerCase();
return pageText.includes('study is full') ||
pageText.includes('no more places') ||
pageText.includes('places available') ||
pageText.includes('all spots taken');
}
// Function to find and click accept buttons
function acceptStudies() {
if (!isEnabled) return;
// Check if study is full
if (isStudyFull()) {
addDebugLog('⚠ Study is full! Refreshing...');
setTimeout(() => {
window.location.reload();
}, 500);
return;
}
const now = Date.now();
// Check if enough time has passed since last click attempt
if (now - lastClickTime < CLICK_COOLDOWN) {
return;
}
const allButtons = document.querySelectorAll('button');
// Look for "Take part in this study" button
const acceptBtns = Array.from(allButtons).filter(btn => {
const text = btn.textContent.trim();
return text === 'Take part in this study' && !btn.disabled && btn.offsetParent !== null;
});
if (acceptBtns.length > 0) {
lastClickTime = now;
addDebugLog(`Found study button - clicking...`);
try {
acceptBtns[0].click();
playNotificationSound();
// Wait and check if the acceptance was successful
setTimeout(() => {
const stillHasButton = Array.from(document.querySelectorAll('button')).some(b => {
const text = b.textContent.trim();
return text === 'Take part in this study' && !b.disabled;
});
if (stillHasButton) {
failedCount++;
GM_setValue('failedCount', failedCount);
const failedEl = document.getElementById('prolific-failed-count');
if (failedEl) {
failedEl.textContent = failedCount;
}
const now = new Date();
const timeStr = now.toLocaleTimeString();
addDebugLog(`✗ Failed to accept at ${timeStr} (${failedCount} failed)`);
} else {
acceptCount++;
GM_setValue('acceptCount', acceptCount);
const countElement = document.getElementById('prolific-accept-count');
if (countElement) {
countElement.textContent = acceptCount;
}
const now = new Date();
const timeStr = now.toLocaleTimeString();
addDebugLog(`✓ Successfully accepted at ${timeStr} (${acceptCount} total)`);
}
}, 2000);
} catch (e) {
addDebugLog(`✗ Error: ${e.message}`);
}
}
}
// Keyboard shortcut - Right Shift to reset position
document.addEventListener('keydown', (e) => {
if (e.key === 'Shift' && e.location === 2) {
e.preventDefault();
resetMenuPosition();
}
}, true);
// Initialize
setTimeout(() => {
createMenu();
addDebugLog('Script initialized');
addDebugLog(`Screen: ${window.innerWidth}x${window.innerHeight}`);
acceptStudies();
}, 500);
// Run periodically
setInterval(acceptStudies, 500);
// Intercept fetch
const originalFetch = window.fetch;
window.fetch = function(...args) {
return originalFetch.apply(this, args).then(response => {
setTimeout(acceptStudies, 200);
return response;
});
};
// Intercept XMLHttpRequest
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(...args) {
this.addEventListener('load', () => {
setTimeout(acceptStudies, 200);
});
return originalOpen.apply(this, args);
};
console.log('Prolific Auto-Accept script loaded');
})();