Prolific Auto-Accept Studies

Automatically accept studies on Prolific with control menu

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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