Prolific Auto-Accept Studies

Automatically accept studies on Prolific with control menu

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Advertisement:

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

Advertisement:

// ==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');
})();