您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Công cụ đăng chương hiện đại cho Tàng Thư Viện với UI/UX được tối ưu
当前为
// ==UserScript== // @name TTV Auto Upload // @namespace http://tampermonkey.net/ // @version 5.6 // @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, TARGET_CHAPTERS: 0 }, ELEMENTS: { qpContent: null, qpButtonSubmit: null, qpButtonRemoveEmpty: null, qpButtonReset: null }, init: function() { try { console.log('[TTV-DEBUG] Script bắt đầu khởi tạo...'); console.log('[TTV-DEBUG] Phiên bản script: 3.0'); this.initializeChapterValues(); console.log('[TTV-DEBUG] Đã khởi tạo giá trị chương'); // 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'); this.STATE.TARGET_CHAPTERS = parseInt(localStorage.getItem('TTV_TARGET_CHAPTERS') || '0'); this.STATE.PROCESSED_CHAPTERS = parseInt(localStorage.getItem('TTV_PROCESSED_CHAPTERS') || '0'); console.log(`[TTV-DEBUG] Khôi phục tự động đăng: ${this.STATE.POSTED_CHAPTERS}/${this.STATE.TOTAL_CHAPTERS}`); console.log(`[TTV-DEBUG] Mục tiêu xử lý: ${this.STATE.PROCESSED_CHAPTERS}/${this.STATE.TARGET_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"); 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.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 // Kiểm tra tiêu đề liên tiếp với khoảng cách tối đa là 3 dòng let isConsecutiveTitle = false; const MAX_DISTANCE = 3; // Khoảng cách tối đa giữa hai tiêu đề để coi là liên tiếp // Duyệt qua MAX_DISTANCE dòng tiếp theo để tìm tiêu đề trùng lặp for (let j = 1; j <= MAX_DISTANCE && i + j < lines.length; j++) { const nextLine = lines[i + j]; // Kiểm tra xem dòng tiếp theo có phải là tiêu đề chương không const nextLineIsChapter = /^\t[Cc]hương\s*\d+\s*:/.test(nextLine) || /^\s{4,}[Cc]hương\s*\d+\s*:/.test(nextLine); if (nextLineIsChapter) { const nextChapterMatch = nextLine.match(/[Cc]hương\s*(\d+)\s*:/); const nextChapterNum = nextChapterMatch ? parseInt(nextChapterMatch[1]) : 0; // Kiểm tra xem số chương có trùng không if (nextChapterNum === currentChapterNum) { isConsecutiveTitle = true; debugOutput.push(` -> Detected consecutive title with same chapter number: ${currentChapterNum}, distance: ${j} line(s)`); // Thêm kiểm tra tiêu đề tương tự const currentTitle = line.split(':')[1]?.trim() || ''; const nextTitle = nextLine.split(':')[1]?.trim() || ''; // Kiểm tra nếu tiêu đề giống nhau một phần (ít nhất 70% giống nhau) if (currentTitle && nextTitle) { const shorterLength = Math.min(currentTitle.length, nextTitle.length); if (shorterLength > 0) { // Kiểm tra nếu tiêu đề này là một phần của tiêu đề kia const isSimilar = currentTitle.includes(nextTitle) || nextTitle.includes(currentTitle); debugOutput.push(` -> Title similarity check: ${isSimilar ? 'similar' : 'different'}, current: "${currentTitle}", next: "${nextTitle}"`); } } break; } } // Nếu tìm thấy dòng không trống và không phải tiêu đề chương, có thể đây là nội dung thực sự if (nextLine.trim() !== '' && !nextLineIsChapter) { break; } } // Kiểm tra nếu dòng trước đó cũng là tiêu đề chương và vừa được xử lý const isAfterPreviousTitle = (i > 0 && line !== lastTitle && currentChapter.length <= 1); if (currentChapter.length > 0) { // Nếu không phải là tiêu đề liên tiếp hoặc sau một tiêu đề khác if (line !== lastTitle && !isConsecutiveTitle && !isAfterPreviousTitle) { // Kiểm tra số chương trùng if (isDuplicateNumber) { debugOutput.push(` -> Warning: Duplicate chapter number ${currentChapterNum} detected`); showNotification(`Cảnh báo: Số chương ${currentChapterNum} bị trùng lặp`, 'warning'); } chapters.push(currentChapter.join('\n')); currentChapter = [line]; lastTitle = line; debugOutput.push(` -> New chapter started`); if (currentChapterNum > 0) { debugOutput.push(`Found chapter number: ${currentChapterNum}`); } } else { if (isConsecutiveTitle) { // Ghi nhớ tiêu đề này để so sánh sau này const currentTitleContent = line.split(':')[1]?.trim() || ''; const previousTitleContent = lastTitle.split(':')[1]?.trim() || ''; // Nếu tiêu đề hiện tại dài hơn tiêu đề trước đó, ưu tiên giữ lại tiêu đề này if (currentTitleContent.length > previousTitleContent.length) { debugOutput.push(` -> Replacing previous shorter title "${previousTitleContent}" with longer title "${currentTitleContent}"`); // Giữ lại tiêu đề dài hơn, nhưng vẫn đánh dấu là chương mới if (currentChapter.length > 1) { chapters.push(currentChapter.join('\n')); } currentChapter = [line]; lastTitle = line; } else { debugOutput.push(` -> Ignoring consecutive title (current is shorter or equal length)`); // Bỏ qua dòng này vì là tiêu đề liên tiếp và không dài hơn continue; } } else { debugOutput.push(` -> Skipped duplicate title`); } } } else { currentChapter = [line]; lastTitle = line; debugOutput.push(` -> First chapter started`); if (currentChapterNum > 0) { debugOutput.push(`Found chapter number: ${currentChapterNum}`); } } } else if (currentChapter.length > 0) { currentChapter.push(line); } } if (currentChapter.length > 0) { chapters.push(currentChapter.join('\n')); debugOutput.push("-> Added final chapter"); } debugOutput.push("=== Results ==="); debugOutput.push(`Found ${chapters.length} chapters`); const processedChapters = []; for (let i = 0; i < chapters.length; i++) { const chapterLines = chapters[i].split('\n'); const title = chapterLines.shift().trim(); const chapterText = chapterLines.join('\n'); const charCount = chapterText.length; debugOutput.push(`Chapter ${i+1} character count: ${charCount}`); if (charCount > 40000) { const parts = Math.ceil(charCount / 40000); debugOutput.push(`Splitting into ${parts} parts`); const charsPerPart = Math.ceil(charCount / parts); debugOutput.push(`Characters per part: ~${charsPerPart}`); let currentText = chapterText; let totalProcessed = 0; for (let part = 0; part < parts; part++) { const isLastPart = part === parts - 1; const targetSize = isLastPart ? currentText.length : charsPerPart; let endPos = Math.min(targetSize, currentText.length); if (!isLastPart && endPos < currentText.length) { const nextParagraph = currentText.indexOf('\n\n', endPos - 500); if (nextParagraph !== -1 && nextParagraph < endPos + 500) { endPos = nextParagraph + 2; } else { const sentenceEnd = Math.max( currentText.lastIndexOf('. ', endPos), currentText.lastIndexOf('! ', endPos), currentText.lastIndexOf('? ', endPos) ); if (sentenceEnd !== -1 && sentenceEnd > endPos - 500) { endPos = sentenceEnd + 2; } } } const partContent = currentText.substring(0, endPos); totalProcessed += partContent.length; currentText = currentText.substring(endPos); let chapterTitle = title; if (title.includes(':')) { chapterTitle = title.substring(title.indexOf(':') + 1).trim(); } let newTitle = `${title} (Phần ${part+1}/${parts})`; processedChapters.push(newTitle + '\n' + partContent); debugOutput.push(`Part ${part+1}: ${partContent.length} chars`); } debugOutput.push(`Total processed: ${totalProcessed}/${charCount} chars`); } else { processedChapters.push(chapters[i]); } } debugOutput.push(`After processing: ${processedChapters.length} chapters`); const chaptersToFill = processedChapters.slice(0, MAX_CHAPTER_POST); const remainingChapters = processedChapters.slice(MAX_CHAPTER_POST); var titles = jQuery("input[name^='chap_name']"); var contents = jQuery("textarea[name^='introduce']"); var advs = jQuery("textarea[name^='adv']"); debugOutput.push(`Forms found: ${titles.length}`); if (processedChapters.length === 0) { showNotification('Không tìm thấy chương nào', 'error'); jQuery('#debug-output').text(debugOutput.join('\n')); return; } if (remainingChapters.length > 0) { debugOutput.push(`${remainingChapters.length} chapters will be copied to clipboard`); } const neededForms = chaptersToFill.length - titles.length; if (neededForms > 0 && titles.length < MAX_CHAPTER_POST) { debugOutput.push(`Need to add ${neededForms} more forms`); for (let i = 0; i < neededForms && (titles.length + i) < MAX_CHAPTER_POST; i++) { this.addNewChapter(); } titles = jQuery("input[name^='chap_name']"); contents = jQuery("textarea[name^='introduce']"); advs = jQuery("textarea[name^='adv']"); } debugOutput.push(`Filling ${chaptersToFill.length} chapters into forms`); jQuery.each(titles, function(k, v) { if (k < chaptersToFill.length) { var content = chaptersToFill[k].split('\n'); var title = content.shift().trim(); var chapterTitle = title; if (title.includes(':')) { chapterTitle = title.substring(title.indexOf(':') + 1).trim(); } debugOutput.push(`\nFilling chapter ${k + 1}:`); debugOutput.push(`Original title: ${title}`); debugOutput.push(`Extracted title: ${chapterTitle}`); debugOutput.push(`Content length: ${content.length} lines`); if (!chapterTitle || chapterTitle.trim() === '') { chapterTitle = "Vô đề"; debugOutput.push(`Empty title detected, using default: ${chapterTitle}`); } titles[k].value = chapterTitle; contents[k].value = HEADER_SIGN + "\r\n" + content.join('\n') + "\r\n" + FOOTER_SIGN; if (advs[k]) advs[k].value = ""; jQuery(contents[k]).trigger('input'); } }); if (remainingChapters.length > 0) { try { const clipboardContent = remainingChapters.map(chap => { const lines = chap.trim().split('\n'); if (lines.length > 0) { if (!lines[0].startsWith('\t')) { lines[0] = '\t' + lines[0]; } } return lines.join('\n'); }).join('\n\n---CHAPTER_SEPARATOR---\n\n'); // Cập nhật số chương đã xử lý this.STATE.PROCESSED_CHAPTERS += remainingChapters.length; localStorage.setItem('TTV_PROCESSED_CHAPTERS', this.STATE.PROCESSED_CHAPTERS.toString()); // Hiển thị thông tin let message = `📝 Đã xử lý ${remainingChapters.length} chương mới\n`; message += `📋 Tổng số đã xử lý: ${this.STATE.PROCESSED_CHAPTERS}/${this.STATE.TARGET_CHAPTERS}\n`; // Copy vào clipboard navigator.clipboard.writeText(clipboardContent) .then(() => { showNotification(message, 'success'); }) .catch((error) => { console.error('Lỗi clipboard:', error); showNotification('Không thể copy vào clipboard. Vui lòng thử lại.', 'error'); }); // Kiểm tra hoàn thành mục tiêu if (this.STATE.TARGET_CHAPTERS > 0 && this.STATE.PROCESSED_CHAPTERS >= this.STATE.TARGET_CHAPTERS) { // Xóa clipboard navigator.clipboard.writeText("") .then(() => { 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'); showNotification('Đã đạt mục tiêu số chương cần xử lý', 'success'); }); } } catch (error) { console.error('Lỗi xử lý chương:', error); showNotification('Có lỗi khi xử lý các chương. Vui lòng thử lại.', 'error'); } } this.ELEMENTS.qpButtonSubmit.removeClass("btn-disable").addClass("btn-success"); this.ELEMENTS.qpContent.val("Đã thực hiện xong"); if (remainingChapters.length === 0) { let splitChapters = 0; let shortChapters = 0; let shortChapterDetails = []; let longChapterDetails = []; for (let i = 0; i < chapters.length; i++) { const chapterLines = chapters[i].split('\n'); const title = chapterLines.shift().trim(); const chapterText = chapterLines.join('\n'); if (chapterText.length > 40000) { splitChapters++; const partsCount = Math.ceil(chapterText.length / 40000); longChapterDetails.push({ title: title, parts: partsCount }); } if (chapterText.length < 3000) { shortChapters++; shortChapterDetails.push({ title: title, length: chapterText.length }); } } const splittedChaptersCount = processedChapters.length - (chapters.length - splitChapters); let message = ''; message = message.concat(`📝Đã xử lý ${processedChapters.length} Chương\n`); message = message.concat(`📝Đã nhập ${Math.min(processedChapters.length, MAX_CHAPTER_POST)} Chương\n`); if (splitChapters > 0) { message = message.concat(`📑Có ${splitChapters} Chương dài chia thành ${splittedChaptersCount} Chương\n`); longChapterDetails.forEach(chapter => { let chapterName = chapter.title; if (chapterName.includes(':')) { chapterName = chapterName.trim(); } message = message.concat(` - ${chapterName}: ${chapter.parts} Chương\n`); }); } if (shortChapters > 0) { message = message.concat(`⚠️<span class="short-chapters-warning">Có ${shortChapters} chương dưới 3000 ký tự</span>\n`); shortChapterDetails.forEach(chapter => { let chapterName = chapter.title; if (chapterName.includes(':')) { chapterName = chapterName.trim(); } message = message.concat(`<span class="short-chapters-warning"> - ${chapterName}: có ${chapter.length.toLocaleString()} ký tự</span>\n`); }); } showNotification(message, 'success'); } jQuery('#debug-output').text(debugOutput.join('\n')); return processedChapters.length; } catch (e) { console.error("Error in performAction:", e); showNotification('Có lỗi khi tách chương', 'error'); return 0; } }, removeEmptyChapters: function() { const forms = document.querySelectorAll('[data-gen="MK_GEN"]'); let removed = 0; forms.forEach(form => { const content = form.querySelector('textarea[name^="introduce"]').value.trim(); if (!content) { form.remove(); removed++; this.updateChapNumber(false); } }); showNotification(`Đã xử lý ${forms.length} chương`, 'info'); }, toggleAutoPost: function() { const targetChapters = parseInt(prompt("Nhập tổng số chương cần xử lý (nhập 0 để tắt tự động đăng):")) || 0; if (targetChapters === 0) { // Tắt chế độ tự động this.STATE.AUTO_POST = false; this.STATE.TOTAL_CHAPTERS = 0; this.STATE.POSTED_CHAPTERS = 0; this.STATE.TARGET_CHAPTERS = 0; this.STATE.PROCESSED_CHAPTERS = 0; // Cập nhật localStorage localStorage.setItem('TTV_AUTO_POST', 'false'); localStorage.removeItem('TTV_TOTAL_CHAPTERS'); localStorage.removeItem('TTV_POSTED_CHAPTERS'); localStorage.removeItem('TTV_TARGET_CHAPTERS'); localStorage.removeItem('TTV_PROCESSED_CHAPTERS'); // Cập nhật giao diện this.ELEMENTS.qpButtonAutoPost.text('🔄 (OFF)'); this.ELEMENTS.qpButtonAutoPost.removeClass('btn-info').addClass('btn-warning'); showNotification('Đã tắt chế độ tự động đăng', 'info'); return; } this.STATE.AUTO_POST = true; this.STATE.TOTAL_CHAPTERS = targetChapters; this.STATE.TARGET_CHAPTERS = targetChapters; this.STATE.POSTED_CHAPTERS = 0; this.STATE.PROCESSED_CHAPTERS = 0; localStorage.setItem('TTV_AUTO_POST', 'true'); localStorage.setItem('TTV_TOTAL_CHAPTERS', targetChapters.toString()); localStorage.setItem('TTV_TARGET_CHAPTERS', targetChapters.toString()); localStorage.setItem('TTV_POSTED_CHAPTERS', '0'); localStorage.setItem('TTV_PROCESSED_CHAPTERS', '0'); this.ELEMENTS.qpButtonAutoPost.text(`🔄 (ON) 0/${targetChapters}`); this.ELEMENTS.qpButtonAutoPost.removeClass('btn-warning').addClass('btn-info'); showNotification(`Đã bật chế độ tự động đăng, mục tiêu: ${targetChapters} chương`, 'success'); }, runAutoPostSequence: async function() { if (!this.STATE.AUTO_POST) return; // Kiểm tra số chương trong form const chaptersInForm = jQuery("textarea[name^='introduce']").length; console.log(`[TTV-DEBUG] Số chương trong form: ${chaptersInForm}`); if (chaptersInForm < 10) { showNotification(`Số chương trong form (${chaptersInForm}) dưới 10 chương, dừng tự động đăng`, 'warning'); // Tắt chế độ tự động this.STATE.AUTO_POST = false; this.ELEMENTS.qpButtonAutoPost.text('🔄 (OFF)'); this.ELEMENTS.qpButtonAutoPost.removeClass('btn-info').addClass('btn-warning'); return; } // Tiếp tục xử lý đăng chương tự động if (this.STATE.POSTED_CHAPTERS < this.STATE.TOTAL_CHAPTERS) { this.submitChapters(); } }, submitChapters: function() { const postButton = jQuery('button[type="submit"]'); if (!postButton.length) { showNotification('Không tìm thấy nút đăng chương!', 'error'); return; } if (!validateChapterLengths()) { showNotification('Có chương chưa đủ độ dài tối thiểu (3000 ký tự)!', 'error'); return; } // Cập nhật số chương đã đăng if (this.STATE.AUTO_POST) { this.STATE.POSTED_CHAPTERS++; localStorage.setItem('TTV_POSTED_CHAPTERS', this.STATE.POSTED_CHAPTERS.toString()); // Cập nhật hiển thị trên nút this.ELEMENTS.qpButtonAutoPost.text(`🔄 (ON) ${this.STATE.POSTED_CHAPTERS}/${this.STATE.TOTAL_CHAPTERS}`); // Kiểm tra nếu đã đạt mục tiêu 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'); showNotification(`Đã hoàn thành mục tiêu ${this.STATE.TOTAL_CHAPTERS} chương`, 'success'); } } // Thực hiện đăng chương postButton.click(); // Nếu đang trong chế độ tự động, chuẩn bị cho lần đăng tiếp theo if (this.STATE.AUTO_POST) { // Reload trang sau 5 giây để chuẩn bị cho lần đăng tiếp setTimeout(() => { window.location.reload(); }, 5000); } }, addNewChapter: function() { if ((this.STATE.CHAP_NUMBER + 1) <= MAX_CHAPTER_POST) { this.updateChapNumber(true); const html = createChapterHTML(this.STATE.CHAP_NUMBER); jQuery('#add-chap').before(html); setupCharacterCounter(); } else { showNotification(`Chỉ có thể đăng tối đa ${MAX_CHAPTER_POST} chương một lần`, 'warning'); } }, updateChapNumber: function(isAdd) { try{ if (isAdd) { let maxStt =0; let maxSerial = 0; jQuery('input[name^="chap_stt"]').each(function() { const val = parseInt(jQuery(this).val()) || 0; maxStt = Math.max(maxStt, val); }); jQuery('input[name^="chap_number"]').each(function() { const val = parseInt(jQuery(this).val()) || 0; maxSerial = Math.max(maxSerial, val); }); const chapStt = parseInt(jQuery('.chap_stt1').val()) || 0; const chapSerial = parseInt(jQuery('.chap_serial').val()) || 0; maxStt = Math.max(maxStt, chapStt); maxSerial = Math.max(maxSerial, chapSerial); this.STATE.CHAP_STT = maxStt + 1; this.STATE.CHAP_SERIAL = maxSerial + 1; this.STATE.CHAP_NUMBER++; } else { if (this.STATE.CHAP_NUMBER > this.STATE.CHAP_NUMBER_ORIGINAL) { this.STATE.CHAP_NUMBER--; } if (this.STATE.CHAP_STT > this.STATE.CHAP_STT_ORIGINAL) { this.STATE.CHAP_STT--; } if (this.STATE.CHAP_SERIAL > this.STATE.CHAP_SERIAL_ORIGINAL) { this.STATE.CHAP_SERIAL--; } } jQuery('#chap_number').val(this.STATE.CHAP_NUMBER); jQuery('#chap_stt').val(this.STATE.CHAP_STT); jQuery('#chap_serial').val(this.STATE.CHAP_SERIAL); jQuery('#countNumberPost').text(this.STATE.CHAP_NUMBER); } catch (e) { console.log("Lỗi: " + e); } }, showLoading: function() { const loading = jQuery("<div>", { class: "loading-overlay", css: { position: "fixed", inset: 0, backgroundColor: "rgba(0, 0, 0, 0.5)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 9999 }, html: "<div class='loading-spinner'></div>" }); jQuery("body").append(loading); }, hideLoading: function() { jQuery(".loading-overlay").remove(); }, resetAutoPost: function() { this.STATE.TOTAL_CHAPTERS = 0; this.STATE.POSTED_CHAPTERS = 0; this.STATE.TARGET_CHAPTERS = 0; this.STATE.PROCESSED_CHAPTERS = 0; localStorage.removeItem('TTV_TOTAL_CHAPTERS'); localStorage.removeItem('TTV_POSTED_CHAPTERS'); localStorage.removeItem('TTV_TARGET_CHAPTERS'); localStorage.removeItem('TTV_PROCESSED_CHAPTERS'); this.ELEMENTS.qpButtonAutoPost.text('🔄 (OFF)'); this.ELEMENTS.qpButtonAutoPost.removeClass('btn-info').addClass('btn-warning'); showNotification('Đã reset tất cả số liệu 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(); })();