TTV Auto Upload

Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao

当前为 2025-03-08 提交的版本,查看 最新版本

// ==UserScript==
// @name         TTV Auto Upload
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao
// @author       HA
// @match        https://tangthuvien.net/dang-chuong/story/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Thêm CSS cho thông báo và nút
    const style = document.createElement('style');
    style.textContent = `
        .ttv-notification {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 10px 20px;
            background: #4CAF50;
            color: white;
            border-radius: 4px;
            z-index: 9999;
            display: none;
        }
        .ttv-error {
            background: #f44336;
        }
        .ttv-control-panel {
            position: fixed;
            top: 70px;
            right: 20px;
            background: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            z-index: 9998;
            width: 250px;
        }
        .ttv-button-group {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        .ttv-button-group button,
        .ttv-file-label {
            width: 100%;
            padding: 8px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            text-align: center;
        }
        .ttv-file-input {
            display: none;
        }
        .ttv-file-label {
            background: #5bc0de;
            color: white;
            margin: 0;
        }
        .ttv-file-label:hover {
            background: #46b8da;
        }
        .ttv-header {
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid #eee;
            font-weight: bold;
            text-align: center;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        button.ttv-minimize {
            position: absolute;
            top: 10px;
            right: 10px;
            background: none;
            border: none;
            cursor: pointer;
            padding: 0;
            font-size: 18px;
            color: #666;
        }
        .ttv-control-panel.minimized {
            width: auto;
            height: auto;
            padding: 10px;
        }
        .ttv-control-panel.minimized .ttv-button-group,
        .ttv-control-panel.minimized .ttv-header {
            display: none;
        }
        .ttv-content-editor {
            width: 100%;
            height: 300px;
            margin: 10px 0;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            line-height: 1.5;
            resize: vertical;
        }
        .ttv-heading {
            font-size: 12px;
            color: #666;
            margin: 5px 0;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .ttv-word-count {
            font-size: 11px;
            color: #999;
        }
        .ttv-preview {
            display: none;
            width: 100%;
            height: 300px;
            margin: 10px 0;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            line-height: 1.5;
            overflow-y: auto;
            background: #f9f9f9;
        }
        .ttv-control-panel.fullscreen {
            position: fixed;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 100%;
            border-radius: 0;
            z-index: 9999;
        }
        .ttv-control-panel.fullscreen .ttv-content-editor,
        .ttv-control-panel.fullscreen .ttv-preview {
            height: calc(100vh - 250px);
        }
        .ttv-toolbar {
            display: flex;
            gap: 5px;
        }
        .ttv-toolbar button {
            padding: 2px 8px;
            font-size: 12px;
            color: #666;
            background: none;
            border: 1px solid #ddd;
            border-radius: 3px;
            cursor: pointer;
        }
        .ttv-toolbar button:hover {
            background: #f0f0f0;
        }
    `;
    document.head.appendChild(style);

    // Tạo div thông báo
    const notification = document.createElement('div');
    notification.className = 'ttv-notification';
    document.body.appendChild(notification);

    // Hiển thị thông báo
    function showNotification(message, isError = false) {
        notification.textContent = message;
        notification.className = 'ttv-notification' + (isError ? ' ttv-error' : '');
        notification.style.display = 'block';
        setTimeout(() => {
            notification.style.display = 'none';
        }, 3000);
    }

    // Đọc nội dung file
    function readFileContent(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = (e) => resolve(e.target.result);
            reader.onerror = (e) => reject(new Error('Lỗi đọc file: ' + e.target.error));
            reader.readAsText(file, 'UTF-8');
        });
    }

    // Đếm số từ và ký tự
    function updateWordCount(content) {
        const wordCount = content.trim().split(/\s+/).length;
        const charCount = content.length;
        return `${wordCount} từ | ${charCount} ký tự`;
    }

    // Tự động lưu nội dung
    let autoSaveTimeout;
    function autoSaveContent(content) {
        clearTimeout(autoSaveTimeout);
        autoSaveTimeout = setTimeout(() => {
            localStorage.setItem('ttv_autosave_content', content);
            showNotification('Đã tự động lưu nội dung');
        }, 2000);
    }

    // Chuyển đổi giữa chế độ soạn thảo và xem trước
    function togglePreview() {
        const contentEditor = document.querySelector('.ttv-content-editor');
        const preview = document.querySelector('.ttv-preview');
        const previewBtn = document.querySelector('.ttv-preview-btn');

        if (contentEditor.style.display !== 'none') {
            contentEditor.style.display = 'none';
            preview.style.display = 'block';
            preview.innerHTML = contentEditor.value.replace(/\n/g, '<br>');
            previewBtn.textContent = 'Soạn thảo';
        } else {
            contentEditor.style.display = 'block';
            preview.style.display = 'none';
            previewBtn.textContent = 'Xem trước';
        }
    }

    // Chuyển đổi chế độ toàn màn hình
    function toggleFullscreen() {
        const panel = document.querySelector('.ttv-control-panel');
        const fullscreenBtn = document.querySelector('.ttv-fullscreen-btn');

        panel.classList.toggle('fullscreen');
        fullscreenBtn.textContent = panel.classList.contains('fullscreen') ? 'Thu nhỏ' : 'Toàn màn hình';
    }

    // Xử lý khi chọn file
    async function handleFileSelect(event) {
        try {
            const file = event.target.files[0];
            if (!file) return;

            // Đọc nội dung file
            const content = await readFileContent(file);

            // Điền vào khung soạn thảo nội dung
            const contentEditor = document.querySelector('.ttv-content-editor');
            if (contentEditor) {
                contentEditor.value = content;

                // Cập nhật số từ
                const wordCountSpan = document.querySelector('.ttv-word-count');
                if (wordCountSpan) {
                    wordCountSpan.textContent = updateWordCount(content);
                }

                // Tự động lưu nội dung
                autoSaveContent(content);

                // Thử tự động lấy tên chương từ dòng đầu tiên
                const firstLine = content.split('\n')[0].trim();
                if (firstLine.includes('Chương')) {
                    const chapterNameInput = document.querySelector('input[name="chap_name[1]"]');
                    if (chapterNameInput) {
                        chapterNameInput.value = firstLine;
                    }
                }

                showNotification('Đã tải nội dung từ file thành công!');
            } else {
                showNotification('Không tìm thấy khung soạn thảo!', true);
            }
        } catch (error) {
            console.error('Lỗi xử lý file:', error);
            showNotification('Có lỗi xảy ra khi đọc file!', true);
        }
    }

    // Chuyển nội dung từ khung soạn thảo sang form
    function transferContent() {
        const contentEditor = document.querySelector('.ttv-content-editor');
        const contentInput = document.querySelector('textarea[name="introduce[1]"]');

        if (contentEditor && contentInput) {
            contentInput.value = contentEditor.value;
            showNotification('Đã chuyển nội dung sang form đăng chương!');
        } else {
            showNotification('Không tìm thấy form đăng chương!', true);
        }
    }

    // Thêm panel điều khiển
    function addControlPanel() {
        // Tạo panel
        const panel = document.createElement('div');
        panel.className = 'ttv-control-panel';

        // Thêm header
        const header = document.createElement('div');
        header.className = 'ttv-header';
        header.innerHTML = `
            <div>Công Cụ Đăng Chương</div>
            <div class="ttv-toolbar">
                <button class="ttv-preview-btn" onclick="togglePreview()">Xem trước</button>
                <button class="ttv-fullscreen-btn" onclick="toggleFullscreen()">Toàn màn hình</button>
                <button class="ttv-minimize">−</button>
            </div>
        `;

        // Container cho các nút
        const buttonGroup = document.createElement('div');
        buttonGroup.className = 'ttv-button-group';

        // Nút tự động điền
        const autoFillBtn = document.createElement('button');
        autoFillBtn.type = 'button';
        autoFillBtn.className = 'btn btn-primary';
        autoFillBtn.innerHTML = '<span>Tự động điền số chương</span>';
        autoFillBtn.onclick = autoFillForm;

        // Input chọn file
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = '.txt';
        fileInput.className = 'ttv-file-input';
        fileInput.id = 'ttv-file-input';
        fileInput.onchange = handleFileSelect;

        // Label cho input file
        const fileLabel = document.createElement('label');
        fileLabel.htmlFor = 'ttv-file-input';
        fileLabel.className = 'ttv-file-label';
        fileLabel.innerHTML = '<span>Chọn file txt chứa nội dung</span>';

        // Khung soạn thảo nội dung
        const contentEditorLabel = document.createElement('div');
        contentEditorLabel.className = 'ttv-heading';
        contentEditorLabel.innerHTML = `
            Nội dung chương:
            <span class="ttv-word-count">0 từ | 0 ký tự</span>
        `;

        const contentEditor = document.createElement('textarea');
        contentEditor.className = 'ttv-content-editor';
        contentEditor.placeholder = 'Nhập hoặc dán nội dung chương vào đây...';

        // Khung xem trước
        const preview = document.createElement('div');
        preview.className = 'ttv-preview';

        // Cập nhật số từ và tự động lưu khi nhập nội dung
        contentEditor.oninput = () => {
            const wordCountSpan = document.querySelector('.ttv-word-count');
            if (wordCountSpan) {
                wordCountSpan.textContent = updateWordCount(contentEditor.value);
            }
            autoSaveContent(contentEditor.value);
        };

        // Nút chuyển nội dung
        const transferBtn = document.createElement('button');
        transferBtn.type = 'button';
        transferBtn.className = 'btn btn-warning';
        transferBtn.innerHTML = '<span>Chuyển nội dung sang form</span>';
        transferBtn.onclick = transferContent;

        // Nút lưu cấu hình
        const saveConfigBtn = document.createElement('button');
        saveConfigBtn.type = 'button';
        saveConfigBtn.className = 'btn btn-success';
        saveConfigBtn.innerHTML = '<span>Lưu cấu hình chương</span>';
        saveConfigBtn.onclick = saveChapterConfig;

        // Nút tải cấu hình
        const loadConfigBtn = document.createElement('button');
        loadConfigBtn.type = 'button';
        loadConfigBtn.className = 'btn btn-info';
        loadConfigBtn.innerHTML = '<span>Tải cấu hình đã lưu</span>';
        loadConfigBtn.onclick = loadChapterConfig;

        // Thêm các phần tử vào panel
        panel.appendChild(header);
        buttonGroup.appendChild(autoFillBtn);
        buttonGroup.appendChild(fileInput);
        buttonGroup.appendChild(fileLabel);
        buttonGroup.appendChild(contentEditorLabel);
        buttonGroup.appendChild(contentEditor);
        buttonGroup.appendChild(preview);
        buttonGroup.appendChild(transferBtn);
        buttonGroup.appendChild(saveConfigBtn);
        buttonGroup.appendChild(loadConfigBtn);
        panel.appendChild(buttonGroup);

        // Khôi phục nội dung đã lưu tự động
        const savedContent = localStorage.getItem('ttv_autosave_content');
        if (savedContent) {
            contentEditor.value = savedContent;
            const wordCountSpan = document.querySelector('.ttv-word-count');
            if (wordCountSpan) {
                wordCountSpan.textContent = updateWordCount(savedContent);
            }
        }

        document.body.appendChild(panel);

        // Thêm xử lý sự kiện cho các nút trong toolbar
        const minimizeBtn = panel.querySelector('.ttv-minimize');
        minimizeBtn.onclick = () => {
            if (panel.classList.contains('minimized')) {
                panel.classList.remove('minimized');
                minimizeBtn.innerHTML = '−';
            } else {
                panel.classList.add('minimized');
                minimizeBtn.innerHTML = '+';
            }
        };

        window.togglePreview = togglePreview;
        window.toggleFullscreen = toggleFullscreen;
    }

    // Tự động điền form
    function autoFillForm() {
        try {
            // Kiểm tra CSRF token
            const tokenInput = document.querySelector('input[name="_token"]');
            if (!tokenInput) {
                showNotification('Không tìm thấy token bảo mật!', true);
                return;
            }

            // Lấy thông tin chương hiện tại
            const chap_stt = document.querySelector('.chap_stt1')?.value;
            const chap_serial = document.querySelector('.chap_serial')?.value;
            const chap_vol = document.querySelector('.chap_vol')?.value;
            const chap_vol_name = document.querySelector('.chap_vol_name')?.value;

            if (!chap_stt || !chap_serial) {
                showNotification('Không tìm thấy thông tin chương!', true);
                return;
            }

            // Điền các trường
            const fields = {
                'chap_stt[1]': chap_stt,
                'chap_number[1]': chap_serial,
                'vol[1]': chap_vol || '1',
                'vol_name[1]': chap_vol_name || '',
                'chap_name[1]': `Chương ${chap_serial}`
            };

            for (const [name, value] of Object.entries(fields)) {
                const input = document.querySelector(`input[name="${name}"]`);
                if (input) {
                    input.value = value;
                }
            }

            // Focus vào ô nội dung
            const contentInput = document.querySelector('textarea[name="introduce[1]"]');
            if (contentInput) {
                contentInput.focus();
                showNotification('Đã điền thông tin chương thành công!');
            } else {
                showNotification('Không tìm thấy ô nhập nội dung!', true);
            }
        } catch (error) {
            console.error('Lỗi khi tự động điền form:', error);
            showNotification('Có lỗi xảy ra khi điền form!', true);
        }
    }

    // Lưu cấu hình chương
    function saveChapterConfig() {
        try {
            const config = {
                chap_stt: document.querySelector('.chap_stt1')?.value,
                chap_serial: document.querySelector('.chap_serial')?.value,
                chap_vol: document.querySelector('.chap_vol')?.value,
                chap_vol_name: document.querySelector('.chap_vol_name')?.value
            };

            localStorage.setItem('ttv_chapter_config', JSON.stringify(config));
            showNotification('Đã lưu cấu hình chương thành công!');
        } catch (error) {
            console.error('Lỗi khi lưu cấu hình:', error);
            showNotification('Có lỗi xảy ra khi lưu cấu hình!', true);
        }
    }

    // Tải cấu hình chương
    function loadChapterConfig() {
        try {
            const savedConfig = localStorage.getItem('ttv_chapter_config');
            if (!savedConfig) {
                showNotification('Không tìm thấy cấu hình đã lưu!', true);
                return;
            }

            const config = JSON.parse(savedConfig);

            // Cập nhật các trường
            const fields = {
                '.chap_stt1': config.chap_stt,
                '.chap_serial': config.chap_serial,
                '.chap_vol': config.chap_vol,
                '.chap_vol_name': config.chap_vol_name
            };

            for (const [selector, value] of Object.entries(fields)) {
                const input = document.querySelector(selector);
                if (input && value) {
                    input.value = value;
                }
            }

            showNotification('Đã tải cấu hình chương thành công!');
        } catch (error) {
            console.error('Lỗi khi tải cấu hình:', error);
            showNotification('Có lỗi xảy ra khi tải cấu hình!', true);
        }
    }

    // Thêm control panel khi trang đã load
    window.addEventListener('load', function() {
        addControlPanel();
    });
})();