您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Công cụ đăng chương đơn giản cho Tàng Thư Viện
当前为
// ==UserScript== // @name TTV Auto Upload // @namespace http://tampermonkey.net/ // @version 1.3 // @description Công cụ đăng chương đơn giản cho Tàng Thư Viện // @author HA // @match https://tangthuvien.net/dang-chuong/story/* // @match https://tangthuvien.net/danh-sach-chuong/story/* // @grant none // ==/UserScript== (function() { 'use strict'; const style = document.createElement('style'); style.textContent = ` #ttv-panel { position: fixed; top: 50px; right: 20px; background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); width: 400px; z-index: 9998; max-height: 90vh; overflow-y: auto; } #ttv-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee; } #ttv-header h3 { color: #2196F3; margin: 0; font-size: 18px; font-weight: 600; } #ttv-stats { display: flex; gap: 10px; align-items: center; font-size: 14px; color: #666; } #ttv-stats .stat { padding: 4px 8px; border-radius: 6px; background: #f5f5f5; } #ttv-content { width: 100%; height: 120px; margin-bottom: 15px; padding: 12px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 14px; font-family: monospace; transition: all 0.2s ease; resize: vertical; background: #fff; } #ttv-content:focus { border-color: #2196F3; box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1); outline: none; } .btn-group { display: flex; gap: 10px; margin: 15px 0; } #ttv-panel button { flex: 1; padding: 12px 15px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 14px; transition: all 0.2s; position: relative; color: white; display: flex; align-items: center; justify-content: center; gap: 8px; } #ttv-panel button:hover { transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #ttv-panel button:active { transform: translateY(0); } #ttv-panel button:disabled { opacity: 0.6; cursor: not-allowed; transform: none; box-shadow: none; } .btn-auto { background: linear-gradient(45deg, #4CAF50, #45a049); } .btn-manual { background: linear-gradient(45deg, #2196F3, #1e88e5); } #ttv-panel button.processing { pointer-events: none; } #ttv-panel button.processing:after { content: ""; position: absolute; width: 20px; height: 20px; right: 10px; border: 2px solid rgba(255,255,255,0.3); border-top: 2px solid white; border-radius: 50%; animation: spin 1s linear infinite; } #ttv-chapters { width: 100%; margin-bottom: 15px; border: 1px solid #eee; border-radius: 8px; max-height: 300px; overflow-y: auto; background: #fafafa; display: none; } #ttv-chapters.has-chapters { display: block; } .chapter-item { padding: 15px; border-bottom: 1px solid #eee; background: white; transition: all 0.2s; } .chapter-item:hover { background: #f5f5f5; } .chapter-item:last-child { border-bottom: none; } .chapter-title { font-weight: 600; margin-bottom: 8px; color: #333; font-size: 14px; } .chapter-stats { font-size: 12px; color: #666; display: flex; gap: 10px; align-items: center; } .chapter-warning { color: #f44336; font-weight: 500; padding: 2px 6px; background: rgba(244, 67, 54, 0.1); border-radius: 4px; } .chapter-long { color: #ff9800; font-weight: 500; padding: 2px 6px; background: rgba(255, 152, 0, 0.1); border-radius: 4px; } #ttv-notification { margin-top: 10px; padding: 10px 15px; border-radius: 8px; font-size: 14px; display: flex; align-items: center; gap: 8px; opacity: 0; transition: opacity 0.3s ease; } #ttv-notification.show { opacity: 1; } #ttv-notification.success { background-color: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; } #ttv-notification.error { background-color: #ffebee; color: #c62828; border: 1px solid #ffcdd2; } #ttv-notification.warning { background-color: #fff3e0; color: #ef6c00; border: 1px solid #ffe0b2; } #ttv-notification.info { background-color: #e3f2fd; color: #1565c0; border: 1px solid #bbdefb; } .loading-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; opacity: 0; transition: opacity 0.3s ease; } .loading-overlay.show { opacity: 1; } .loading-content { background: white; padding: 20px; border-radius: 8px; text-align: center; } .loading-spinner { width: 40px; height: 40px; border: 3px solid #f3f3f3; border-top: 3px solid #2196F3; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 10px; } .chapter-character-count { text-align: right; font-size: 12px; margin-top: 5px; color: #666; } textarea[name^="introduce"].short-chapter { border: 2px solid #f44336 !important; background-color: rgba(244, 67, 54, 0.05) !important; animation: shortChapterBlink 1s infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes shortChapterBlink { 0% { background-color: rgba(244, 67, 54, 0.05); } 50% { background-color: rgba(244, 67, 54, 0.1); } 100% { background: rgba(244, 67, 54, 0.05); } } .chapter-counter { position: absolute; top: -8px; right: -8px; background: #ff5722; color: white; border-radius: 12px; padding: 2px 6px; font-size: 12px; font-weight: bold; } `; document.head.appendChild(style); const TTVManager = { STATE: { chapterNumber: 1, chapterSTT: 1, chapterSerial: 1, isAuto: false, isProcessing: false, totalChapters: 0, processedChapters: 0 }, init: function() { console.log('[TTV-DEBUG] Initializing script...'); this.createFormContainer = this.createFormContainer.bind(this); this.initializeChapterValues = this.initializeChapterValues.bind(this); this.createInterface = this.createInterface.bind(this); this.setupEventListeners = this.setupEventListeners.bind(this); this.setupCharacterCounter = this.setupCharacterCounter.bind(this); this.showNotification = this.showNotification.bind(this); this.showLoading = this.showLoading.bind(this); this.hideLoading = this.hideLoading.bind(this); this.createFormContainer(); this.initializeChapterValues(); this.createInterface(); this.setupEventListeners(); this.setupCharacterCounter(); console.log('[TTV-DEBUG] Script initialized successfully'); this.showNotification('Công cụ đã sẵn sàng', 'success'); }, createInterface: function() { const panel = document.createElement('div'); panel.id = 'ttv-panel'; panel.innerHTML = ` <div id="ttv-header"> <h3>📝 ĐĂNG CHƯƠNG</h3> <div id="ttv-stats"> <span class="stat" id="total-chapters">0 chương</span> <span class="stat" id="processed-chapters">0/0</span> </div> </div> <div id="ttv-chapters"></div> <textarea id="ttv-content" placeholder="Dán nội dung vào đây để tự động tách chương... Hỗ trợ các định dạng: - Chương 1: ... - [Tab]Chương 2: ... - Chương 3: ..." ></textarea> <div class="btn-group"> <button class="btn-auto" id="ttv-auto"> <span>🔄 Tự động đăng</span> <div class="chapter-counter">0/10</div> </button> <button class="btn-manual" id="ttv-manual"> <span>📝 Đăng thủ công</span> </button> </div> <div id="ttv-notification"></div> `; document.body.appendChild(panel); }, updateStats: function() { document.getElementById('total-chapters').textContent = `${this.STATE.totalChapters} chương`; document.getElementById('processed-chapters').textContent = `${this.STATE.processedChapters}/${this.STATE.totalChapters}`; document.querySelector('.chapter-counter').textContent = `${Math.min(this.STATE.totalChapters, 10)}/10`; }, createChapterHTML: function(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>`; }, setupCharacterCounter: function() { 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>`); } } }); }, validateChapterLengths: function() { 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; }, 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.chapterNumber = this.STATE.chapterNumberOriginal = chap_number || 1; this.STATE.chapterSTT = this.STATE.chapterSTTOriginal = chap_stt || 1; this.STATE.chapterSerial = this.STATE.chapterSerialOriginal = chap_serial || 1; } catch (e) { console.error("Error initializing chapter values:", e); } }, showNotification: function(message, type = 'info') { const notification = document.getElementById('ttv-notification'); let icon = ''; switch(type) { case 'success': icon = '✅'; break; case 'error': icon = '❌'; break; case 'warning': icon = '⚠️'; break; case 'info': icon = 'ℹ️'; break; } notification.className = type; notification.innerHTML = ` <span class="notification-icon">${icon}</span> ${message} `; notification.classList.add('show'); if (type === 'success' || type === 'info') { setTimeout(() => { notification.classList.remove('show'); }, 5000); } }, showLoading: function(message = 'Đang xử lý...') { let overlay = document.querySelector('.loading-overlay'); if (!overlay) { overlay = document.createElement('div'); overlay.className = 'loading-overlay'; overlay.innerHTML = ` <div class="loading-content"> <div class="loading-spinner"></div> <div class="loading-message">${message}</div> </div> `; document.body.appendChild(overlay); setTimeout(() => overlay.classList.add('show'), 0); } }, hideLoading: function() { const overlay = document.querySelector('.loading-overlay'); if (overlay) { overlay.classList.remove('show'); setTimeout(() => overlay.remove(), 300); } }, // ...rest of the functions remain largely unchanged... (handlePaste, performAction, etc.) 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.chapterSTT = maxStt + 1; this.STATE.chapterSerial = maxSerial + 1; this.STATE.chapterNumber++; } else { if (this.STATE.chapterNumber > this.STATE.chapterNumberOriginal) { this.STATE.chapterNumber--; } if (this.STATE.chapterSTT > this.STATE.chapterSTTOriginal) { this.STATE.chapterSTT--; } if (this.STATE.chapterSerial > this.STATE.chapterSerialOriginal) { this.STATE.chapterSerial--; } } jQuery('#chap_number').val(this.STATE.chapterNumber); jQuery('#chap_stt').val(this.STATE.chapterSTT); jQuery('#chap_serial').val(this.STATE.chapterSerial); jQuery('#countNumberPost').text(this.STATE.chapterNumber); } catch (e) { console.log("Lỗi: " + e); } }, // ...rest of the functions remain largely unchanged... (removeEmptyChapters, toggleAutoPost, runAutoPostSequence, submitChapters, addNewChapter, resetAutoPost, toggleAutoMode) setupEventListeners: function() { jQuery('#ttv-content').on('paste', this.handlePaste.bind(this)); jQuery('#ttv-manual').on('click', this.submitChapters.bind(this)); jQuery('#ttv-auto').on('click', this.toggleAutoPost.bind(this)); jQuery('#qpButtonRemoveEmpty').on('click', this.removeEmptyChapters.bind(this)); jQuery('#qpButtonReset').on('click', this.resetAutoPost.bind(this)); jQuery('#qpOptionLoop').on('change', this.toggleAutoMode.bind(this)); this.setupCharacterCounter(); if (window.location.href.includes('/dang-chuong/story/')) { setTimeout(() => { if (this.STATE.isAuto && this.STATE.processedChapters < this.STATE.totalChapters) { this.runAutoPostSequence(); } }, 2000); } }, handlePasteButton: function() { this.showLoading(); navigator.clipboard.readText() .then(text => { jQuery('#ttv-content').val(text); setTimeout(() => { this.performAction(); this.hideLoading(); }, 100); }) .catch(err => { console.error('Không thể đọc dữ liệu từ clipboard:', err); this.hideLoading(); this.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(); jQuery('#ttv-content').val(""); this.showLoading(); const pastedText = e.originalEvent.clipboardData.getData('text'); jQuery('#ttv-content').val(pastedText); setTimeout(() => { this.performAction(); this.hideLoading(); }, 100); }, performAction: function() { try { console.log("Starting performAction"); var text = jQuery('#ttv-content').val(); if (!text) { this.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(''); } // 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); debugOutput.push(`Line ${i}: ${visualizeWhitespace(line.substring(0, 50))}${line.length > 50 ? '...' : ''}`); debugOutput.push(` Is chapter: ${isChapterTitle}`); 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; debugOutput.push(` -> New chapter started: ${currentChapterCode}`); } else { debugOutput.push(` -> 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; debugOutput.push(` -> First chapter started: ${currentChapterCode}`); } } 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) { this.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`); }); } if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(clipboardContent) .then(() => { debugOutput.push(`📋Đã lưu lại ${remainingChapters.length} Chương\n`); this.showNotification(message, remainingChapters.length > 0 ? 'warning' : 'success'); }) .catch(err => { throw err; }); } else { 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 = clipboardContent; 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'); } debugOutput.push(`Đã sao chép ${remainingChapters.length} chương vào clipboard (execCommand)`); this.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}`); this.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); }); } } jQuery('#qpButtonSubmit').removeClass("btn-disable").addClass("btn-success"); jQuery('#ttv-content').val("Đã xử lý 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`); }); } this.showNotification(message, 'success'); } jQuery('#debug-output').text(debugOutput.join('\n')); return processedChapters.length; } catch (e) { console.error("Error in performAction:", e); this.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); } }); this.showNotification(`Đã xử lý ${forms.length} chương`, 'info'); }, toggleAutoPost: function() { this.STATE.isAuto = !this.STATE.isAuto; if (this.STATE.isAuto) { this.STATE.totalChapters = parseInt(prompt("Nhập tổng số lần tự động đăng:", "10")) || 0; this.STATE.processedChapters = parseInt(localStorage.getItem('TTV_POSTED_CHAPTERS') || '0'); localStorage.setItem('TTV_TOTAL_CHAPTERS', this.STATE.totalChapters.toString()); this.updateStats(); this.showNotification(`Đã bật tự động đăng (${this.STATE.processedChapters}/${this.STATE.totalChapters})`, 'success'); if (window.location.href.includes('/dang-chuong/story/')) { setTimeout(() => this.runAutoPostSequence(), 2000); } } else { localStorage.setItem('TTV_AUTO_POST', 'false'); this.STATE.processedChapters = 0; localStorage.setItem('TTV_POSTED_CHAPTERS', '0'); this.STATE.totalChapters = 0; localStorage.setItem('TTV_TOTAL_CHAPTERS', '0'); this.updateStats(); this.showNotification('Đã tắt tự động đăng và reset số lần đăng', 'info'); } }, runAutoPostSequence: function() { if (this.STATE.processedChapters >= this.STATE.totalChapters) { this.STATE.isAuto = false; localStorage.setItem('TTV_AUTO_POST', 'false'); this.STATE.processedChapters = 0; localStorage.setItem('TTV_POSTED_CHAPTERS', '0'); this.STATE.totalChapters = 0; localStorage.setItem('TTV_TOTAL_CHAPTERS', '0'); this.updateStats(); this.showNotification(`Đã hoàn thành tự động đăng ${this.STATE.totalChapters}/${this.STATE.totalChapters} chương và đã reset số lần đăng`, 'success'); return; } if (!this.STATE.isAuto) { return; } setTimeout(() => { if (this.STATE.isAuto) { this.handlePasteButton(); setTimeout(() => { if (this.STATE.isAuto) { this.submitChapters(); } }, 3000); } }, 2000); }, submitChapters: function() { if (!this.validateChapterLengths()) { return; } this.showLoading(); document.querySelector('form[name="postChapForm"] button[type="submit"]').click(); if (this.STATE.isAuto) { this.STATE.processedChapters++; localStorage.setItem('TTV_POSTED_CHAPTERS', this.STATE.processedChapters.toString()); if (this.STATE.processedChapters >= this.STATE.totalChapters) { this.STATE.isAuto = false; localStorage.setItem('TTV_AUTO_POST', 'false'); this.STATE.processedChapters = 0; localStorage.setItem('TTV_POSTED_CHAPTERS', '0'); this.STATE.totalChapters = 0; localStorage.setItem('TTV_TOTAL_CHAPTERS', '0'); this.updateStats(); setTimeout(() => { this.showNotification(`Đã hoàn thành tự động đăng ${this.STATE.totalChapters}/${this.STATE.totalChapters} chương và đã reset số lần đăng`, 'success'); }, 3000); } } setTimeout(() => this.hideLoading(), 2000); }, addNewChapter: function() { if ((this.STATE.chapterNumber + 1) <= MAX_CHAPTER_POST) { this.updateChapNumber(true); const html = this.createChapterHTML(this.STATE.chapterNumber); jQuery('#add-chap').before(html); this.setupCharacterCounter(); } else { this.showNotification(`Chỉ có thể đăng tối đa ${MAX_CHAPTER_POST} chương một lần`, 'warning'); } }, createFormContainer: function() { const container = document.createElement('div'); container.id = 'ttv-chapter-forms'; document.body.appendChild(container); }, resetAutoPost: function() { this.STATE.totalChapters = 0; this.STATE.processedChapters = 0; localStorage.removeItem('TTV_TOTAL_CHAPTERS'); localStorage.removeItem('TTV_POSTED_CHAPTERS'); this.updateStats(); this.showNotification('Đã reset số lần tự động đăng', 'info'); }, toggleAutoMode: function() { const isAutoMode = jQuery('#qpOptionLoop').is(':checked'); if (isAutoMode) { jQuery('#qpButtonAutoPost').show(); jQuery('#qpButtonReset').show(); jQuery('#qpButtonPaste').hide(); jQuery('#qpButtonSubmit').hide(); this.showNotification('Đã bật chế độ tự động', 'info'); } else { jQuery('#qpButtonAutoPost').hide(); jQuery('#qpButtonReset').hide(); jQuery('#qpButtonPaste').show(); jQuery('#qpButtonSubmit').show(); this.showNotification('Đã tắt chế độ tự động', 'info'); } localStorage.setItem('TTV_AUTO_MODE', isAutoMode.toString()); } }; const MAX_CHAPTER_POST = 10; const HEADER_SIGN = "/***"; const FOOTER_SIGN = "***/"; TTVManager.init(); })();