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 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         TTV Auto Upload
// @namespace    http://tampermonkey.net/
// @version      2.7
// @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';

    // CSS cho thông báo và control panel
    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: 50px;
            right: 20px;
            background: white;
            padding: 25px;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
            z-index: 9998;
            width: 500px;
            margin-bottom: 20px;
            transition: all 0.3s ease;
        }
        .ttv-control-panel.minimized {
            width: auto;
            height: auto;
            padding: 10px;
            opacity: 0.8;
            transform: translateX(calc(100% - 40px));
            transition: all 0.3s ease;
        }
        .ttv-control-panel.minimized:hover {
            opacity: 1;
            transform: translateX(0);
        }
        .ttv-control-panel.minimized .ttv-button-group,
        .ttv-control-panel.minimized .ttv-header {
            display: none;
        }
        .ttv-button-group {
            display: flex;
            flex-direction: column;
            gap: 15px;
        }
        .ttv-content-editor {
            width: 100%;
            height: 100px;
            margin: 6px 0;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 13px;
            line-height: 1.4;
            resize: vertical;
            transition: all 0.2s ease;
        }
        .ttv-content-editor:focus {
            border-color: #5bc0de;
            outline: none;
            box-shadow: 0 0 8px rgba(91,192,222,0.2);
        }
        .ttv-preview {
            display: none;
            width: 100%;
            height: 100px;
            margin: 6px 0;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 13px;
            line-height: 1.4;
            overflow-y: auto;
            background: #f9f9f9;
            transition: all 0.2s ease;
        }
        .ttv-heading {
            font-size: 15px;
            color: #555;
            margin: 12px 0;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .ttv-word-count {
            font-size: 12px;
            color: #888;
            padding: 3px 8px;
            background: #f5f5f5;
            border-radius: 4px;
            transition: all 0.2s ease;
        }
        .ttv-chapter-list {
            width: 100%;
            margin: 10px 0;
            max-height: 200px;
            overflow-y: auto;
            border: 1px solid #eee;
            border-radius: 6px;
            padding: 8px;
        }
        .ttv-chapter-item {
            padding: 8px;
            border-bottom: 1px solid #eee;
            cursor: pointer;
            transition: all 0.2s ease;
            line-height: 1.4;
            font-size: 12px;
            color: #666;
        }
        .ttv-chapter-item .chapter-title {
            font-weight: bold;
            margin-bottom: 4px;
        }
        .ttv-chapter-item .chapter-name {
            color: #888;
            padding-left: 10px;
            border-left: 2px solid #ddd;
            margin: 4px 0;
        }
        .ttv-chapter-item .chapter-stats {
            font-size: 11px;
            color: #999;
        }
        .ttv-chapter-item:last-child {
            border-bottom: none;
        }
        .ttv-chapter-item.selected {
            background: #f0f8ff;
            border-left: 2px solid #5bc0de;
        }
        .ttv-control-panel.fullscreen {
            position: fixed;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 100%;
            border-radius: 0;
            z-index: 9999;
            padding: 15px;
            display: flex;
            flex-direction: column;
        }
        .ttv-control-panel.fullscreen .ttv-content-editor,
        .ttv-control-panel.fullscreen .ttv-preview {
            height: calc(100vh - 250px);
            margin: 15px 0;
            font-size: 16px;
        }
        .ttv-header {
            margin-bottom: 20px;
            padding-bottom: 15px;
            border-bottom: 2px solid #eee;
            font-weight: bold;
            font-size: 16px;
            color: #444;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        button.btn-warning {
            background: #f0ad4e;
            color: white;
            border: none;
            padding: 12px;
            border-radius: 6px;
            width: 100%;
            font-size: 14px;
            transition: all 0.2s ease;
        }
        button.btn-warning:hover {
            background: #ec971f;
            transform: translateY(-1px);
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        button.ttv-minimize {
            padding: 2px 8px;
            background: none;
            border: none;
            cursor: pointer;
            font-size: 16px;
            color: #666;
            transition: all 0.2s ease;
        }
        button.ttv-minimize:hover {
            color: #333;
        }
        .chapter-form {
            margin-bottom: 20px;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 8px;
        }
        .chapter-form h3 {
            margin: 0 0 10px;
            font-size: 16px;
            color: #333;
        }
        .form-row {
            display: flex;
            gap: 10px;
            margin-bottom: 10px;
        }
        .form-row input {
            flex: 1;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .chapter-form textarea {
            width: 100%;
            min-height: 100px;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            resize: vertical;
        }
        .ttv-forms-container {
            max-height: 600px;
            overflow-y: auto;
            padding: 10px;
            margin: 10px 0;
            border: 1px solid #eee;
            border-radius: 8px;
        }
    `;
    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);
    }

    // Parse chapters function update
    function parseChapters(content) {
        const lines = content.split('\n');
        const chapters = [];
        let currentChapter = {
            title: '',
            name: '',
            content: []
        };

        const chapterPattern = /^\s*Chương\s+\d+:/;
        let previousChapterTitle = '';

        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trim();

            if (chapterPattern.test(line)) {
                // Kiểm tra xem có phải là tiêu đề trùng lặp không
                if (line !== previousChapterTitle) {
                    // Log để debug
                    console.log(`Tìm thấy chương mới: ${line}`);

                    // Nếu đã có chương trước đó, lưu lại
                    if (currentChapter.title && currentChapter.content.length > 0) {
                        chapters.push({...currentChapter});
                        console.log(`Đã lưu chương: ${currentChapter.title}\nTên: ${currentChapter.name}\nSố dòng: ${currentChapter.content.length}`);
                        currentChapter = {title: '', name: '', content: []};
                    }

                    // Lưu tiêu đề chương hiện tại
                    currentChapter.title = line;
                    // Lấy tên chương sau dấu :
                    let name = line.split(':')[1]?.trim() || '';
                    // Xóa dấu ., , hoặc ; ở đầu và cuối tên chương
                    name = name.replace(/^[.,;'"]+|[.,;'"]+$/g, '').trim();
                    currentChapter.name = name;
                    previousChapterTitle = line;

                    console.log(`Đang xử lý chương mới:\nTiêu đề: ${line}\nTên chương: ${name}`);
                } else {
                    console.log(`Bỏ qua tiêu đề trùng lặp: ${line}`);
                }
            } else {
                // Lưu tất cả nội dung không phải tiêu đề chương
                if (line) { // Chỉ thêm dòng không trống
                    if (currentChapter.title) {
                        currentChapter.content.push(line);
                    }
                }
            }
        }

        // Thêm chương cuối cùng nếu có
        if (currentChapter.title && currentChapter.content.length > 0) {
            chapters.push({...currentChapter});
        }

        // Tự động tạo form và điền nội dung
        if (chapters.length > 0) {
            // Tạo số form bằng với số chương tìm thấy
            createChapterForms(chapters.length);
            // Hiển thị danh sách chương
            displayChapters(chapters);
            // Tự động điền form
            setTimeout(() => {
                try {
                    transferContent();
                } catch (error) {
                    console.error('Lỗi khi tự động điền form:', error);
                    showNotification('Có lỗi xảy ra khi tự động điền form!', true);
                }
            }, 1000);
        }

        console.log(`Tổng số chương tìm thấy: ${chapters.length}`);
        return chapters;
    }

    // Chọn chương để hiển thị
    function selectChapter(chapter, index) {
        const contentEditor = document.querySelector('.ttv-content-editor');
        contentEditor.value = chapter.title + '\n' + chapter.content.join('\n');

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

        // Highlight selected chapter
        const chapterItems = document.querySelectorAll('.ttv-chapter-item');
        chapterItems.forEach(item => item.classList.remove('selected'));
        chapterItems[index]?.classList.add('selected');

        // Tự động điền form khi chọn chương
        try {
            transferContent();
        } catch (error) {
            console.error('Lỗi khi tự động điền form:', error);
            showNotification('Có lỗi xảy ra khi tự động điền form!', true);
        }
    }

    // Hiển thị danh sách chương
    function displayChapters(chapters) {
        const chapterList = document.createElement('div');
        chapterList.className = 'ttv-chapter-list';

        chapters.forEach((chapter, index) => {
            const chapterItem = document.createElement('div');
            chapterItem.className = 'ttv-chapter-item';

            // Store chapter content
            chapterItem._content = chapter.title + '\n' + chapter.content.join('\n');

            // Tạo các phần tử con với định dạng riêng
            const titleDiv = document.createElement('div');
            titleDiv.className = 'chapter-title';
            titleDiv.textContent = chapter.title;

            const nameDiv = document.createElement('div');
            nameDiv.className = 'chapter-name';
            nameDiv.textContent = `Tên chương: ${chapter.name}`;

            const statsDiv = document.createElement('div');
            statsDiv.className = 'chapter-stats';
            statsDiv.textContent = `${chapter.content.length} dòng`;

            // Thêm các phần tử vào item
            chapterItem.appendChild(titleDiv);
            chapterItem.appendChild(nameDiv);
            chapterItem.appendChild(statsDiv);

            chapterItem.onclick = () => selectChapter(chapter, index);
            chapterList.appendChild(chapterItem);

            // Select first chapter by default
            if (index === 0) {
                chapterItem.classList.add('selected');
            }
        });

        const existingList = document.querySelector('.ttv-chapter-list');
        if (existingList) {
            existingList.remove();
        }

        const contentEditor = document.querySelector('.ttv-content-editor');
        contentEditor.parentNode.insertBefore(chapterList, contentEditor);

        // Hiển thị thông báo về số chương tìm thấy
        showNotification(`Đã tìm thấy ${chapters.length} chương và tự động điền vào form.${chapters.length >= 10 ? ' (Giới hạn 10 chương đầu tiên)' : ''}`);

        // Tự động chọn chương đầu tiên
        if (chapters.length > 0) {
            selectChapter(chapters[0], 0);
        }
    }

    // Đế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ự`;
    }

    // Chuyển nội dung từ khung soạn thảo sang form
    function transferContent() {
        try {
            // Get all chapters
            const chapterItems = document.querySelectorAll('.ttv-chapter-item');

            // Fill each chapter's content into corresponding form
            chapterItems.forEach((chapterItem, index) => {
                const formIndex = index + 1;
                const formFields = {
                    chapterName: document.querySelector(`input[name="chap_name[${formIndex}]"]`),
                    content: document.querySelector(`textarea[name="introduce[${formIndex}]"]`),
                    chapterNumber: document.querySelector(`input[name="chap_number[${formIndex}]"]`),
                    chapterOrder: document.querySelector(`input[name="chap_stt[${formIndex}]"]`),
                    volume: document.querySelector(`input[name="vol[${formIndex}]"]`)
                };

                // Verify all form fields exist
                Object.entries(formFields).forEach(([fieldName, element]) => {
                    if (!element) {
                        throw new Error(`Không tìm thấy trường "${fieldName}" cho chương ${formIndex}`);
                    }
                });

                // Get chapter info
                const chapterTitle = chapterItem.querySelector('.chapter-title').textContent;
                const chapterName = chapterItem.querySelector('.chapter-name').textContent.replace('Tên chương: ', '');
                const chapterNumber = chapterTitle.match(/Chương\s+(\d+)/)?.[1] || formIndex.toString();

                // Fill form fields
                formFields.chapterName.value = chapterName;
                formFields.chapterNumber.value = chapterNumber;
                formFields.chapterOrder.value = chapterNumber;
                formFields.volume.value = '1';

                // Get content for this chapter
                if (index === 0) { // If it's the first/selected chapter
                    formFields.content.value = document.querySelector('.ttv-content-editor').value;
                } else {
                    formFields.content.value = chapterItem._content || ''; // Use stored content
                }

                console.log(`Đã điền form cho chương ${formIndex}:`, {
                    title: chapterTitle,
                    name: chapterName,
                    number: chapterNumber,
                    contentLength: formFields.content.value.length
                });
            });

            showNotification(`Đã tự động điền ${chapterItems.length} chương vào form!`);
            console.log(`Đã điền ${chapterItems.length} chương vào form`);

        } catch (error) {
            console.error('Lỗi khi điền form:', error);
            showNotification('Có lỗi xảy ra khi điền form: ' + error.message, true);
            throw error;
        }
    }

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

    // Function to create form fields for multiple chapters using website's template
    function createChapterForms(totalChapters) {
        try {
            // Find the original form container
            const originalForm = document.querySelector('form');
            if (!originalForm) {
                throw new Error('Không tìm thấy form gốc trên trang web');
            }

            // Get original chapter form template
            const originalChapterDiv = document.querySelector('div[id^="div-chapter"]');
            if (!originalChapterDiv) {
                throw new Error('Không tìm thấy mẫu form chương gốc');
            }

            // Debug log template structure
            console.log('Template form structure:', {
                id: originalChapterDiv.id,
                fields: Array.from(originalChapterDiv.querySelectorAll('input, textarea')).map(el => ({
                    name: el.getAttribute('name'),
                    type: el.tagName.toLowerCase()
                }))
            });

            // Clear any existing additional chapter divs
            const existingChapters = document.querySelectorAll('div[id^="div-chapter"]');
            console.log(`Xóa ${existingChapters.length - 1} form cũ`);
            existingChapters.forEach((div, index) => {
                if (index > 0) { // Keep the first one as template
                    div.remove();
                }
            });

            // Create new chapter divs based on template
            for (let i = 1; i <= totalChapters; i++) {
                const newChapterDiv = originalChapterDiv.cloneNode(true);
                const newId = `div-chapter-${i}`;
                newChapterDiv.id = newId;

                // Update all input fields with proper indices
                newChapterDiv.querySelectorAll('input, textarea').forEach(input => {
                    const name = input.getAttribute('name');
                    if (name) {
                        const oldIndex = name.match(/\[(\d+)\]/)?.[1];
                        const newName = name.replace(/\[\d+\]/, `[${i}]`);
                        console.log(`Cập nhật trường ${name} -> ${newName} trong form ${i}`);

                        // Update the index in the name attribute
                        input.setAttribute('name', newName);
                        // Clear any existing values
                        input.value = '';
                    }
                });

                // Update any labels or headers
                newChapterDiv.querySelectorAll('label, .chapter-header').forEach(el => {
                    const oldText = el.textContent;
                    const newText = el.textContent.replace(/\d+/, i);
                    console.log(`Cập nhật label ${oldText} -> ${newText} trong form ${i}`);
                    el.textContent = newText;
                });

                // Append the new chapter div to the form
                originalForm.appendChild(newChapterDiv);
                console.log(`Đã tạo form chương ${i} với ID ${newId}`);
            }

            console.log(`Đã tạo ${totalChapters} form chương dựa trên mẫu của trang web`);
            showNotification(`Đã tạo ${totalChapters} form chương`);

        } catch (error) {
            console.error('Lỗi khi tạo form chương:', error);
            showNotification('Có lỗi xảy ra khi tạo form chương: ' + error.message, true);
            throw error;
        }
    }



    // 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>Soạn Thảo Nội Dung</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>
        `;

        const buttonGroup = document.createElement('div');
        buttonGroup.className = 'ttv-button-group';

        // 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ừ khi nhập nội dung
        contentEditor.oninput = () => {
            const wordCountSpan = document.querySelector('.ttv-word-count');
            if (wordCountSpan) {
                wordCountSpan.textContent = updateWordCount(contentEditor.value);
            }
        };

        // Xử lý khi paste nội dung
        contentEditor.onpaste = (e) => {
            // Cho phép paste hoàn tất
            setTimeout(() => {
                const content = contentEditor.value;
                const chapters = parseChapters(content);
                if (chapters.length > 0) {
                    displayChapters(chapters);
                    // Tự động điền form sau khi hiển thị danh sách chương
                    setTimeout(() => {
                        try {
                            transferContent();
                        } catch (error) {
                            console.error('Lỗi khi tự động điền form:', error);
                            showNotification('Có lỗi xảy ra khi tự động điền form!', true);
                        }
                    }, 1000); // Đợi 1s để đảm bảo UI đã được cập nhật hoàn toàn
                }
            }, 0);
        };

        // 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;

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

        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 = () => {
            panel.classList.toggle('minimized');
            minimizeBtn.innerHTML = panel.classList.contains('minimized') ? '+' : '−';
        };

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

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