// ==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();
})();