您需要先安装一个扩展,例如 篡改猴、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 0.5 // @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/* // @grant none // ==/UserScript== (function() { 'use strict'; const HEADER_SIGN = ""; const FOOTER_SIGN = ""; const MAX_CHAPTER_POST = 10; 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; } #ttv-panel textarea { width: 100%; height: 150px; margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; resize: vertical; } #ttv-panel .btn-group { display: flex; gap: 10px; margin-top: 15px; } #ttv-panel button { flex: 1; padding: 10px; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; transition: all 0.2s; } #ttv-panel button:hover { opacity: 0.9; } #ttv-panel .btn-auto { background: #4CAF50; color: white; } #ttv-panel .btn-manual { background: #2196F3; color: white; } .chapter-character-count { text-align: right; font-size: 12px; margin-top: 5px; color: #666; } textarea[name^="introduce"].short-chapter { border: 2px solid #ff0000 !important; background-color: rgba(255,0,0,0.1) !important; animation: shortChapterBlink 1s infinite; } @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); } } `; document.head.appendChild(style); const TTVManager = { STATE: { isAutoMode: false, chapterNumber: 1, chapterSTT: 1, chapterSerial: 1 }, init: function() { this.createInterface(); this.setupEventListeners(); this.setupCharacterCounter(); console.log('[TTV] Script khởi động thành công'); }, createInterface: function() { const panel = document.createElement('div'); panel.id = 'ttv-panel'; panel.innerHTML = ` <h3 style="margin: 0 0 15px; color: #333;">📝 ĐĂNG CHƯƠNG</h3> <textarea id="ttv-content" placeholder="Dán nội dung vào đây để tách chương..."></textarea> <div class="btn-group"> <button class="btn-auto" id="ttv-auto">🔄 Đăng tự động</button> <button class="btn-manual" id="ttv-manual">📝 Đăng nhanh</button> </div> <div id="ttv-notification" style="margin-top: 10px;"></div> `; document.body.appendChild(panel); }, setupEventListeners: function() { const content = document.getElementById('ttv-content'); const autoBtn = document.getElementById('ttv-auto'); const manualBtn = document.getElementById('ttv-manual'); // Xử lý paste content.addEventListener('paste', (e) => { e.preventDefault(); const text = e.clipboardData.getData('text'); content.value = text; this.processContent(text, false); }); // Nút đăng tự động autoBtn.addEventListener('click', () => { this.STATE.isAutoMode = true; navigator.clipboard.readText() .then(text => { content.value = text; this.processContent(text, true); }) .catch(() => this.showNotification('Không thể đọc clipboard', 'error')); }); // Nút đăng nhanh manualBtn.addEventListener('click', () => { this.STATE.isAutoMode = false; this.processContent(content.value, false); }); }, setupCharacterCounter: function() { document.addEventListener('input', (e) => { if (e.target.matches('textarea[name^="introduce"]')) { const text = e.target.value; const charCount = text.length; let counter = e.target.nextElementSibling; if (!counter || !counter.classList.contains('chapter-character-count')) { counter = document.createElement('div'); counter.className = 'chapter-character-count'; e.target.parentNode.insertBefore(counter, e.target.nextSibling); } if (charCount < 3000) { e.target.classList.add('short-chapter'); counter.innerHTML = `<span style="color: #f44336;">${charCount.toLocaleString()}/40.000 ký tự</span>`; } else { e.target.classList.remove('short-chapter'); counter.innerHTML = `<span style="color: ${charCount > 40000 ? '#ff9800' : '#4caf50'}">${charCount.toLocaleString()}/40.000 ký tự</span>`; } } }); }, processContent: function(text, autoPost = false) { if (!text) { this.showNotification('Vui lòng nhập nội dung', 'error'); return; } const chapters = this.splitChapters(text); if (chapters.length === 0) { this.showNotification('Không tìm thấy chương nào', 'error'); return; } // Lấy 10 chương đầu const chaptersToFill = chapters.slice(0, MAX_CHAPTER_POST); const remainingChapters = chapters.slice(MAX_CHAPTER_POST); // Điền form this.fillChaptersToForm(chaptersToFill); // Copy chương còn lại vào clipboard if (remainingChapters.length > 0) { this.copyRemainingChapters(remainingChapters); } // Tự động đăng nếu ở chế độ tự động if (autoPost) { setTimeout(() => this.submitChapters(), 2000); } }, splitChapters: function(text) { const chapters = []; const lines = text.split('\n'); let currentChapter = []; let lastTitle = null; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const isChapterTitle = /^\t[Cc]hương\s*\d+\s*:/.test(line) || /^\s{4,}[Cc]hương\s*\d+\s*:/.test(line); if (isChapterTitle) { if (currentChapter.length > 0) { chapters.push(currentChapter.join('\n')); currentChapter = [line]; lastTitle = line; } else { currentChapter = [line]; lastTitle = line; } } else if (currentChapter.length > 0) { currentChapter.push(line); } } if (currentChapter.length > 0) { chapters.push(currentChapter.join('\n')); } return chapters; }, fillChaptersToForm: function(chapters) { // Thêm form cho đủ số chương while (document.querySelectorAll('input[name^="chap_name"]').length < chapters.length) { this.addNewChapterForm(); } const titles = document.querySelectorAll('input[name^="chap_name"]'); const contents = document.querySelectorAll('textarea[name^="introduce"]'); const advs = document.querySelectorAll('textarea[name^="adv"]'); chapters.forEach((chapter, index) => { if (index >= titles.length) return; const lines = chapter.split('\n'); const title = lines.shift().trim(); let chapterName = title.includes(':') ? title.split(':')[1].trim() : title; chapterName = chapterName || 'Vô đề'; titles[index].value = chapterName; contents[index].value = HEADER_SIGN + "\n" + lines.join('\n') + "\n" + FOOTER_SIGN; if (advs[index]) advs[index].value = ''; // Trigger character counter const event = new Event('input', { bubbles: true }); contents[index].dispatchEvent(event); }); this.showNotification(`Đã điền ${chapters.length} chương vào form`, 'success'); }, copyRemainingChapters: function(chapters) { try { const content = chapters.map(chapter => { const lines = chapter.trim().split('\n'); if (lines[0] && !lines[0].startsWith('\t')) { lines[0] = '\t' + lines[0]; } return lines.join('\n'); }).join('\n\n'); navigator.clipboard.writeText(content).then(() => { this.showNotification(`Đã copy ${chapters.length} chương còn lại vào clipboard`, 'info'); }); } catch (error) { console.error('[TTV] Lỗi copy clipboard:', error); this.showNotification('Không thể copy vào clipboard', 'error'); } }, submitChapters: function() { const submitBtn = document.querySelector('button[type="submit"]'); if (!submitBtn) { this.showNotification('Không tìm thấy nút đăng chương', 'error'); return; } // Kiểm tra độ dài các chương const shortChapters = Array.from(document.querySelectorAll('textarea[name^="introduce"]')) .filter(textarea => textarea.value.length < 3000); if (shortChapters.length > 0) { this.showNotification(`Có ${shortChapters.length} chương chưa đủ 3000 ký tự`, 'error'); return; } // Đếm số chương const chapterCount = document.querySelectorAll('textarea[name^="introduce"]').length; if (chapterCount === 0) { this.showNotification('Không có chương nào để đăng', 'error'); return; } // Đăng chương submitBtn.click(); this.showNotification('Đang đăng chương...', 'info'); // Kiểm tra sau khi đăng setTimeout(() => { const remainingChapters = document.querySelectorAll('textarea[name^="introduce"]').length; console.log('[TTV] Số chương còn lại:', remainingChapters); if (remainingChapters < 10 && this.STATE.isAutoMode) { this.STATE.isAutoMode = false; this.showNotification(`Còn ${remainingChapters} chương, dưới 10 chương nên đã tắt chế độ tự động`, 'warning'); } else if (this.STATE.isAutoMode) { setTimeout(() => window.location.reload(), 2000); } }, 3000); }, addNewChapterForm: function() { this.STATE.chapterNumber++; this.STATE.chapterSTT++; this.STATE.chapterSerial++; const formHtml = ` <div data-gen="MK_GEN" id="COUNT_CHAP_${this.STATE.chapterNumber}_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[${this.STATE.chapterNumber}]" value="${this.STATE.chapterSTT}" 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="${this.STATE.chapterSerial}" required class="form-control" name="chap_number[${this.STATE.chapterNumber}]" 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[${this.STATE.chapterNumber}]" value="1" placeholder="Quyển số" type="number" 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[${this.STATE.chapterNumber}]" placeholder="Tên quyển" type="text" /> </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[${this.STATE.chapterNumber}]" 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[${this.STATE.chapterNumber}]" 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[${this.STATE.chapterNumber}]" placeholder="Quảng cáo" type="text"></textarea> </div> </div> </div>`; document.querySelector('#div_chapt_upload').insertAdjacentHTML('beforeend', formHtml); }, showNotification: function(message, type = 'info') { const notification = document.getElementById('ttv-notification'); notification.innerHTML = ` <div style=" padding: 10px 15px; border-radius: 6px; background-color: ${type === 'error' ? '#ffebee' : type === 'success' ? '#e8f5e9' : type === 'warning' ? '#fff3e0' : '#e3f2fd'}; color: ${type === 'error' ? '#c62828' : type === 'success' ? '#1b5e20' : type === 'warning' ? '#e65100' : '#0d47a1'}; border: 1px solid ${type === 'error' ? '#ef9a9a' : type === 'success' ? '#a5d6a7' : type === 'warning' ? '#ffcc80' : '#90caf9'}; "> ${message} </div> `; console.log(`[TTV] ${type.toUpperCase()}: ${message}`); } }; // Khởi động script TTVManager.init(); })();