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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
    }

})();