Turnitin Auto Submit (Sequential + Limit 3 + Reset Data)

Chọn file -> Chọn Student lần lượt (max 3) -> Có nút Reset về 0

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Turnitin Auto Submit (Sequential + Limit 3 + Reset Data)
// @namespace    http://tampermonkey.net/
// @version      4.4
// @description  Chọn file -> Chọn Student lần lượt (max 3) -> Có nút Reset về 0
// @author       You
// @match        https://www.turnitin.com/t_submit.asp*
// @match        https://*.turnitin.com/*submit*
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // ===================== CSS =====================
    const css = `
        #turnitin-auto-panel { position: fixed; bottom: 20px; right: 20px; z-index: 9999999; font-family: sans-serif; }
        #turnitin-toggle-btn { width: 50px; height: 50px; border-radius: 50%; background: #2980b9; color: white; border: none; font-size: 24px; cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.3); display:none; }

        #turnitin-main-box { display: none; position: absolute; bottom: 0; right: 0; width: 380px; background: white; border-radius: 8px; box-shadow: 0 5px 30px rgba(0,0,0,0.4); overflow: hidden; border: 1px solid #ddd; }
        #turnitin-main-box.active { display: block; }

        .t-header { background: #2980b9; color: white; padding: 12px 15px; font-weight: bold; display: flex; justify-content: space-between; align-items: center; }
        .t-body { padding: 15px; max-height: 500px; overflow-y: auto; }

        .t-btn { width: 100%; padding: 12px; border: none; border-radius: 5px; cursor: pointer; margin-bottom: 8px; font-weight: bold; font-size: 14px; transition: background 0.2s; text-transform: uppercase; color: white; }
        .btn-select { background: #e67e22; box-shadow: 0 3px 0 #d35400; }
        .btn-select:active { box-shadow: none; transform: translateY(3px); }

        .btn-clear { background: #95a5a6; font-size: 12px; padding: 10px; }
        .btn-reset { background: #c0392b; font-size: 12px; padding: 10px; }

        .t-file-list { border: 1px solid #eee; padding: 5px; margin-bottom: 10px; border-radius: 4px; max-height: 150px; overflow-y: auto; background: #f9f9f9; }
        .t-file-item { font-size: 12px; padding: 8px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
        .t-current { background-color: #d5f5e3; font-weight:bold; color: #27ae60; border-left: 4px solid #27ae60; }

        .t-log-box { font-size: 11px; background: #34495e; color: #ecf0f1; padding: 8px; border-radius: 4px; height: 120px; overflow-y: auto; font-family: monospace; margin-top:5px; border: 1px solid #2c3e50; }
        .t-status-indicator { text-align:center; font-size: 13px; font-weight: bold; color: #7f8c8d; margin-bottom: 10px; }
        .t-running { color: #e74c3c; animation: blink 1s infinite; }

        .t-row-btn { display: flex; gap: 5px; }

        @keyframes blink { 50% { opacity: 0.5; } }
    `;
    GM_addStyle(css);

    // ===================== DATA =====================
    const firstNames = ['Nguyen', 'Tran', 'Le', 'Pham', 'Hoang', 'Huynh', 'Phan', 'Vu', 'Vo', 'Dang'];
    const lastNames = ['Van A', 'Thi B', 'Minh C', 'Duc D', 'Anh E', 'Tuan F', 'Hung G', 'Linh H'];
    let selectedFiles = [];
    let isProcessing = false;

    // ===================== HELPERS =====================
    function log(msg) {
        const box = document.getElementById('t-log-box');
        if (box) {
            const time = new Date().toLocaleTimeString();
            box.innerHTML += `<div>[${time}] ${msg}</div>`;
            box.scrollTop = box.scrollHeight;
        }
        console.log(`[AutoTurnitin] ${msg}`);
    }

    function setNativeValue(element, value) {
        if (!element) return;
        const prototype = Object.getPrototypeOf(element);
        const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
        if (prototypeValueSetter) prototypeValueSetter.call(element, value);
        else element.value = value;
        element.dispatchEvent(new Event('input', { bubbles: true }));
        element.dispatchEvent(new Event('change', { bubbles: true }));
        element.dispatchEvent(new Event('blur', { bubbles: true }));
    }

    function waitForElement(selector, timeout = 60000) {
        return new Promise((resolve, reject) => {
            const intervalTime = 500;
            let elapsed = 0;
            const check = setInterval(() => {
                const el = document.querySelector(selector);
                if (el && el.offsetParent !== null && !el.disabled) {
                    clearInterval(check);
                    resolve(el);
                }
                elapsed += intervalTime;
                if (elapsed >= timeout) {
                    clearInterval(check);
                    reject(new Error(`Timeout: ${selector}`));
                }
            }, intervalTime);
        });
    }

    function updateStatus(msg, isRunning = false) {
        const el = document.getElementById('t-status-text');
        if (el) {
            el.innerText = msg;
            el.className = `t-status-indicator ${isRunning ? 't-running' : ''}`;
        }
    }

    // ===================== LOGIC USER SELECT (SEQUENTIAL) =====================
    async function selectStudentLogic() {
        const select = document.getElementById('userID');
        if (!select) {
            log('⚠️ Không tìm thấy select box UserID. Bỏ qua chọn student.');
            return false;
        }

        const options = Array.from(select.options).filter(opt => opt.value !== "");

        if (options.length === 0) {
            log('⚠️ Danh sách Student rỗng.');
            return false;
        }

        // --- LOAD DATA ---
        let usageData = JSON.parse(localStorage.getItem('turnitin_student_usage') || '{}');
        let lastIndex = parseInt(localStorage.getItem('turnitin_last_index') || '-1');

        const getEmail = (text) => {
            const match = text.match(/\(([^)]+)\)$/);
            return match ? match[1] : text;
        };

        let foundOption = null;
        let newIndex = -1;

        // --- THUẬT TOÁN TÌM TUẦN TỰ ---
        for (let i = 1; i <= options.length; i++) {
            let currentIndex = (lastIndex + i) % options.length;
            let opt = options[currentIndex];
            let email = getEmail(opt.text);
            let usedCount = usageData[email] || 0;

            if (usedCount < 3) {
                foundOption = opt;
                newIndex = currentIndex;
                break;
            }
        }

        // --- RESET NẾU FULL ---
        if (!foundOption) {
            log('♻️ Tất cả User đã dùng 3 lần. TỰ ĐỘNG RESET VỀ 0!');
            usageData = {};
            localStorage.setItem('turnitin_student_usage', JSON.stringify(usageData));

            localStorage.setItem('turnitin_last_index', '-1');
            newIndex = 0;
            foundOption = options[0];
        }

        // --- ÁP DỤNG ---
        const chosenEmail = getEmail(foundOption.text);
        usageData[chosenEmail] = (usageData[chosenEmail] || 0) + 1;

        localStorage.setItem('turnitin_student_usage', JSON.stringify(usageData));
        localStorage.setItem('turnitin_last_index', newIndex.toString());

        log(`👤 Chọn [${newIndex + 1}/${options.length}]: ${chosenEmail} (Lần ${usageData[chosenEmail]})`);

        setNativeValue(select, foundOption.value);
        await new Promise(r => setTimeout(r, 1500));
        return true;
    }

    // ===================== LOGIC AUTOMATION =====================
    async function step1_FillAndUpload(file) {
        log(`➡️ FILE: ${file.name}`);
        const studentSelected = await selectStudentLogic();

        const inputs = {
            first: document.querySelector('input[name="author_first"]') || document.getElementById('author_first'),
            last: document.querySelector('input[name="author_last"]') || document.getElementById('author_last'),
            title: document.querySelector('input[name="title"]') || document.getElementById('title'),
            file: document.querySelector('input[type="file"]') || document.getElementById('userfile')
        };

        if (!inputs.file) throw new Error("Không tìm thấy ô input file!");

        const shouldFillName = !studentSelected || (inputs.first && !inputs.first.disabled && inputs.first.value === "");

        if (shouldFillName) {
            log('✏️ Điền tên Random...');
            if (inputs.first) setNativeValue(inputs.first, firstNames[Math.floor(Math.random() * firstNames.length)]);
            if (inputs.last) setNativeValue(inputs.last, lastNames[Math.floor(Math.random() * lastNames.length)]);
        } else {
            log('ℹ️ Tên đã được tự động điền theo Student.');
        }

        if (inputs.title) {
            let fileName = file.name;
            if (fileName.lastIndexOf('.') > 0) fileName = fileName.substring(0, fileName.lastIndexOf('.'));
            setNativeValue(inputs.title, fileName);
        }

        const dt = new DataTransfer();
        dt.items.add(file);
        inputs.file.files = dt.files;
        inputs.file.dispatchEvent(new Event('change', { bubbles: true }));

        await new Promise(r => setTimeout(r, 1500));

        const uploadBtn = document.querySelector('#upload-btn') || document.querySelector('.button_upload');
        if (uploadBtn) {
            log('🖱️ Click Upload...');
            uploadBtn.click();
        } else {
            throw new Error("Không tìm thấy nút Upload");
        }
    }

    async function step2_ConfirmAndNext() {
        try {
            log('⏳ Chờ Confirm...');
            const confirmBtn = await waitForElement('#confirm-btn');
            await new Promise(r => setTimeout(r, 1000));
            confirmBtn.click();

            log('⏳ Chờ Submit Next...');
            const nextBtn = await waitForElement('#submit-another-btn');
            await new Promise(r => setTimeout(r, 1000));
            nextBtn.click();

            log('⏳ Chờ reset form...');
            await waitForElement('#userID, #author_first');
            return true;
        } catch (e) { throw e; }
    }

    async function runAutoLoop() {
        if (selectedFiles.length === 0) {
            log('🏁 ĐÃ XONG HẾT!');
            updateStatus('SẴN SÀNG', false);
            isProcessing = false;
            return;
        }

        isProcessing = true;
        updateStatus('⛔ ĐANG CHẠY TỰ ĐỘNG...', true);

        try {
            const currentFile = selectedFiles[0];
            renderFiles();
            await step1_FillAndUpload(currentFile);
            await step2_ConfirmAndNext();
            log('✅ Xong 1 file.');
            selectedFiles.shift();
            setTimeout(runAutoLoop, 2000);
        } catch (err) {
            log(`❌ LỖI: ${err.message}`);
            updateStatus('BỊ DỪNG DO LỖI', false);
            isProcessing = false;
        }
    }

    // ===================== GUI & EVENTS =====================
    function renderFiles() {
        const container = document.getElementById('t-files-container');
        container.innerHTML = '';

        if (selectedFiles.length === 0) {
            container.innerHTML = '<div style="text-align:center; color:#999; padding:10px;">Chưa có file</div>';
            return;
        }

        selectedFiles.forEach((f, i) => {
            const div = document.createElement('div');
            div.className = `t-file-item ${i === 0 && isProcessing ? 't-current' : ''}`;
            div.innerHTML = `
                <span style="overflow:hidden; white-space:nowrap; max-width: 280px;">
                    ${i === 0 && isProcessing ? '🔄 ' : '📄 '}${f.name}
                </span>
                ${!isProcessing ? `<span class="t-remove" data-idx="${i}" style="color:red;cursor:pointer">✕</span>` : ''}
            `;
            container.appendChild(div);
        });

        if (!isProcessing) {
            container.querySelectorAll('.t-remove').forEach(btn => {
                btn.onclick = (e) => {
                    selectedFiles.splice(parseInt(e.target.getAttribute('data-idx')), 1);
                    renderFiles();
                };
            });
        }
    }

    function createGUI() {
        const wrapper = document.createElement('div');
        wrapper.id = 'turnitin-auto-panel';
        wrapper.innerHTML = `
            <div id="turnitin-main-box" class="active">
                <div class="t-header">
                    <span>🤖 Auto v4.4 (Reset Data)</span>
                    <span id="t-minimize" style="cursor:pointer; font-size:20px;">_</span>
                </div>
                <div class="t-body">
                    <div id="t-status-text" class="t-status-indicator">SẴN SÀNG</div>

                    <input type="file" id="hidden-file-picker" multiple style="display:none !important;">

                    <button class="t-btn btn-select" id="btn-trigger-select">
                        📂 CHỌN FILE & TỰ CHẠY
                    </button>

                    <div class="t-file-list" id="t-files-container">
                        <div style="text-align:center; color:#999; padding:10px;">Danh sách trống</div>
                    </div>

                    <div class="t-log-box" id="t-log-box"></div>

                    <div class="t-row-btn">
                        <button class="t-btn btn-clear" id="btn-clear-log" style="width:50%">
                            🧹 XÓA LOG VIEW
                        </button>
                        <button class="t-btn btn-reset" id="btn-reset-data" style="width:50%">
                            ♻️ RESET DATA (Về 0)
                        </button>
                    </div>
                </div>
            </div>
            <button id="turnitin-toggle-btn" title="Mở lại">🤖</button>
        `;

        document.body.appendChild(wrapper);

        // UI Logic
        const box = document.getElementById('turnitin-main-box');
        const toggleBtn = document.getElementById('turnitin-toggle-btn');
        document.getElementById('t-minimize').onclick = () => { box.classList.remove('active'); toggleBtn.style.display = 'block'; };
        toggleBtn.onclick = () => { box.classList.add('active'); toggleBtn.style.display = 'none'; };

        // File Selection
        const hiddenInput = document.getElementById('hidden-file-picker');
        const triggerBtn = document.getElementById('btn-trigger-select');
        triggerBtn.onclick = () => hiddenInput.click();

        hiddenInput.onchange = (e) => {
            const newFiles = Array.from(e.target.files);
            if (newFiles.length > 0) {
                selectedFiles = selectedFiles.concat(newFiles);
                renderFiles();
                hiddenInput.value = '';
                if (!isProcessing) {
                    log('🚀 Kích hoạt chạy tự động...');
                    runAutoLoop();
                }
            }
        };

        // === NÚT XÓA LOG VIEW ===
        document.getElementById('btn-clear-log').onclick = () => {
            document.getElementById('t-log-box').innerHTML = '<div>[LOG] Đã xóa màn hình log.</div>';
            console.clear();
        };

        // === NÚT RESET DATA (QUAN TRỌNG) ===
        document.getElementById('btn-reset-data').onclick = () => {
            if(confirm("Bạn có chắc muốn Reset bộ đếm về 0?\n(Lần chạy tới sẽ bắt đầu lại từ User đầu tiên)")) {
                localStorage.removeItem('turnitin_student_usage');
                localStorage.removeItem('turnitin_last_index');

                // Clear luôn màn hình cho sạch
                document.getElementById('t-log-box').innerHTML = '<div>[RESET] Đã xóa dữ liệu lưu trữ.</div>';
                document.getElementById('t-log-box').innerHTML += '<div>[INFO] Lần chạy tới sẽ bắt đầu từ User đầu tiên.</div>';
                console.log('[AutoTurnitin] DATA RESET SUCCESSFUL.');
            }
        };
    }

    if (document.readyState === 'complete') {
        createGUI();
    } else {
        window.addEventListener('load', createGUI);
    }

})();