TTV Auto Upload

Công cụ đăng chương hiện đại cho Tàng Thư Viện với UI/UX được tối ưu

当前为 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      5.8
// @description  Công cụ đăng chương hiện đại cho Tàng Thư Viện với UI/UX được tối ưu
// @author       HA
// @match        https://tangthuvien.net/dang-chuong/story/*
// @match        https://tangthuvien.net/danh-sach-chuong/story/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @required     https://code.jquery.com/jquery-3.2.1.min.js
// ==/UserScript==

(function() {
    'use strict';
    if (window.location.href.includes('/danh-sach-chuong/story/')) {
        const storyId = window.location.pathname.split('/').pop();
        setTimeout(() => {
            window.location.href = `https://tangthuvien.net/dang-chuong/story/${storyId}`;
        }, 3000);
        return;
    }

    const HEADER_SIGN = "";
    const FOOTER_SIGN = "";
    const MAX_CHAPTER_POST = 10;
    GM_addStyle(`
        /* Vị trí và giao diện của công cụ đăng nhanh */
        #modern-uploader {
            background-color: white;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
            position: fixed;
            right: 20px;
            top: 50%;
            transform: translateY(-50%);
            width: 400px;
            max-height: 90vh;
            overflow-y: auto;
            z-index: 1000;
        }
        @keyframes shortChapterBlink {
            0% { background-color: rgba(255, 0, 0, 0.1); }
            50% { background-color: rgba(255, 0, 0, 0.2); }
            100% { background-color: rgba(255, 0, 0, 0.1); }
        }
        textarea[name^="introduce"] {
            transition: all 0.3s ease;
        }
        textarea[name^="introduce"].short-chapter {
            animation: shortChapterBlink 1s infinite;
            border: 2px solid #ff0000 !important;
            background-color: rgba(255, 0, 0, 0.1) !important;
        }
        .chapter-character-count {
            text-align: right;
            font-size: 12px;
            margin-top: 5px;
            color: #666;
        }
        .short-chapters-warning {
            color: #ff0000;
            font-weight: bold;
            animation: shortChapterBlink 1s infinite;
        }

        .button-container {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 15px;
            margin-top: 15px;
        }
        #modern-uploader .btn {
            padding: 10px 20px;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            font-size: 14px;
            transition: all 0.2s ease;
        }
        #modern-uploader .form-control {
            width: 100%;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 8px;
            margin-bottom: 15px;
            font-size: 16px;
            transition: border-color 0.2s ease;
        }
        #modern-uploader .form-control:focus {
            border-color: #4285f4;
            outline: none;
        }
    `);
    function showNotification(message, type) {
        jQuery('#modern-uploader .notification-container').remove();
        const container = jQuery("<div>", {
            class: "notification-container",
            css: {
                width: "100%",
                padding: "10px 0",
                marginTop: "10px",
                textAlign: "left",
                borderTop: "1px solid rgba(0,0,0,0.1)"
            }
        });
        const notification = jQuery("<div>", {
            class: `notification-${type}`,
            css: {
                backgroundColor: "#e8f5e9",
                color: "#000000",
                padding: "10px 15px",
                borderRadius: "8px",
                fontSize: "14px",
                fontWeight: "500",
                boxShadow: "0 4px 10px rgba(0,0,0,0.15)",
                display: "inline-block",
                maxWidth: "90%",
                margin: "0",
                wordBreak: "break-word",
                border: "1px solid #81c784"
            }
        });
        const lines = message.split('\n');
        lines.forEach((line, index) => {
            notification.append(jQuery("<div>").html(line));
        });
        container.append(notification);
        jQuery("#modern-uploader .button-container").after(container);
        notification.fadeIn(300);
    }
    function createChapterHTML(chapNum) {
        const chap_vol = parseInt(jQuery('.chap_vol').val()) || 1;
        const chap_vol_name = jQuery('.chap_vol_name').val() || '';
        return `
        <div data-gen="MK_GEN" id="COUNT_CHAP_${chapNum}_MK">
            <div class="col-xs-12 form-group"></div>
            <div class="form-group">
                <label class="col-sm-2" for="chap_stt">STT</label>
                <div class="col-sm-8">
                    <input class="form-control" required name="chap_stt[${chapNum}]" value="${dăngnhanhTTV.STATE.CHAP_STT}" placeholder="Số thứ tự của chương" type="text"/>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="chap_number">Chương thứ..</label>
                <div class="col-sm-8">
                    <input value="${dăngnhanhTTV.STATE.CHAP_SERIAL}" required class="form-control" name="chap_number[${chapNum}]" placeholder="Chương thứ.. (1,2,3..)" type="text"/>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="chap_name">Quyển số</label>
                <div class="col-sm-8">
                    <input class="form-control" name="vol[${chapNum}]" placeholder="Quyển số" type="number" value="${chap_vol}" required/>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="chap_name">Tên quyển</label>
                <div class="col-sm-8">
                    <input class="form-control chap_vol_name" name="vol_name[${chapNum}]" placeholder="Tên quyển" type="text" value="${chap_vol_name}" />
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="chap_name">Tên chương</label>
                <div class="col-sm-8">
                    <input required class="form-control" name="chap_name[${chapNum}]" placeholder="Tên chương" type="text"/>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="introduce">Nội dung</label>
                <div class="col-sm-8">
                    <textarea maxlength="75000" style="color:#000;font-weight: 400;" required class="form-control" name="introduce[${chapNum}]" rows="20" placeholder="Nội dung" type="text"></textarea>
                    <div class="chapter-character-count"></div>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="adv">Quảng cáo</label>
                <div class="col-sm-8">
                    <textarea maxlength="1000" class="form-control" name="adv[${chapNum}]" placeholder="Quảng cáo" type="text"></textarea>
                </div>
            </div>
        </div>`;
    }
    function setupCharacterCounter() {
        jQuery(document).on("input", "[name^=introduce]", function() {
            const text = jQuery(this).val();
            const charCount = text.length;
            let charCountElement = jQuery(this).next('.chapter-character-count');
            if (charCountElement.length === 0) {
                charCountElement = jQuery('<div class="chapter-character-count"></div>');
                jQuery(this).after(charCountElement);
            }
            if(charCount < 3000) {
                jQuery(this).addClass('short-chapter');
                charCountElement.html(`<span class="short-chapters-warning">${charCount.toLocaleString()}/40.000 ký tự</span>`);
            } else {
                jQuery(this).removeClass('short-chapter');
                if(charCount > 40000) {
                    charCountElement.html(`<span style="color: #fbbc05;">${charCount.toLocaleString()}/40.000 ký tự</span>`);
                } else {
                    charCountElement.html(`<span style="color: #34a853;">${charCount.toLocaleString()}/40.000 ký tự</span>`);
                }
            }
        });
    }
    function validateChapterLengths() {
        let hasError = false;
        jQuery('form[name="postChapForm"] .chapter-detail').each(function() {
            const form = this;
            const contentTextarea = form.querySelector('textarea[name^="introduce"]');
            const content = contentTextarea.value;
            if (content.length < 3000) {
                jQuery(contentTextarea).addClass('short-chapter');
                let warningIcon = form.querySelector('.warning-icon');
                if (!warningIcon) {
                    warningIcon = document.createElement('div');
                    warningIcon.className = 'warning-icon';
                    warningIcon.innerHTML = '⚠️';
                    contentTextarea.parentNode.appendChild(warningIcon);
                }
                hasError = true;
            } else {
                jQuery(contentTextarea).removeClass('short-chapter');
                const warningIcon = form.querySelector('.warning-icon');
                if (warningIcon) {
                    warningIcon.remove();
                }
            }
        });
        return !hasError;
    }
    const dăngnhanhTTV = {
        STATE: {
            CHAP_NUMBER: 1,
            CHAP_STT: 1,
            CHAP_SERIAL: 1,
            CHAP_NUMBER_ORIGINAL: 1,
            CHAP_STT_ORIGINAL: 1,
            CHAP_SERIAL_ORIGINAL: 1
        },
        ELEMENTS: {
            qpContent: null,
            qpButtonSubmit: null,
            qpButtonRemoveEmpty: null,
            qpButtonReset: null
        },
        init: function() {
            try {
                console.log('[TTV-DEBUG] Script bắt đầu khởi tạo...');
                console.log('[TTV-DEBUG] Phiên bản script: 3.0');
                this.initializeChapterValues();
                console.log('[TTV-DEBUG] Đã khởi tạo giá trị chương');

                this.createInterface();
                console.log('[TTV-DEBUG] Đã tạo giao diện');
                this.cacheElements();
                console.log('[TTV-DEBUG] Đã cache các elements');
                this.registerEvents();
                console.log('[TTV-DEBUG] Đã đăng ký các events');
                console.log('[TTV-DEBUG] Script đã khởi động thành công');
                showNotification('Công cụ đã chạy', 'success');


                // Khôi phục trạng thái chế độ tự động đăng
                const isAutoMode = localStorage.getItem('TTV_AUTO_MODE') === 'true';
                if (isAutoMode) {
                    this.ELEMENTS.qpOptionLoop.prop('checked', true);
                    this.toggleAutoMode(); // Áp dụng giao diện theo chế độ
                }

                // Khởi tạo biến theo dõi số chương đã xử lý
                this.STATE.PROCESSED_CHAPTERS = 0;
                this.STATE.TARGET_CHAPTERS = 0;
            } catch (e) {
                console.error('[TTV-ERROR] Lỗi khởi tạo:', e);
                showNotification('Có lỗi khi khởi tạo Script', 'error');
            }
        },

        initializeChapterValues: function() {
            try {
                const chap_number = parseInt(jQuery('#chap_number').val());
                let chap_stt = parseInt(jQuery('.chap_stt1').val());
                let chap_serial = parseInt(jQuery('.chap_serial').val());

                if (parseInt(jQuery('#chap_stt').val()) > chap_stt) {
                    chap_stt = parseInt(jQuery('#chap_stt').val());
                }
                if (parseInt(jQuery('#chap_serial').val()) > chap_serial) {
                    chap_serial = parseInt(jQuery('#chap_serial').val());
                }

                this.STATE.CHAP_NUMBER = this.STATE.CHAP_NUMBER_ORIGINAL = chap_number || 1;
                this.STATE.CHAP_STT = this.STATE.CHAP_STT_ORIGINAL = chap_stt || 1;
                this.STATE.CHAP_SERIAL = this.STATE.CHAP_SERIAL_ORIGINAL = chap_serial || 1;
            } catch (e) {
                console.error("Error initializing chapter values:", e);
            }
        },
        createInterface: function() {
            const html = `
            <div id="modern-uploader">
                <div class="text-center mb-4">
                    <h3 style="color: #4285f4; margin-bottom: 15px; font-weight: 700; font-size: 18px;">📝 CÔNG CỤ ĐĂNG NHANH</h3>
                </div>
                <div class="form-group">
                    <textarea placeholder="Nội dung truyện (Dán vào đây để tự động tách chương)" id="qpContent" class="form-control" rows="5"></textarea>
                </div>
                <div class="button-container" style="display: flex; justify-content: center; gap: 15px;">
                    <button class="btn btn-primary" id="qpButtonPaste">📋 Paste</button>
                    <button class="btn btn-danger" id="qpButtonRemoveEmpty" style="display: none;">Ẩn chương trống</button>
                </div>
                <div class="notification-container"></div>
            </div>`;

            jQuery(".list-in-user").before(html);
        },
        cacheElements: function() {
            this.ELEMENTS.qpContent = jQuery("#qpContent");
            this.ELEMENTS.qpButtonRemoveEmpty = jQuery("#qpButtonRemoveEmpty");
            this.ELEMENTS.qpButtonPaste = jQuery("#qpButtonPaste");
        },
        registerEvents: function() {
            this.ELEMENTS.qpContent.on("paste", this.handlePaste.bind(this));
            this.ELEMENTS.qpButtonRemoveEmpty.on('click', this.removeEmptyChapters.bind(this));
            this.ELEMENTS.qpButtonPaste.on('click', this.handlePasteButton.bind(this));
            setupCharacterCounter();
        },
        handlePasteButton: function() {
            this.showLoading();
            navigator.clipboard.readText()
                .then(text => {
                    this.ELEMENTS.qpContent.val(text);
                    setTimeout(() => {
                        this.performAction();
                        this.hideLoading();

                        // Tự động đăng sau 1 giây
                        setTimeout(() => {
                            this.submitChapters();
                        }, 1000);
                    }, 100);
                })
                .catch(err => {
                    console.error('Không thể đọc dữ liệu từ clipboard:', err);
                    this.hideLoading();
                    showNotification('Không thể truy cập clipboard. Vui lòng dán trực tiếp vào ô nội dung.', 'error');
                });
        },
        handlePaste: function(e) {
            e.preventDefault();
            this.ELEMENTS.qpContent.val("");
            this.showLoading();
            const pastedText = e.originalEvent.clipboardData.getData('text');
            this.ELEMENTS.qpContent.val(pastedText);
            setTimeout(() => {
                this.performAction();
                this.hideLoading();

                // Tự động đăng sau 1 giây
                setTimeout(() => {
                    this.submitChapters();
                }, 1000);
            }, 100);
        },
        performAction: function() {
            try {
                console.log("Starting performAction");
                var text = this.ELEMENTS.qpContent.val();

                if (!text) {
                    showNotification('Không có nội dung để tách chương', 'error');
                    return 0;
                }
                var debugOutput = [];
                var chapters = [];
                var lines = text.split('\n');
                var currentChapter = [];
                var lastTitle = null;
                debugOutput.push("=== Processing Text ===");
                debugOutput.push(`Total lines: ${lines.length}`);
                debugOutput.push("=== Line Analysis ===");
                function visualizeWhitespace(str) {
                    return str.split('').map(c => {
                        if (c === '\t') return '\\t';
                        if (c === ' ') return '·';
                        if (c === '\n') return '\\n';
                        return c;
                    }).join('');
                }
                for (let i = 0; i < lines.length; i++) {
                    let line = lines[i];
                    let isChapterTitle = /^\t[Cc]hương\s*\d+\s*:/.test(line) || /^\s{4,}[Cc]hương\s*\d+\s*:/.test(line);
                    debugOutput.push(`Line ${i}: ${visualizeWhitespace(line.substring(0, 50))}${line.length > 50 ? '...' : ''}`);
                    debugOutput.push(`  Is chapter: ${isChapterTitle}`);

                    if (isChapterTitle) {
                        // Trích xuất số chương từ tiêu đề
                        const chapterMatch = line.match(/[Cc]hương\s*(\d+)\s*:/);
                        const currentChapterNum = chapterMatch ? parseInt(chapterMatch[1]) : 0;
                        const isDuplicateNumber = currentChapterNum > 0 && debugOutput.includes(`Found chapter number: ${currentChapterNum}`);

                        // Kiểm tra nếu dòng tiếp theo cũng là tiêu đề chương và có chung số chương
                        // Kiểm tra tiêu đề liên tiếp với khoảng cách tối đa là 3 dòng
                        let isConsecutiveTitle = false;
                        const MAX_DISTANCE = 3; // Khoảng cách tối đa giữa hai tiêu đề để coi là liên tiếp

                        // Duyệt qua MAX_DISTANCE dòng tiếp theo để tìm tiêu đề trùng lặp
                        for (let j = 1; j <= MAX_DISTANCE && i + j < lines.length; j++) {
                            const nextLine = lines[i + j];
                            // Kiểm tra xem dòng tiếp theo có phải là tiêu đề chương không
                            const nextLineIsChapter = /^\t[Cc]hương\s*\d+\s*:/.test(nextLine) || /^\s{4,}[Cc]hương\s*\d+\s*:/.test(nextLine);

                            if (nextLineIsChapter) {
                                const nextChapterMatch = nextLine.match(/[Cc]hương\s*(\d+)\s*:/);
                                const nextChapterNum = nextChapterMatch ? parseInt(nextChapterMatch[1]) : 0;

                                // Kiểm tra xem số chương có trùng không
                                if (nextChapterNum === currentChapterNum) {
                                    isConsecutiveTitle = true;
                                    debugOutput.push(`  -> Detected consecutive title with same chapter number: ${currentChapterNum}, distance: ${j} line(s)`);

                                    // Thêm kiểm tra tiêu đề tương tự
                                    const currentTitle = line.split(':')[1]?.trim() || '';
                                    const nextTitle = nextLine.split(':')[1]?.trim() || '';

                                    // Kiểm tra nếu tiêu đề giống nhau một phần (ít nhất 70% giống nhau)
                                    if (currentTitle && nextTitle) {
                                        const shorterLength = Math.min(currentTitle.length, nextTitle.length);
                                        if (shorterLength > 0) {
                                            // Kiểm tra nếu tiêu đề này là một phần của tiêu đề kia
                                            const isSimilar = currentTitle.includes(nextTitle) || nextTitle.includes(currentTitle);
                                            debugOutput.push(`  -> Title similarity check: ${isSimilar ? 'similar' : 'different'}, current: "${currentTitle}", next: "${nextTitle}"`);
                                        }
                                    }
                                    break;
                                }
                            }

                            // Nếu tìm thấy dòng không trống và không phải tiêu đề chương, có thể đây là nội dung thực sự
                            if (nextLine.trim() !== '' && !nextLineIsChapter) {
                                break;
                            }
                        }

                        // Kiểm tra nếu dòng trước đó cũng là tiêu đề chương và vừa được xử lý
                        const isAfterPreviousTitle = (i > 0 && line !== lastTitle && currentChapter.length <= 1);

                        if (currentChapter.length > 0) {
                            // Nếu không phải là tiêu đề liên tiếp hoặc sau một tiêu đề khác
                            if (line !== lastTitle && !isConsecutiveTitle && !isAfterPreviousTitle) {
                                // Kiểm tra số chương trùng
                                if (isDuplicateNumber) {
                                    debugOutput.push(`  -> Warning: Duplicate chapter number ${currentChapterNum} detected`);
                                    showNotification(`Cảnh báo: Số chương ${currentChapterNum} bị trùng lặp`, 'warning');
                                }

                                chapters.push(currentChapter.join('\n'));
                                currentChapter = [line];
                                lastTitle = line;
                                debugOutput.push(`  -> New chapter started`);
                                if (currentChapterNum > 0) {
                                    debugOutput.push(`Found chapter number: ${currentChapterNum}`);
                                }
                            } else {
                                if (isConsecutiveTitle) {
                                    // Ghi nhớ tiêu đề này để so sánh sau này
                                    const currentTitleContent = line.split(':')[1]?.trim() || '';
                                    const previousTitleContent = lastTitle.split(':')[1]?.trim() || '';

                                    // Nếu tiêu đề hiện tại dài hơn tiêu đề trước đó, ưu tiên giữ lại tiêu đề này
                                    if (currentTitleContent.length > previousTitleContent.length) {
                                        debugOutput.push(`  -> Replacing previous shorter title "${previousTitleContent}" with longer title "${currentTitleContent}"`);

                                        // Giữ lại tiêu đề dài hơn, nhưng vẫn đánh dấu là chương mới
                                        if (currentChapter.length > 1) {
                                            chapters.push(currentChapter.join('\n'));
                                        }
                                        currentChapter = [line];
                                        lastTitle = line;
                                    } else {
                                        debugOutput.push(`  -> Ignoring consecutive title (current is shorter or equal length)`);
                                        // Bỏ qua dòng này vì là tiêu đề liên tiếp và không dài hơn
                                        continue;
                                    }
                                } else {
                                    debugOutput.push(`  -> Skipped duplicate title`);
                                }
                            }
                        } else {
                            currentChapter = [line];
                            lastTitle = line;
                            debugOutput.push(`  -> First chapter started`);
                            if (currentChapterNum > 0) {
                                debugOutput.push(`Found chapter number: ${currentChapterNum}`);
                            }
                        }
                    } else if (currentChapter.length > 0) {
                        currentChapter.push(line);
                    }
                }
                if (currentChapter.length > 0) {
                    chapters.push(currentChapter.join('\n'));
                    debugOutput.push("-> Added final chapter");
                }
                debugOutput.push("=== Results ===");
                debugOutput.push(`Found ${chapters.length} chapters`);
                const processedChapters = [];
                for (let i = 0; i < chapters.length; i++) {
                    const chapterLines = chapters[i].split('\n');
                    const title = chapterLines.shift().trim();
                    const chapterText = chapterLines.join('\n');
                    const charCount = chapterText.length;
                    debugOutput.push(`Chapter ${i+1} character count: ${charCount}`);
                    if (charCount > 40000) {
                        const parts = Math.ceil(charCount / 40000);
                        debugOutput.push(`Splitting into ${parts} parts`);
                        const charsPerPart = Math.ceil(charCount / parts);
                        debugOutput.push(`Characters per part: ~${charsPerPart}`);
                        let currentText = chapterText;
                        let totalProcessed = 0;
                        for (let part = 0; part < parts; part++) {
                            const isLastPart = part === parts - 1;
                            const targetSize = isLastPart ? currentText.length : charsPerPart;
                            let endPos = Math.min(targetSize, currentText.length);
                            if (!isLastPart && endPos < currentText.length) {
                                const nextParagraph = currentText.indexOf('\n\n', endPos - 500);
                                if (nextParagraph !== -1 && nextParagraph < endPos + 500) {
                                    endPos = nextParagraph + 2;
                                } else {
                                    const sentenceEnd = Math.max(
                                        currentText.lastIndexOf('. ', endPos),
                                        currentText.lastIndexOf('! ', endPos),
                                        currentText.lastIndexOf('? ', endPos)
                                    );
                                    if (sentenceEnd !== -1 && sentenceEnd > endPos - 500) {
                                        endPos = sentenceEnd + 2;
                                    }
                                }
                            }
                            const partContent = currentText.substring(0, endPos);
                            totalProcessed += partContent.length;
                            currentText = currentText.substring(endPos);
                            let chapterTitle = title;
                            if (title.includes(':')) {
                                chapterTitle = title.substring(title.indexOf(':') + 1).trim();
                            }
                            let newTitle = `${title} (Phần ${part+1}/${parts})`;
                            processedChapters.push(newTitle + '\n' + partContent);
                            debugOutput.push(`Part ${part+1}: ${partContent.length} chars`);
                        }
                        debugOutput.push(`Total processed: ${totalProcessed}/${charCount} chars`);
                    } else {
                        processedChapters.push(chapters[i]);
                    }
                }
                debugOutput.push(`After processing: ${processedChapters.length} chapters`);
                const chaptersToFill = processedChapters.slice(0, MAX_CHAPTER_POST);
                const remainingChapters = processedChapters.slice(MAX_CHAPTER_POST);
                var titles = jQuery("input[name^='chap_name']");
                var contents = jQuery("textarea[name^='introduce']");
                var advs = jQuery("textarea[name^='adv']");
                debugOutput.push(`Forms found: ${titles.length}`);
                if (processedChapters.length === 0) {
                    showNotification('Không tìm thấy chương nào', 'error');
                    jQuery('#debug-output').text(debugOutput.join('\n'));
                    return;
                }
                if (remainingChapters.length > 0) {
                    debugOutput.push(`${remainingChapters.length} chapters will be copied to clipboard`);
                }
                const neededForms = chaptersToFill.length - titles.length;
                if (neededForms > 0 && titles.length < MAX_CHAPTER_POST) {
                    debugOutput.push(`Need to add ${neededForms} more forms`);
                    for (let i = 0; i < neededForms && (titles.length + i) < MAX_CHAPTER_POST; i++) {
                        this.addNewChapter();
                    }
                    titles = jQuery("input[name^='chap_name']");
                    contents = jQuery("textarea[name^='introduce']");
                    advs = jQuery("textarea[name^='adv']");
                }
                debugOutput.push(`Filling ${chaptersToFill.length} chapters into forms`);
                jQuery.each(titles, function(k, v) {
                    if (k < chaptersToFill.length) {
                        var content = chaptersToFill[k].split('\n');
                        var title = content.shift().trim();
                        var chapterTitle = title;
                        if (title.includes(':')) {
                            chapterTitle = title.substring(title.indexOf(':') + 1).trim();
                        }
                        debugOutput.push(`\nFilling chapter ${k + 1}:`);
                        debugOutput.push(`Original title: ${title}`);
                        debugOutput.push(`Extracted title: ${chapterTitle}`);
                        debugOutput.push(`Content length: ${content.length} lines`);
                        if (!chapterTitle || chapterTitle.trim() === '') {
                            chapterTitle = "Vô đề";
                            debugOutput.push(`Empty title detected, using default: ${chapterTitle}`);
                        }
                        titles[k].value = chapterTitle;
                        contents[k].value = HEADER_SIGN + "\r\n" + content.join('\n') + "\r\n" + FOOTER_SIGN;
                        if (advs[k]) advs[k].value = "";
                        jQuery(contents[k]).trigger('input');
                    }
                });

                if (remainingChapters.length > 0) {
                    try {
                        const clipboardContent = remainingChapters.map(chap => {
                            const lines = chap.trim().split('\n');
                            if (lines.length > 0) {
                                if (!lines[0].startsWith('\t')) {
                                    lines[0] = '\t' + lines[0];
                                }
                            }
                            return lines.join('\n');
                        }).join('\n\n---CHAPTER_SEPARATOR---\n\n');

                        // Cập nhật số chương đã xử lý
                        this.STATE.PROCESSED_CHAPTERS += remainingChapters.length;
                        localStorage.setItem('TTV_PROCESSED_CHAPTERS', this.STATE.PROCESSED_CHAPTERS.toString());

                        // Hiển thị thông tin
                        let message = `📝 Đã xử lý ${remainingChapters.length} chương mới\n`;
                        message += `📋 Tổng số đã xử lý: ${this.STATE.PROCESSED_CHAPTERS}/${this.STATE.TARGET_CHAPTERS}\n`;

                        // Copy vào clipboard
                        navigator.clipboard.writeText(clipboardContent)
                            .then(() => {
                                showNotification(message, 'success');
                            })
                            .catch((error) => {
                                console.error('Lỗi clipboard:', error);
                                showNotification('Không thể copy vào clipboard. Vui lòng thử lại.', 'error');
                            });

                        // Kiểm tra hoàn thành mục tiêu

                    } catch (error) {
                        console.error('Lỗi xử lý chương:', error);
                        showNotification('Có lỗi khi xử lý các chương. Vui lòng thử lại.', 'error');
                    }
                }

                this.ELEMENTS.qpButtonSubmit.removeClass("btn-disable").addClass("btn-success");
                this.ELEMENTS.qpContent.val("Đã thực hiện xong");
                if (remainingChapters.length === 0) {
                    let splitChapters = 0;
                    let shortChapters = 0;
                    let shortChapterDetails = [];
                    let longChapterDetails = [];
                    for (let i = 0; i < chapters.length; i++) {
                        const chapterLines = chapters[i].split('\n');
                        const title = chapterLines.shift().trim();
                        const chapterText = chapterLines.join('\n');
                        if (chapterText.length > 40000) {
                            splitChapters++;
                            const partsCount = Math.ceil(chapterText.length / 40000);
                            longChapterDetails.push({
                                title: title,
                                parts: partsCount
                            });
                        }
                        if (chapterText.length < 3000) {
                            shortChapters++;
                            shortChapterDetails.push({
                                title: title,
                                length: chapterText.length
                            });
                        }
                    }
                    const splittedChaptersCount = processedChapters.length - (chapters.length - splitChapters);
                    let message = '';
                    message = message.concat(`📝Đã xử lý ${processedChapters.length} Chương\n`);
                    message = message.concat(`📝Đã nhập ${Math.min(processedChapters.length, MAX_CHAPTER_POST)} Chương\n`);
                    if (splitChapters > 0) {
                        message = message.concat(`📑Có ${splitChapters} Chương dài chia thành ${splittedChaptersCount} Chương\n`);
                        longChapterDetails.forEach(chapter => {
                            let chapterName = chapter.title;
                            if (chapterName.includes(':')) {
                                chapterName = chapterName.trim();
                            }
                            message = message.concat(` - ${chapterName}: ${chapter.parts} Chương\n`);
                        });
                    }
                    if (shortChapters > 0) {
                        message = message.concat(`⚠️<span class="short-chapters-warning">Có ${shortChapters} chương dưới 3000 ký tự</span>\n`);
                        shortChapterDetails.forEach(chapter => {
                            let chapterName = chapter.title;
                            if (chapterName.includes(':')) {
                                chapterName = chapterName.trim();
                            }
                            message = message.concat(`<span class="short-chapters-warning"> - ${chapterName}: có ${chapter.length.toLocaleString()} ký tự</span>\n`);
                        });
                    }
                    showNotification(message, 'success');
                }
                jQuery('#debug-output').text(debugOutput.join('\n'));
                return processedChapters.length;
            } catch (e) {
                console.error("Error in performAction:", e);
                showNotification('Có lỗi khi tách chương', 'error');
                return 0;
            }
        },
        removeEmptyChapters: function() {
            const forms = document.querySelectorAll('[data-gen="MK_GEN"]');
            let removed = 0;

            forms.forEach(form => {
                const content = form.querySelector('textarea[name^="introduce"]').value.trim();
                if (!content) {
                    form.remove();
                    removed++;
                    this.updateChapNumber(false);
                }
            });
            showNotification(`Đã xử lý ${forms.length} chương`, 'info');
        },
        submitChapters: function() {
            // Kiểm tra nút submit
            const postButton = jQuery('button[type="submit"]');
            if (!postButton.length) {
                showNotification('Không tìm thấy nút đăng chương!', 'error');
                return;
            }

            // Kiểm tra độ dài chương
            if (!validateChapterLengths()) {
                showNotification('Có chương chưa đủ độ dài tối thiểu (3000 ký tự)!', 'error');
                return;
            }

            // Kiểm tra số chương trong form
            const chaptersInForm = jQuery("textarea[name^='introduce']").length;
            console.log(`[TTV-DEBUG] Số chương trong form: ${chaptersInForm}`);

            // Thực hiện đăng chương
            postButton.click();
            showNotification(`Đang đăng ${chaptersInForm} chương...`, 'info');

            // Nếu số chương < 10, thông báo và dừng
            if (chaptersInForm < 10) {
                setTimeout(() => {
                    showNotification(`Đã đăng xong ${chaptersInForm} chương. Dừng tự động vì số chương dưới 10.`, 'success');
                }, 3000);
                return;
            }

            // Nếu còn nhiều chương, reload trang sau 5 giây
            setTimeout(() => {
                window.location.reload();
            }, 5000);
        },
        addNewChapter: function() {
            if ((this.STATE.CHAP_NUMBER + 1) <= MAX_CHAPTER_POST) {
                this.updateChapNumber(true);
                const html = createChapterHTML(this.STATE.CHAP_NUMBER);
                jQuery('#add-chap').before(html);
                setupCharacterCounter();
            } else {
                showNotification(`Chỉ có thể đăng tối đa ${MAX_CHAPTER_POST} chương một lần`, 'warning');
            }
        },
        updateChapNumber: function(isAdd) {
            try{
                if (isAdd) {
                    let maxStt =0;
                    let maxSerial = 0;
                    jQuery('input[name^="chap_stt"]').each(function() {
                        const val = parseInt(jQuery(this).val()) || 0;
                        maxStt = Math.max(maxStt, val);
                    });
                    jQuery('input[name^="chap_number"]').each(function() {
                        const val = parseInt(jQuery(this).val()) || 0;
                        maxSerial = Math.max(maxSerial, val);
                    });
                    const chapStt = parseInt(jQuery('.chap_stt1').val()) || 0;
                    const chapSerial = parseInt(jQuery('.chap_serial').val()) || 0;
                    maxStt = Math.max(maxStt, chapStt);
                    maxSerial = Math.max(maxSerial, chapSerial);
                    this.STATE.CHAP_STT = maxStt + 1;
                    this.STATE.CHAP_SERIAL = maxSerial + 1;
                    this.STATE.CHAP_NUMBER++;
                } else {
                    if (this.STATE.CHAP_NUMBER > this.STATE.CHAP_NUMBER_ORIGINAL) {
                        this.STATE.CHAP_NUMBER--;
                    }
                    if (this.STATE.CHAP_STT > this.STATE.CHAP_STT_ORIGINAL) {
                        this.STATE.CHAP_STT--;
                    }
                    if (this.STATE.CHAP_SERIAL > this.STATE.CHAP_SERIAL_ORIGINAL) {
                        this.STATE.CHAP_SERIAL--;
                    }
                }
                jQuery('#chap_number').val(this.STATE.CHAP_NUMBER);
                jQuery('#chap_stt').val(this.STATE.CHAP_STT);
                jQuery('#chap_serial').val(this.STATE.CHAP_SERIAL);
                jQuery('#countNumberPost').text(this.STATE.CHAP_NUMBER);
            } catch (e) {
                console.log("Lỗi: " + e);
            }
        },
        showLoading: function() {
            const loading = jQuery("<div>", {
                class: "loading-overlay",
                css: {
                    position: "fixed",
                    inset: 0,
                    backgroundColor: "rgba(0, 0, 0, 0.5)",
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    zIndex: 9999
                },
                html: "<div class='loading-spinner'></div>"
            });
            jQuery("body").append(loading);
        },
        hideLoading: function() {
            jQuery(".loading-overlay").remove();
        },
        toggleAutoMode: function() {

        }
    };
    dăngnhanhTTV.init();
})();