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

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         TTV Auto Upload
// @namespace    http://tampermonkey.net/
// @version      5.3
// @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,
            AUTO_POST: false,
            TOTAL_CHAPTERS: 0,
            POSTED_CHAPTERS: 0,
            PROCESSED_CHAPTERS: 0, // Added: Tracks processed chapters
            TARGET_CHAPTERS: 0    // Added: Tracks target chapters
        },
        ELEMENTS: {
            qpContent: null,
            qpButtonSubmit: null,
            qpButtonRemoveEmpty: null,
            qpButtonReset: null // Added element
        },
        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');

                // Khôi phục trạng thái tự động đăng
                this.loadAutoPostState();
                console.log('[TTV-DEBUG] Đã khôi phục trạng thái tự động đă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');

                // Cập nhật hiển thị nút tự động đăng
                if (this.STATE.AUTO_POST) {
                    this.ELEMENTS.qpButtonAutoPost.text(`🔄 (ON) ${this.STATE.POSTED_CHAPTERS}/${this.STATE.TOTAL_CHAPTERS}`);
                    this.ELEMENTS.qpButtonAutoPost.removeClass('btn-warning').addClass('btn-info');
                }

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

        loadAutoPostState: function() {
            // Khôi phục trạng thái tự động đăng từ localStorage
            const autoPost = localStorage.getItem('TTV_AUTO_POST') === 'true';
            this.STATE.AUTO_POST = autoPost;

            if (autoPost) {
                this.STATE.TOTAL_CHAPTERS = parseInt(localStorage.getItem('TTV_TOTAL_CHAPTERS') || '0');
                this.STATE.POSTED_CHAPTERS = parseInt(localStorage.getItem('TTV_POSTED_CHAPTERS') || '0');

                console.log(`[TTV-DEBUG] Khôi phục tự động đăng: ${this.STATE.POSTED_CHAPTERS}/${this.STATE.TOTAL_CHAPTERS}`);
            }
        },
        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="text-center mb-3">
                    <label style="color: #bef385;"><input type="checkbox" id="qpOptionLoop" class="form-control" style="height:10px;width: 10px;display: inline-block;">Chế độ tự động</label>
                </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-success" id="qpButtonSubmit">📤 Đăng chương</button>
                    <button class="btn btn-danger" id="qpButtonRemoveEmpty" style="display: none;">Ẩn chương trống</button>
                    <button class="btn btn-warning" id="qpButtonAutoPost" style="display: none;">🔄 (OFF)</button>
                    <button class="btn btn-secondary" id="qpButtonReset" style="display: none;">🔁 Reset</button>
                </div>
                <div class="notification-container"></div>
            </div>`;

            jQuery(".list-in-user").before(html);
        },
        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);
            }
        },
        cacheElements: function() {
            this.ELEMENTS.qpContent = jQuery("#qpContent");
            this.ELEMENTS.qpButtonSubmit = jQuery("#qpButtonSubmit");
            this.ELEMENTS.qpButtonRemoveEmpty = jQuery("#qpButtonRemoveEmpty");
            this.ELEMENTS.qpButtonPaste = jQuery("#qpButtonPaste");
            this.ELEMENTS.qpButtonAutoPost = jQuery("#qpButtonAutoPost");
            this.ELEMENTS.qpButtonReset = jQuery("#qpButtonReset"); // Added element caching
            this.ELEMENTS.qpOptionLoop = jQuery("#qpOptionLoop"); // Added checkbox caching
        },
        registerEvents: function() {
            this.ELEMENTS.qpContent.on("paste", this.handlePaste.bind(this));
            this.ELEMENTS.qpButtonRemoveEmpty.on('click', this.removeEmptyChapters.bind(this));
            this.ELEMENTS.qpButtonSubmit.on('click', this.submitChapters.bind(this));
            this.ELEMENTS.qpButtonPaste.on('click', this.handlePasteButton.bind(this));
            this.ELEMENTS.qpButtonAutoPost.on('click', this.toggleAutoPost.bind(this));
            this.ELEMENTS.qpButtonReset.on('click', this.resetAutoPost.bind(this)); //Added event listener
            this.ELEMENTS.qpOptionLoop.on('change', this.toggleAutoMode.bind(this)); // Added checkbox event
            setupCharacterCounter();

            // Kiểm tra và bắt đầu tự động đăng nếu đã bật
            if (window.location.href.includes('/dang-chuong/story/')) {
                setTimeout(() => {
                    if (this.STATE.AUTO_POST && this.STATE.POSTED_CHAPTERS < this.STATE.TOTAL_CHAPTERS) {
                        this.runAutoPostSequence();
                    }
                }, 2000);
            }
        },
        handlePasteButton: function() {
            this.showLoading();
            navigator.clipboard.readText()
                .then(text => {
                    this.ELEMENTS.qpContent.val(text);
                    setTimeout(() => {
                        this.performAction();
                        this.hideLoading();
                    }, 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();
            }, 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
                        let isConsecutiveTitle = false;
                        if (i < lines.length - 1) {
                            const nextLine = lines[i + 1];
                            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;
                                isConsecutiveTitle = (nextChapterNum === currentChapterNum);
                                if (isConsecutiveTitle) {
                                    debugOutput.push(`  -> Detected consecutive title with same chapter number: ${currentChapterNum}`);
                                }
                            }
                        }
                        
                        // 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) {
                                    debugOutput.push(`  -> Ignoring consecutive title`);
                                    // Bỏ qua dòng này vì là tiêu đề liên tiếp
                                    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');
                        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 (remainingChapters.length > 0) {
                            message = message.concat(`📋Đã lưu lại ${remainingChapters.length} Chương\n`);
                        }
                        if (splitChapters > 0) {
                            message = message.concat(`📑Có ${splitChapters} Chương đã 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`);
                            });
                        }
                        // Cập nhật số chương đã xử lý
                        this.STATE.PROCESSED_CHAPTERS += processedChapters.length;

                        // Kiểm tra xem đã xử lý đủ số chương chưa
                        if (this.STATE.TARGET_CHAPTERS > 0 && this.STATE.PROCESSED_CHAPTERS >= this.STATE.TARGET_CHAPTERS) {
                            message = message.concat(`\n🎯 Đã đạt mục tiêu xử lý ${this.STATE.PROCESSED_CHAPTERS}/${this.STATE.TARGET_CHAPTERS} chương`);
                            // Xóa clipboard nếu đã đủ số chương
                            if (navigator.clipboard && navigator.clipboard.writeText) {
                                navigator.clipboard.writeText("");
                            }
                        }

                        showNotification(message, 'success');
                    } catch (err) {
                    console.error('Lỗi khi sao chép vào clipboard:', err);
                    debugOutput.push(`Lỗi khi sao chép vào clipboard: ${err.message}`);
                    showNotification('Không thể sao chép vào clipboard. Vui lòng thử lại.', 'error');
                    const manualCopyArea = document.createElement('div');
                    manualCopyArea.style.position = 'fixed';
                    manualCopyArea.style.top = '50%';
                    manualCopyArea.style.left = '50%';
                    manualCopyArea.style.transform = 'translate(-50%, -50%)';
                    manualCopyArea.style.backgroundColor = 'white';
                    manualCopyArea.style.padding = '20px';
                    manualCopyArea.style.borderRadius = '8px';
                    manualCopyArea.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)';
                    manualCopyArea.style.zIndex = '10000';
                    manualCopyArea.style.maxWidth = '80%';
                    manualCopyArea.style.maxHeight = '80%';
                    manualCopyArea.style.overflow = 'auto';
                    manualCopyArea.innerHTML = `
                        <h3 style="margin-top: 0;">Sao chép thủ công</h3>
                        <p>Không thể sao chép tự động. Vui lòng chọn toàn bộ nội dung bên dưới và sao chép (Ctrl+C):</p>
                        <textarea style="width: 100%; height: 300px; padding: 10px;">${clipboardContent}</textarea>
                            <div style="text-align: right; margin-top: 10px;">
                                <button id="closeManualCopy" style="padding: 8px 16px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer;">Đóng</button>
                            </div>
                        `;
                        document.body.appendChild(manualCopyArea);
                        document.getElementById('closeManualCopy').addEventListener('click', () => {
                            document.body.removeChild(manualCopyArea);
                        });
                    }
                }
                this.ELEMENTS.qpButtonSubmit.removeClass("btn-disable").addClass("btn-success");
                this.ELEMENTS.qpContent.val("Đã thựchiệ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');
        },
        toggleAutoPost: function() {
            this.STATE.AUTO_POST = !this.STATE.AUTO_POST;

            if (this.STATE.AUTO_POST) {
                // Lưu trạng thái tự động đăng vào localStorage
                localStorage.setItem('TTV_AUTO_POST', 'true');
                this.STATE.TOTAL_CHAPTERS = parseInt(prompt("Nhập tổng số lần tự động đăng:", "10")) || 0;
                this.STATE.POSTED_CHAPTERS = parseInt(localStorage.getItem('TTV_POSTED_CHAPTERS') || '0');
                this.STATE.TARGET_CHAPTERS = this.STATE.TOTAL_CHAPTERS; // Set target chapters

                localStorage.setItem('TTV_TOTAL_CHAPTERS', this.STATE.TOTAL_CHAPTERS.toString());

                this.ELEMENTS.qpButtonAutoPost.text(`🔄 (ON) ${this.STATE.POSTED_CHAPTERS}/${this.STATE.TOTAL_CHAPTERS}`);
                this.ELEMENTS.qpButtonAutoPost.removeClass('btn-warning').addClass('btn-info');

                showNotification(`Đã bật tự động đăng (${this.STATE.POSTED_CHAPTERS}/${this.STATE.TOTAL_CHAPTERS})`, 'success');

                // Bắt đầu quy trình tự động
                if (window.location.href.includes('/dang-chuong/story/')) {
                    setTimeout(() => this.runAutoPostSequence(), 2000);
                }
            } else {
                // Tắt tự động đăng
                localStorage.setItem('TTV_AUTO_POST', 'false');
                // Reset số lần đã đăng về 0
                this.STATE.POSTED_CHAPTERS = 0;
                localStorage.setItem('TTV_POSTED_CHAPTERS', '0');
                // Reset tổng số lần đăng về 0
                this.STATE.TOTAL_CHAPTERS = 0;
                localStorage.setItem('TTV_TOTAL_CHAPTERS', '0');
                this.STATE.TARGET_CHAPTERS = 0; // Reset target chapters
                this.ELEMENTS.qpButtonAutoPost.text('🔄 (OFF)');
                this.ELEMENTS.qpButtonAutoPost.removeClass('btn-info').addClass('btn-warning');
                showNotification('Đã tắt tự động đăng và reset số lần đăng', 'info');
            }
        },

        runAutoPostSequence: function() {
            // Kiểm tra trước khi chạy tự động
            if (this.STATE.POSTED_CHAPTERS >= this.STATE.TOTAL_CHAPTERS) {
                // Reset trạng thái tự động nếu đã hoàn thành
                this.STATE.AUTO_POST = false;
                localStorage.setItem('TTV_AUTO_POST', 'false');
                this.ELEMENTS.qpButtonAutoPost.text('🔄 (OFF)');
                this.ELEMENTS.qpButtonAutoPost.removeClass('btn-info').addClass('btn-warning');

                // Reset số lần đã đăng
                this.STATE.POSTED_CHAPTERS = 0;
                localStorage.setItem('TTV_POSTED_CHAPTERS', '0');

                showNotification(`Đã hoàn thành tự động đăng ${this.STATE.TOTAL_CHAPTERS}/${this.STATE.TOTAL_CHAPTERS} chương và đã reset số lần đăng`, 'success');
                return;
            }

            if (!this.STATE.AUTO_POST) {
                return;
            }

            // Tự động nhấn nút Paste sau 2 giây
            setTimeout(() => {
                if (this.STATE.AUTO_POST) {
                    this.handlePasteButton();

                    // Tự động nhấn nút Đăng chương sau 3 giây
                    setTimeout(() => {
                        if (this.STATE.AUTO_POST) {
                            this.submitChapters();
                        }
                    }, 3000);
                }
            }, 2000);
        },

        submitChapters: function() {
            if (!validateChapterLengths()) {
                return;
            }
            this.showLoading();
            document.querySelector('form[name="postChapForm"] button[type="submit"]').click();

            if (this.STATE.AUTO_POST) {
                // Cập nhật số chương đã đăng
                this.STATE.POSTED_CHAPTERS++;
                localStorage.setItem('TTV_POSTED_CHAPTERS', this.STATE.POSTED_CHAPTERS.toString());

                // Kiểm tra và tắt tự động đăng nếu đã đủ số chương
                if (this.STATE.POSTED_CHAPTERS >= this.STATE.TOTAL_CHAPTERS) {
                    this.STATE.AUTO_POST = false;
                    localStorage.setItem('TTV_AUTO_POST', 'false');
                    this.ELEMENTS.qpButtonAutoPost.text('🔄 (OFF)');
                    this.ELEMENTS.qpButtonAutoPost.removeClass('btn-info').addClass('btn-warning');

                    // Reset số lần đã đăng
                    this.STATE.POSTED_CHAPTERS = 0;
                    localStorage.setItem('TTV_POSTED_CHAPTERS', '0');

                    setTimeout(() => {
                        showNotification(`Đã hoàn thành tự động đăng ${this.STATE.TOTAL_CHAPTERS}/${this.STATE.TOTAL_CHAPTERS} chương và đã reset số lần đăng`, 'success');
                    }, 3000);
                }
            }

            setTimeout(() => this.hideLoading(), 2000);
        },
        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();
        },
        resetAutoPost: function() { // Added reset function
            this.STATE.TOTAL_CHAPTERS = 0;
            this.STATE.POSTED_CHAPTERS = 0;
            this.STATE.TARGET_CHAPTERS = 0; // Reset target chapters
            localStorage.removeItem('TTV_TOTAL_CHAPTERS');
            localStorage.removeItem('TTV_POSTED_CHAPTERS');
            this.ELEMENTS.qpButtonAutoPost.text('🔄 (OFF)');
            this.ELEMENTS.qpButtonAutoPost.removeClass('btn-info').addClass('btn-warning');
            showNotification('Đã reset số lần tự động đăng', 'info');
        },
        toggleAutoMode: function() {
            const isAutoMode = this.ELEMENTS.qpOptionLoop.is(':checked');

            if (isAutoMode) {
                // Hiển thị nút tự động đăng và reset, ẩn nút paste và đăng chương
                this.ELEMENTS.qpButtonAutoPost.show();
                this.ELEMENTS.qpButtonReset.show();
                this.ELEMENTS.qpButtonPaste.hide();
                this.ELEMENTS.qpButtonSubmit.hide();

                showNotification('Đã bật chế độ tự động', 'info');
            } else {
                // Hiển thị nút paste và đăng chương, ẩn nút tự động đăng và reset
                this.ELEMENTS.qpButtonAutoPost.hide();
                this.ELEMENTS.qpButtonReset.hide();
                this.ELEMENTS.qpButtonPaste.show();
                this.ELEMENTS.qpButtonSubmit.show();

                showNotification('Đã tắt chế độ tự động', 'info');
            }

            // Lưu trạng thái vào localStorage
            localStorage.setItem('TTV_AUTO_MODE', isAutoMode.toString());
        }
    };
    dăngnhanhTTV.init();
})();