您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tự động điền form đăng chương trên tangthuvien.net
当前为
// ==UserScript== // @name TTV Auto Upload // @namespace http://tampermonkey.net/ // @version 4.4 // @description Tự động điền form đăng chương trên tangthuvien.net // @author HA // @match https://tangthuvien.net/dang-chuong/story/* // @grant none // ==/UserScript== (function() { 'use strict'; // CSS tối giản const style = document.createElement('style'); style.textContent = ` .ttv-notification {position:fixed;top:20px;right:20px;padding:10px 20px;background:#4CAF50;color:white;border-radius:4px;z-index:9999;display:none} .ttv-error {background:#f44336} .ttv-control-panel {position:fixed;top:50px;right:20px;background:white;padding:25px;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.15);z-index:9998;width:500px;transition:all 0.3s ease} .ttv-control-panel.minimized {width:auto;height:auto;padding:10px;opacity:0.8;transform:translateX(calc(100% - 40px))} .ttv-control-panel.minimized:hover {opacity:1;transform:translateX(0)} .ttv-control-panel.minimized .ttv-button-group,.ttv-control-panel.minimized .ttv-header {display:none} .ttv-button-group {display:flex;flex-direction:column;gap:15px} .ttv-content-editor {width:100%;height:100px;margin:6px 0;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:13px;resize:vertical} .ttv-preview {display:none;width:100%;height:100px;margin:6px 0;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:13px;background:#f9f9f9} .ttv-heading {font-size:15px;color:#555;margin:12px 0;display:flex;justify-content:space-between;align-items:center} .ttv-word-count {font-size:12px;color:#888;padding:3px 8px;background:#f5f5f5;border-radius:4px} .ttv-chapter-list {width:100%;margin:10px 0;max-height:200px;overflow-y:auto;border:1px solid #eee;border-radius:6px;padding:8px} .ttv-chapter-item {padding:8px;border-bottom:1px solid #eee;cursor:pointer;font-size:12px;color:#666} .ttv-chapter-item .chapter-title {font-weight:bold;margin-bottom:4px} .ttv-chapter-item .chapter-name {color:#888;padding-left:10px;border-left:2px solid #ddd;margin:4px 0} .ttv-chapter-item .chapter-stats {font-size:11px;color:#999} .ttv-chapter-item:last-child {border-bottom:none} .ttv-chapter-item.selected {background:#f0f8ff;border-left:2px solid #5bc0de} button.btn-warning {background:#f0ad4e;color:white;border:none;padding:12px;border-radius:6px;width:100%;font-size:14px} button.btn-warning:hover {background:#ec971f} button.ttv-minimize {padding:2px 8px;background:none;border:none;cursor:pointer;font-size:16px;color:#666} `; document.head.appendChild(style); // State management const dăngnhanhTTV = { STATE: { CHAP_NUMBER: 1, CHAP_STT: 1, CHAP_SERIAL: 1, CHAP_NUMBER_ORIGINAL: 1, CHAP_STT_ORIGINAL: 1, CHAP_SERIAL_ORIGINAL: 1 }, 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); } } }; // UI Elements const notification = document.createElement('div'); notification.className = 'ttv-notification'; document.body.appendChild(notification); // Hiển thị thông báo function showNotification(message, isError = false) { notification.textContent = message; notification.className = 'ttv-notification' + (isError ? ' ttv-error' : ''); notification.style.display = 'block'; setTimeout(() => notification.style.display = 'none', 3000); } // Phân tích chương function parseChapters(content) { const lines = content.split('\n'); const chapters = []; let currentChapter = {title: '', name: '', content: []}; const chapterPattern = /^\s*Chương\s+\d+:/; // Biến để kiểm soát tiêu đề trùng lặp let chapterTitles = new Set(); let chapterNumbers = new Map(); // Lưu số chương đã gặp let duplicateCount = 0; // Mảng lưu nội dung của các dòng trống gần nhất let emptyLineBuffer = []; // Biến để theo dõi xem ta có đang ở trong một khối tiêu đề lặp không let inDuplicatedTitleBlock = false; // Theo dõi tiêu đề chương trước đó let previousChapterLine = ''; // Khoảng cách dòng tối thiểu giữa các tiêu đề hợp lệ const MIN_LINES_BETWEEN_CHAPTERS = 5; // Số dòng đã qua kể từ tiêu đề cuối let linesSinceLastTitle = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const trimmedLine = line.trim(); linesSinceLastTitle++; // Xử lý dòng trống if (trimmedLine === '') { emptyLineBuffer.push(line); continue; } if (chapterPattern.test(trimmedLine)) { // Trích xuất số chương từ tiêu đề const chapterMatch = trimmedLine.match(/Chương\s+(\d+):/); const chapterNum = chapterMatch ? parseInt(chapterMatch[1]) : 0; const chapterName = trimmedLine.split(':')[1]?.trim() || ''; // Kiểm tra các điều kiện để xác định tiêu đề chương hợp lệ const isPotentialChapter = linesSinceLastTitle > MIN_LINES_BETWEEN_CHAPTERS || chapters.length === 0; const isFullTitle = chapterName.length > 2; // Tiêu đề đầy đủ phải có ít nhất 3 ký tự sau dấu : const isDuplicateOfPrevious = trimmedLine === previousChapterLine; if (isPotentialChapter && isFullTitle && !isDuplicateOfPrevious) { // Lưu chương hiện tại nếu có if (currentChapter.title && currentChapter.content.length > 0) { chapters.push({...currentChapter}); currentChapter = {title: '', name: '', content: []}; } // Khởi tạo chương mới currentChapter.title = trimmedLine; let name = chapterName; name = name.replace(/^[.,;'"]+|[.,;'"]+$/g, '').trim(); currentChapter.name = name; // Đánh dấu đã gặp tiêu đề và số chương này chapterTitles.add(trimmedLine); chapterNumbers.set(chapterNum, true); // Đặt lại các biến theo dõi inDuplicatedTitleBlock = false; linesSinceLastTitle = 0; previousChapterLine = trimmedLine; emptyLineBuffer = []; } else { // Đây là một tiêu đề bị lặp, xử lý như nội dung chương thông thường if (currentChapter.title) { // Thêm các dòng trống đã lưu trữ currentChapter.content.push(...emptyLineBuffer); emptyLineBuffer = []; // Thêm dòng hiện tại currentChapter.content.push(line); } inDuplicatedTitleBlock = true; duplicateCount++; } } else if (currentChapter.title) { // Giữ nguyên định dạng gốc của dòng (không trim), // bao gồm cả khoảng trắng, xuống dòng và định dạng đặc biệt currentChapter.content.push(line); } } if (currentChapter.title && currentChapter.content.length > 0) { chapters.push({...currentChapter}); } if (chapters.length > 0) { displayChapters(chapters); if (duplicateCount > 0) { showNotification(`Đã tìm thấy ${chapters.length} chương và bỏ qua ${duplicateCount} tiêu đề lặp lại.`); } else { showNotification(`Đã tìm thấy ${chapters.length} chương và tự động điền vào form.`); } setTimeout(() => { try { transferContent(); } catch (error) { console.error('Lỗi khi tự động điền form:', error); showNotification('Có lỗi xảy ra khi tự động điền form!', true); } }, 1000); } return chapters; } // Tạo HTML cho form chương function createChapterHTML(chapNum) { const chap_vol = parseInt(jQuery('.chap_vol').val()) || 1; const chap_vol_name = jQuery('.chap_vol_name').val() || ''; return ` <div data-gen="MK_GEN" id="COUNT_CHAP_${chapNum}_MK"> <div class="col-xs-12 form-group"></div> <div class="form-group"> <label class="col-sm-2" for="chap_stt">STT</label> <div class="col-sm-8"> <input class="form-control" required name="chap_stt[${chapNum}]" value="${dăngnhanhTTV.STATE.CHAP_STT}" placeholder="Số thứ tự của chương" type="text"/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_number">Chương thứ..</label> <div class="col-sm-8"> <input value="${dăngnhanhTTV.STATE.CHAP_SERIAL}" required class="form-control" name="chap_number[${chapNum}]" placeholder="Chương thứ.. (1,2,3..)" type="text"/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_name">Quyển số</label> <div class="col-sm-8"> <input class="form-control" name="vol[${chapNum}]" placeholder="Quyển số" type="number" value="${chap_vol}" required/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_name">Tên quyển</label> <div class="col-sm-8"> <input class="form-control chap_vol_name" name="vol_name[${chapNum}]" placeholder="Tên quyển" type="text" value="${chap_vol_name}" /> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_name">Tên chương</label> <div class="col-sm-8"> <input required class="form-control" name="chap_name[${chapNum}]" placeholder="Tên chương" type="text"/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="introduce">Nội dung</label> <div class="col-sm-8"> <textarea maxlength="75000" style="color:#000;font-weight: 400;" required class="form-control" name="introduce[${chapNum}]" rows="20" placeholder="Nội dung" type="text"></textarea> <div class="chapter-character-count"></div> </div> </div> <div class="form-group"> <label class="col-sm-2" for="adv">Quảng cáo</label> <div class="col-sm-8"> <textarea maxlength="1000" class="form-control" name="adv[${chapNum}]" placeholder="Quảng cáo" type="text"></textarea> </div> </div> </div>`; } // Chuyển nội dung vào form async function transferContent() { try { const chapterItems = document.querySelectorAll('.ttv-chapter-item'); if (!chapterItems.length) { throw new Error('Không tìm thấy chương nào để điền vào form'); } const form = document.querySelector('form[name="postChapForm"]'); if (!form) { throw new Error('Không tìm thấy form đăng chương'); } let chap_vol = parseInt(jQuery('.chap_vol').val()) || 1; let chap_vol_name = jQuery('.chap_vol_name').val() || ''; let maxChapStt = 0; let maxChapSerial = 0; const existingForms = form.querySelectorAll('[id^="COUNT_CHAP_"]'); existingForms.forEach(formElem => { const formIdMatch = formElem.id.match(/COUNT_CHAP_(\d+)_MK/); if (formIdMatch && formIdMatch[1]) { const formIndex = parseInt(formIdMatch[1]); const sttInput = formElem.querySelector(`input[name="chap_stt[${formIndex}]"]`); if (sttInput && sttInput.value && !isNaN(parseInt(sttInput.value))) { const sttVal = parseInt(sttInput.value); if (sttVal > maxChapStt) { maxChapStt = sttVal; } } const serialInput = formElem.querySelector(`input[name="chap_number[${formIndex}]"]`); if (serialInput && serialInput.value && !isNaN(parseInt(serialInput.value))) { const serialVal = parseInt(serialInput.value); if (serialVal > maxChapSerial) { maxChapSerial = serialVal; } } } }); if (maxChapStt > 0) { dăngnhanhTTV.STATE.CHAP_STT = maxChapStt; } if (maxChapSerial > 0) { dăngnhanhTTV.STATE.CHAP_SERIAL = maxChapSerial; } const existingFormCount = existingForms.length; for (let i = 0; i < chapterItems.length; i++) { const formIndex = existingFormCount + i + 1; dăngnhanhTTV.STATE.CHAP_STT++; dăngnhanhTTV.STATE.CHAP_SERIAL++; const chapterHTML = createChapterHTML(formIndex); const tempDiv = document.createElement('div'); tempDiv.innerHTML = chapterHTML; const newFormElement = tempDiv.firstElementChild; if (!newFormElement) { throw new Error(`Không thể tạo element form cho chương ${formIndex}`); } form.appendChild(newFormElement); const chapterItem = chapterItems[i]; const titleElement = chapterItem.querySelector('.chapter-title'); const nameElement = chapterItem.querySelector('.chapter-name'); if (!titleElement || !nameElement) { throw new Error(`Thiếu thông tin tiêu đề hoặc tên cho chương ${formIndex}`); } const chapterTitle = titleElement.textContent; const chapterName = nameElement.textContent.replace('Tên chương: ', ''); const formFields = { chapterName: form.querySelector(`input[name="chap_name[${formIndex}]"]`), content: form.querySelector(`textarea[name="introduce[${formIndex}]"]`), chapterNumber: form.querySelector(`input[name="chap_number[${formIndex}]"]`), chapterOrder: form.querySelector(`input[name="chap_stt[${formIndex}]"]`), volume: form.querySelector(`input[name="vol[${formIndex}]"]`), volumeName: form.querySelector(`input[name="vol_name[${formIndex}]"]`), advertisement: form.querySelector(`textarea[name="adv[${formIndex}]"]`) }; formFields.chapterName.value = chapterName; formFields.chapterNumber.value = dăngnhanhTTV.STATE.CHAP_SERIAL.toString(); formFields.chapterOrder.value = dăngnhanhTTV.STATE.CHAP_STT.toString(); formFields.volume.value = chap_vol; formFields.volumeName.value = chap_vol_name; if (chapterItem._content) { // Chỉ lấy phần nội dung, không lấy phần tiêu đề // Đảm bảo giữ nguyên định dạng gốc của nội dung, không trim formFields.content.value = chapterItem._content; } else { formFields.content.value = ''; } formFields.advertisement.value = ''; const inputEvent = new Event('input', { bubbles: true }); formFields.content.dispatchEvent(inputEvent); } showNotification(`Đã tự động điền ${chapterItems.length} chương vào form!`); } catch (error) { console.error('Lỗi khi điền form:', error); showNotification('Có lỗi xảy ra khi điền form: ' + error.message, true); } } // Hiển thị danh sách chương function displayChapters(chapters) { const chapterList = document.createElement('div'); chapterList.className = 'ttv-chapter-list'; chapters.forEach((chapter, index) => { const chapterItem = document.createElement('div'); chapterItem.className = 'ttv-chapter-item'; // Chỉ lấy phần nội dung dưới tiêu đề, không bao gồm tiêu đề chapterItem._content = chapter.content.join('\n'); // Trích xuất số chương để hiển thị const chapterMatch = chapter.title.match(/Chương\s+(\d+):/); const chapterNum = chapterMatch ? chapterMatch[1] : '?'; const titleDiv = document.createElement('div'); titleDiv.className = 'chapter-title'; titleDiv.textContent = chapter.title; const nameDiv = document.createElement('div'); nameDiv.className = 'chapter-name'; nameDiv.textContent = `Tên chương: ${chapter.name}`; const statsDiv = document.createElement('div'); statsDiv.className = 'chapter-stats'; statsDiv.textContent = `Chương ${chapterNum} | ${chapter.content.length} dòng`; chapterItem.appendChild(titleDiv); chapterItem.appendChild(nameDiv); chapterItem.appendChild(statsDiv); chapterItem.onclick = () => selectChapter(chapter, index); chapterList.appendChild(chapterItem); if (index === 0) { chapterItem.classList.add('selected'); } }); const existingList = document.querySelector('.ttv-chapter-list'); if (existingList) { existingList.remove(); } const contentEditor = document.querySelector('.ttv-content-editor'); contentEditor.parentNode.insertBefore(chapterList, contentEditor); if (chapters.length > 0) { selectChapter(chapters[0], 0); } } // Chọn chương function selectChapter(chapter, index) { const contentEditor = document.querySelector('.ttv-content-editor'); // Chỉ lấy nội dung, không lấy tiêu đề để tránh lặp lại contentEditor.value = chapter.content.join('\n'); const wordCountSpan = document.querySelector('.ttv-word-count'); if (wordCountSpan) { wordCountSpan.textContent = updateWordCount(contentEditor.value); } const chapterItems = document.querySelectorAll('.ttv-chapter-item'); chapterItems.forEach(item => item.classList.remove('selected')); chapterItems[index]?.classList.add('selected'); try { transferContent(); } catch (error) { console.error('Lỗi khi tự động điền form:', error); showNotification('Có lỗi xảy ra khi tự động điền form!', true); } } // Tính số từ function updateWordCount(content) { const wordCount = content.trim().split(/\s+/).length; const charCount = content.length; return `${wordCount} từ | ${charCount} ký tự`; } // Xem trước function togglePreview() { const contentEditor = document.querySelector('.ttv-content-editor'); const preview = document.querySelector('.ttv-preview'); const previewBtn = document.querySelector('.ttv-preview-btn'); if (contentEditor.style.display !== 'none') { contentEditor.style.display = 'none'; preview.style.display = 'block'; preview.innerHTML = contentEditor.value.replace(/\n/g, '<br>'); previewBtn.textContent = 'Soạn thảo'; } else { contentEditor.style.display = 'block'; preview.style.display = 'none'; previewBtn.textContent = 'Xem trước'; } } // Toàn màn hình function toggleFullscreen() { const panel = document.querySelector('.ttv-control-panel'); const fullscreenBtn = document.querySelector('.ttv-fullscreen-btn'); panel.classList.toggle('fullscreen'); fullscreenBtn.textContent = panel.classList.contains('fullscreen') ? 'Thu nhỏ' : 'Toàn màn hình'; } // Tạo panel điều khiển function addControlPanel() { const panel = document.createElement('div'); panel.className = 'ttv-control-panel'; const header = document.createElement('div'); header.className = 'ttv-header'; header.innerHTML = ` <div>Soạn Thảo Nội Dung</div> <div class="ttv-toolbar"> <button class="ttv-preview-btn" onclick="togglePreview()">Xem trước</button> <button class="ttv-fullscreen-btn" onclick="toggleFullscreen()">Toàn màn hình</button> <button class="ttv-minimize">−</button> </div> `; const buttonGroup = document.createElement('div'); buttonGroup.className = 'ttv-button-group'; const contentEditorLabel = document.createElement('div'); contentEditorLabel.className = 'ttv-heading'; contentEditorLabel.innerHTML = ` Nội dung chương: <span class="ttv-word-count">0 từ | 0 ký tự</span> `; const contentEditor = document.createElement('textarea'); contentEditor.className = 'ttv-content-editor'; contentEditor.placeholder = 'Nhập hoặc dán nội dung chương vào đây...'; const preview = document.createElement('div'); preview.className = 'ttv-preview'; contentEditor.oninput = () => { const wordCountSpan = document.querySelector('.ttv-word-count'); if (wordCountSpan) { wordCountSpan.textContent = updateWordCount(contentEditor.value); } }; contentEditor.onpaste = (e) => { setTimeout(() => { const content = contentEditor.value; const chapters = parseChapters(content); if (chapters.length > 0) { displayChapters(chapters); setTimeout(() => { try { transferContent(); } catch (error) { console.error('Lỗi khi tự động điền form:', error); showNotification('Có lỗi xảy ra khi tự động điền form!', true); } }, 1000); } }, 0); }; const transferBtn = document.createElement('button'); transferBtn.type = 'button'; transferBtn.className = 'btn btn-warning'; transferBtn.innerHTML = '<span>Chuyển nội dung sang form</span>'; transferBtn.onclick = transferContent; panel.appendChild(header); buttonGroup.appendChild(contentEditorLabel); buttonGroup.appendChild(contentEditor); buttonGroup.appendChild(preview); buttonGroup.appendChild(transferBtn); panel.appendChild(buttonGroup); document.body.appendChild(panel); const minimizeBtn = panel.querySelector('.ttv-minimize'); minimizeBtn.onclick = () => { panel.classList.toggle('minimized'); minimizeBtn.innerHTML = panel.classList.contains('minimized') ? '+' : '−'; }; window.togglePreview = togglePreview; window.toggleFullscreen = toggleFullscreen; } window.addEventListener('load', function() { dăngnhanhTTV.initializeChapterValues(); addControlPanel(); }); })();