您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao
当前为
// ==UserScript== // @name TTV Auto Upload // @namespace http://tampermonkey.net/ // @version 3.5 // @description Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao // @author HA // @match https://tangthuvien.net/dang-chuong/story/* // @grant none // ==/UserScript== (function() { 'use strict'; // CSS cho thông báo và control panel 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; margin-bottom: 20px; transition: all 0.3s ease; } .ttv-control-panel.minimized { width: auto; height: auto; padding: 10px; opacity: 0.8; transform: translateX(calc(100% - 40px)); transition: all 0.3s ease; } .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; line-height: 1.4; resize: vertical; transition: all 0.2s ease; } .ttv-content-editor:focus { border-color: #5bc0de; outline: none; box-shadow: 0 0 8px rgba(91,192,222,0.2); } .ttv-preview { display: none; width: 100%; height: 100px; margin: 6px 0; padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; line-height: 1.4; overflow-y: auto; background: #f9f9f9; transition: all 0.2s ease; } .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; transition: all 0.2s ease; } .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; transition: all 0.2s ease; line-height: 1.4; 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; } .ttv-control-panel.fullscreen { position: fixed; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; border-radius: 0; z-index: 9999; padding: 15px; display: flex; flex-direction: column; } .ttv-control-panel.fullscreen .ttv-content-editor, .ttv-control-panel.fullscreen .ttv-preview { height: calc(100vh - 250px); margin: 15px 0; font-size: 16px; } .ttv-header { margin-bottom: 20px; padding-bottom: 15px; border-bottom: 2px solid #eee; font-weight: bold; font-size: 16px; color: #444; display: flex; justify-content: space-between; align-items: center; } button.btn-warning { background: #f0ad4e; color: white; border: none; padding: 12px; border-radius: 6px; width: 100%; font-size: 14px; transition: all 0.2s ease; } button.btn-warning:hover { background: #ec971f; transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0,0,0,0.1); } button.ttv-minimize { padding: 2px 8px; background: none; border: none; cursor: pointer; font-size: 16px; color: #666; transition: all 0.2s ease; } button.ttv-minimize:hover { color: #333; } `; document.head.appendChild(style); // Tạo div thông báo 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); } // Parse chapters function update function parseChapters(content) { const lines = content.split('\n'); const chapters = []; let currentChapter = { title: '', name: '', content: [] }; const chapterPattern = /^\s*Chương\s+\d+:/; let previousChapterTitle = ''; // Find chapters with clear markers for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (chapterPattern.test(line)) { // Check if it's a duplicate header if (line !== previousChapterTitle) { // Log for debug console.log(`Tìm thấy chương mới: ${line}`); // If there was a previous chapter, save it if (currentChapter.title && currentChapter.content.length > 0) { chapters.push({...currentChapter}); console.log(`Đã lưu chương: ${currentChapter.title}\nTên: ${currentChapter.name}\nSố dòng: ${currentChapter.content.length}`); currentChapter = {title: '', name: '', content: []}; } // Save current chapter title currentChapter.title = line; // Get chapter name after : let name = line.split(':')[1]?.trim() || ''; // Remove punctuation from start and end of chapter name name = name.replace(/^[.,;'"]+|[.,;'"]+$/g, '').trim(); currentChapter.name = name; previousChapterTitle = line; console.log(`Đang xử lý chương mới:\nTiêu đề: ${line}\nTên chương: ${name}`); } else { console.log(`Bỏ qua tiêu đề trùng lặp: ${line}`); } } else { // Save all non-chapter-title content if (line) { // Only add non-empty lines if (currentChapter.title) { currentChapter.content.push(line); } } } } // Add the last chapter if exists if (currentChapter.title && currentChapter.content.length > 0) { chapters.push({...currentChapter}); } // Display chapters and auto fill forms if (chapters.length > 0) { displayChapters(chapters); // Auto fill forms 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); } console.log(`Tổng số chương tìm thấy: ${chapters.length}`); return chapters; } // Thêm function createChapterHTML 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>`; } // Update transferContent function to use createChapterHTML async function transferContent() { try { // Get all chapters 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'); } // Find the form container const form = document.querySelector('form[name="postChapForm"]'); if (!form) { throw new Error('Không tìm thấy form đăng chương'); } // Get and update state values const chap_vol = parseInt(jQuery('.chap_vol').val()) || 1; const chap_vol_name = jQuery('.chap_vol_name').val() || ''; dăngnhanhTTV.STATE.CHAP_STT = parseInt(jQuery('#chap_stt').val()) || 1; dăngnhanhTTV.STATE.CHAP_SERIAL = parseInt(jQuery('#chap_serial').val()) || 1; console.log(`Bắt đầu tạo ${chapterItems.length} form chương`); // Create forms for all chapters for (let i = 0; i < chapterItems.length; i++) { const formIndex = i + 1; console.log(`Đang tạo form cho chương ${formIndex}`); // Create new chapter form HTML const chapterHTML = createChapterHTML(formIndex); const tempDiv = document.createElement('div'); tempDiv.innerHTML = chapterHTML; form.appendChild(tempDiv.firstElementChild); // Get chapter info const chapterItem = chapterItems[i]; const chapterTitle = chapterItem.querySelector('.chapter-title').textContent; const chapterName = chapterItem.querySelector('.chapter-name').textContent.replace('Tên chương: ', ''); const chapterNumber = chapterTitle.match(/Chương\s+(\d+)/)?.[1] || formIndex.toString(); // Fill in the form fields 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}]"]`) }; // Verify all fields exist const missingFields = Object.entries(formFields) .filter(([_, element]) => !element) .map(([fieldName]) => fieldName); if (missingFields.length > 0) { throw new Error(`Thiếu các trường ${missingFields.join(', ')} trong form ${formIndex}`); } try { // Fill form fields formFields.chapterName.value = chapterName; formFields.chapterNumber.value = chapterNumber; formFields.chapterOrder.value = chapterNumber; formFields.volume.value = chap_vol; formFields.volumeName.value = chap_vol_name; formFields.content.value = chapterItem._content || ''; formFields.advertisement.value = ''; // Update state for next chapter dăngnhanhTTV.STATE.CHAP_STT++; dăngnhanhTTV.STATE.CHAP_SERIAL++; console.log(`Đã điền form cho chương ${formIndex}:`, { title: chapterTitle, name: chapterName, number: chapterNumber, contentLength: formFields.content.value.length }); // Trigger input event for content field to update character count const inputEvent = new Event('input', { bubbles: true }); formFields.content.dispatchEvent(inputEvent); } catch (error) { console.error(`Lỗi khi điền nội dung vào form ${formIndex}:`, error); throw new Error(`Không thể điền nội dung vào form ${formIndex}: ${error.message}`); } } showNotification(`Đã tự động điền ${chapterItems.length} chương vào form!`); console.log(`Đã đ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); throw error; } } // Chọn chương để hiển thị function selectChapter(chapter, index) { const contentEditor = document.querySelector('.ttv-content-editor'); contentEditor.value = chapter.title + '\n' + chapter.content.join('\n'); // Cập nhật số từ const wordCountSpan = document.querySelector('.ttv-word-count'); if (wordCountSpan) { wordCountSpan.textContent = updateWordCount(contentEditor.value); } // Highlight selected chapter const chapterItems = document.querySelectorAll('.ttv-chapter-item'); chapterItems.forEach(item => item.classList.remove('selected')); chapterItems[index]?.classList.add('selected'); // Tự động điền form khi chọn chương 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); } } // 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'; // Store chapter content chapterItem._content = chapter.title + '\n' + chapter.content.join('\n'); // Tạo các phần tử con với định dạng riêng 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 = `${chapter.content.length} dòng`; // Thêm các phần tử vào item chapterItem.appendChild(titleDiv); chapterItem.appendChild(nameDiv); chapterItem.appendChild(statsDiv); chapterItem.onclick = () => selectChapter(chapter, index); chapterList.appendChild(chapterItem); // Select first chapter by default 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); // Hiển thị thông báo về số chương tìm thấy showNotification(`Đã tìm thấy ${chapters.length} chương và tự động điền vào form.`); // Tự động chọn chương đầu tiên if (chapters.length > 0) { selectChapter(chapters[0], 0); } } // Đếm số từ và ký tự function updateWordCount(content) { const wordCount = content.trim().split(/\s+/).length; const charCount = content.length; return `${wordCount} từ | ${charCount} ký tự`; } // Chuyển đổi giữa chế độ soạn thảo và 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'; } } // Chuyển đổi chế độ 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'; } // Thêm panel điều khiển function addControlPanel() { // Tạo panel const panel = document.createElement('div'); panel.className = 'ttv-control-panel'; // Thêm header 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'; // Khung soạn thảo nội dung 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...'; // Khung xem trước const preview = document.createElement('div'); preview.className = 'ttv-preview'; // Cập nhật số từ khi nhập nội dung contentEditor.oninput = () => { const wordCountSpan = document.querySelector('.ttv-word-count'); if (wordCountSpan) { wordCountSpan.textContent = updateWordCount(contentEditor.value); } }; // Xử lý khi paste nội dung contentEditor.onpaste = (e) => { // Cho phép paste hoàn tất setTimeout(() => { const content = contentEditor.value; const chapters = parseChapters(content); if (chapters.length > 0) { displayChapters(chapters); // Tự động điền form sau khi hiển thị danh sách chương 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); // Đợi 1s để đảm bảo UI đã được cập nhật hoàn toàn } }, 0); }; // Nút chuyển nội dung 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; // Thêm các phần tử vào panel panel.appendChild(header); buttonGroup.appendChild(contentEditorLabel); buttonGroup.appendChild(contentEditor); buttonGroup.appendChild(preview); buttonGroup.appendChild(transferBtn); panel.appendChild(buttonGroup); document.body.appendChild(panel); // Thêm xử lý sự kiện cho các nút trong toolbar const minimizeBtn = panel.querySelector('.ttv-minimize'); minimizeBtn.onclick = () => { panel.classList.toggle('minimized'); minimizeBtn.innerHTML = panel.classList.contains('minimized') ? '+' : '−'; }; window.togglePreview = togglePreview; window.toggleFullscreen = toggleFullscreen; } // Thêm control panel khi trang đã load window.addEventListener('load', function() { addControlPanel(); }); })();