TTV Auto Upload

Tự động điền form đăng chương trên tangthuvien.net

目前為 2025-03-08 提交的版本,檢視 最新版本

// ==UserScript==
// @name         TTV Auto Upload
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Tự động điền form đăng chương trên tangthuvien.net
// @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;
    const MIN_CHARS_WARNING = 3000;

    // CSS tối giản và cải tiến
    GM_addStyle(`
        #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;
        }
        .notification-container {
            width: 100%;
            padding: 10px 0;
            margin-top: 10px;
            text-align: left;
            border-top: 1px solid rgba(0,0,0,0.1);
        }
        .notification-success {
            background-color: #e8f5e9;
            color: #000000;
            padding: 10px 15px;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 500;
            box-shadow: 0 4px 10px rgba(0,0,0,0.15);
            display: inline-block;
            max-width: 90%;
            margin: 0;
            word-break: break-word;
            border: 1px solid #81c784;
        }
        .notification-error {
            background-color: #ffebee;
            color: #000000;
            padding: 10px 15px;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 500;
            box-shadow: 0 4px 10px rgba(0,0,0,0.15);
            display: inline-block;
            max-width: 90%;
            margin: 0;
            word-break: break-word;
            border: 1px solid #e57373;
        }
        .notification-warning {
            background-color: #fff8e1;
            color: #000000;
            padding: 10px 15px;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 500;
            box-shadow: 0 4px 10px rgba(0,0,0,0.15);
            display: inline-block;
            max-width: 90%;
            margin: 0;
            word-break: break-word;
            border: 1px solid #ffd54f;
        }
        .notification-info {
            background-color: #e3f2fd;
            color: #000000;
            padding: 10px 15px;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 500;
            box-shadow: 0 4px 10px rgba(0,0,0,0.15);
            display: inline-block;
            max-width: 90%;
            margin: 0;
            word-break: break-word;
            border: 1px solid #64b5f6;
        }
        .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;
        }
        .loading-overlay {
            position: fixed;
            inset: 0;
            background-color: rgba(0, 0, 0, 0.5);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 9999;
        }
        .loading-spinner {
            width: 50px;
            height: 50px;
            border: 5px solid rgba(255, 255, 255, 0.3);
            border-radius: 50%;
            border-top-color: white;
            animation: spin 1s ease-in-out infinite;
        }
        @keyframes spin {
            to { transform: rotate(360deg); }
        }
    `);

    // Hiển thị thông báo
    function showNotification(message, type = 'success') {
        jQuery('#modern-uploader .notification-container').remove();
        const container = jQuery("<div>", {
            class: "notification-container"
        });
        const notification = jQuery("<div>", {
            class: `notification-${type}`
        });
        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);
    }

    // State management
    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
        },
        ELEMENTS: {
            qpContent: null,
            qpButtonSubmit: null,
            qpButtonRemoveEmpty: null,
            qpButtonPaste: null,
            qpButtonAutoPost: null,
            qpButtonReset: null,
            qpOptionLoop: null
        },
        
        init: function() {
            try {
                console.log('[TTV-DEBUG] Script bắt đầu khởi tạo...');
                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ế độ
                }
            } 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);
            }
        },

        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 style="text-align: center; margin-bottom: 15px;">
                    <label style="color: #333;"><input type="checkbox" id="qpOptionLoop" 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);
        },

        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");
            this.ELEMENTS.qpOptionLoop = jQuery("#qpOptionLoop");
        },

        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));
            this.ELEMENTS.qpOptionLoop.on('change', this.toggleAutoMode.bind(this));
            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.parseChapters();
                        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.parseChapters();
                this.hideLoading();
            }, 100);
        },

        parseChapters: function() {
            try {
                console.log("Starting parseChapters");
                var text = this.ELEMENTS.qpContent.val();

                if (!text) {
                    showNotification('Không có nội dung để tách chương', 'error');
                    return 0;
                }

                var chapters = [];
                var lines = text.split('\n');
                var currentChapter = [];
                var lastTitle = null;
                
                // Hàm lấy mã chương dựa vào tiêu đề
                function getChapterCode(title) {
                    // Lấy số chương + tên chương, bỏ qua các ký tự đặc biệt
                    const match = title.match(/[Cc]hương\s*(\d+)\s*:/);
                    if (!match) return title.trim();
                    const chapterNum = match[1];
                    return `chap_${chapterNum}`;
                }
                
                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);

                    if (isChapterTitle) {
                        // Lấy mã chương để so sánh
                        const currentChapterCode = getChapterCode(line);
                        const lastTitleCode = lastTitle ? getChapterCode(lastTitle) : null;
                        
                        if (currentChapter.length > 0) {
                            // Kiểm tra nếu chương hiện tại khác chương trước đó
                            if (currentChapterCode !== lastTitleCode) {
                                chapters.push(currentChapter.join('\n'));
                                currentChapter = [line];
                                lastTitle = line;
                                console.log(`New chapter started: ${currentChapterCode}`);
                            } else {
                                console.log(`Skipped duplicate chapter: ${currentChapterCode}`);
                                // Không cần thêm dòng này vào chapter hiện tại vì nó là tiêu đề trùng lặp
                            }
                        } else {
                            currentChapter = [line];
                            lastTitle = line;
                            console.log(`First chapter started: ${currentChapterCode}`);
                        }
                    } else if (currentChapter.length > 0) {
                        currentChapter.push(line);
                    }
                }

                if (currentChapter.length > 0) {
                    chapters.push(currentChapter.join('\n'));
                    console.log("Added final chapter");
                }

                console.log(`Found ${chapters.length} chapters`);

                const processedChapters = this.processChapters(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']");

                if (processedChapters.length === 0) {
                    showNotification('Không tìm thấy chương nào', 'error');
                    return 0;
                }

                const neededForms = chaptersToFill.length - titles.length;
                if (neededForms > 0 && titles.length < MAX_CHAPTER_POST) {
                    console.log(`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']");
                }

                console.log(`Filling ${chaptersToFill.length} chapters into forms`);
                jQuery.each(titles, (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();
                        }
                        
                        console.log(`Filling chapter ${k + 1}: ${chapterTitle}`);
                        
                        if (!chapterTitle || chapterTitle.trim() === '') {
                            chapterTitle = "Vô đề";
                        }
                        
                        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) {
                    this.handleRemainingChapters(remainingChapters, chapters);
                } else {
                    this.showChaptersReport(chapters, processedChapters);
                }

                this.ELEMENTS.qpButtonSubmit.removeClass("btn-disable").addClass("btn-success");
                this.ELEMENTS.qpContent.val("Đã xử lý xong");
                
                return processedChapters.length;
            } catch (e) {
                console.error("Error in parseChapters:", e);
                showNotification('Có lỗi khi tách chương: ' + e.message, 'error');
                return 0;
            }
        },

        processChapters: function(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;
                
                console.log(`Chapter ${i+1} character count: ${charCount}`);
                
                if (charCount > 40000) {
                    const parts = Math.ceil(charCount / 40000);
                    console.log(`Splitting into ${parts} parts`);
                    const charsPerPart = Math.ceil(charCount / parts);
                    console.log(`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);
                        console.log(`Part ${part+1}: ${partContent.length} chars`);
                    }
                    
                    console.log(`Total processed: ${totalProcessed}/${charCount} chars`);
                } else {
                    processedChapters.push(chapters[i]);
                }
            }
            
            console.log(`After processing: ${processedChapters.length} chapters`);
            return processedChapters;
        },

        handleRemainingChapters: function(remainingChapters, originalChapters) {
            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');

                if (navigator.clipboard && navigator.clipboard.writeText) {
                    navigator.clipboard.writeText(clipboardContent)
                        .then(() => {
                            console.log(`Đã lưu lại ${remainingChapters.length} Chương vào clipboard`);
                            this.showChaptersReport(originalChapters, remainingChapters, true);
                        })
                        .catch(err => {
                            throw err;
                        });
                } else {
                    this.fallbackClipboardCopy(clipboardContent);
                    this.showChaptersReport(originalChapters, remainingChapters, true);
                }
            } catch (err) {
                console.error('Lỗi khi sao chép vào clipboard:', err);
                this.showManualCopyOption(remainingChapters);
                this.showChaptersReport(originalChapters, remainingChapters, true);
            }
        },

        fallbackClipboardCopy: function(content) {
            const tempTextarea = document.createElement('textarea');
            tempTextarea.style.position = 'fixed';
            tempTextarea.style.top = '0';
            tempTextarea.style.left = '0';
            tempTextarea.style.width = '2em';
            tempTextarea.style.height = '2em';
            tempTextarea.style.opacity = '0';
            tempTextarea.style.pointerEvents = 'none';
            tempTextarea.value = content;
            document.body.appendChild(tempTextarea);
            tempTextarea.focus();
            tempTextarea.select();
            const successful = document.execCommand('copy');
            document.body.removeChild(tempTextarea);
            
            if (!successful) {
                throw new Error('Không thể sao chép vào clipboard');
            }
            
            console.log(`Đã sao chép nội dung vào clipboard (execCommand)`);
        },

        showManualCopyOption: function(remainingChapters) {
            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');
            
            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);
            });
        },

        showChaptersReport: function(originalChapters, processedChapters, hasRemainingChapters = false) {
            let splitChapters = 0;
            let shortChapters = 0;
            let shortChapterDetails = [];
            let longChapterDetails = [];
            
            for (let i = 0; i < originalChapters.length; i++) {
                const chapterLines = originalChapters[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 < MIN_CHARS_WARNING) {
                    shortChapters++;
                    shortChapterDetails.push({
                        title: title,
                        length: chapterText.length
                    });
                }
            }
            
            const splittedChaptersCount = processedChapters.length - (originalChapters.length - splitChapters);
            let message = '';
            
            message = message.concat(`📝Đã xử lý ${originalChapters.length} Chương gốc\n`);
            message = message.concat(`📝Đã nhập ${Math.min(processedChapters.length, MAX_CHAPTER_POST)} Chương\n`);
            
            if (hasRemainingChapters) {
                message = message.concat(`📋Đã lưu lại ${processedChapters.length - Math.min(processedChapters.length, MAX_CHAPTER_POST)} 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 ${MIN_CHARS_WARNING} 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, shortChapters > 0 ? 'warning' : 'success');
        },

        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óa ${removed} chương trống từ ${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');

                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.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",
                html: "<div class='loading-spinner'></div>"
            });
            jQuery("body").append(loading);
        },

        hideLoading: function() {
            jQuery(".loading-overlay").remove();
        },

        resetAutoPost: function() {
            this.STATE.TOTAL_CHAPTERS = 0;
            this.STATE.POSTED_CHAPTERS = 0;
            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());
        }
    };

    // Tạo HTML cho form chương
    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>`;
    }

    // Thiết lập bộ đếm ký tự
    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 < MIN_CHARS_WARNING) {
                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>`);
                }
            }
        });
    }

    // Kiểm tra độ dài chương
    function validateChapterLengths() {
        let hasError = false;
        jQuery('form[name="postChapForm"] [data-gen="MK_GEN"]').each(function() {
            const form = this;
            const contentTextarea = form.querySelector('textarea[name^="introduce"]');
            const content = contentTextarea.value;
            
            if (content.length < MIN_CHARS_WARNING) {
                jQuery(contentTextarea).addClass('short-chapter');
                let warningIcon = form.querySelector('.warning-icon');
                
                if (!warningIcon) {
                    warningIcon = document.createElement('div');
                    warningIcon.className = 'warning-icon';
                    warningIcon.innerHTML = '⚠️';
                    warningIcon.style.position = 'absolute';
                    warningIcon.style.right = '10px';
                    warningIcon.style.top = '10px';
                    warningIcon.style.color = 'red';
                    warningIcon.style.fontSize = '20px';
                    contentTextarea.parentNode.style.position = 'relative';
                    contentTextarea.parentNode.appendChild(warningIcon);
                }
                
                hasError = true;
            } else {
                jQuery(contentTextarea).removeClass('short-chapter');
                const warningIcon = form.querySelector('.warning-icon');
                
                if (warningIcon) {
                    warningIcon.remove();
                }
            }
        });
        
        if (hasError) {
            showNotification('Có chương quá ngắn (dưới 3000 ký tự). Vui lòng kiểm tra lại.', 'warning');
        }
        
        return !hasError;
    }

    // Khởi động script
    jQuery(document).ready(function() {
        dăngnhanhTTV.init();
        
        // Tự động thiết lập bộ đếm ký tự cho các form có sẵn
        setTimeout(() => {
            jQuery("[name^=introduce]").each(function() {
                jQuery(this).trigger('input');
            });
        }, 1000);
    });
})();