- // ==UserScript==
- // @name TTV Auto Upload
- // @namespace http://tampermonkey.net/
- // @version 1.7
- // @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';
-
- // Thêm CSS cho thông báo và nút
- 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;
- }
-
- // Điều chỉnh toolbar và các nút điều khiển
- .ttv-toolbar {
- display: flex;
- gap: 6px;
- align-items: center;
- }
-
- .ttv-toolbar button {
- padding: 2px 8px;
- font-size: 11px;
- color: #555;
- background: #f5f5f5;
- border: 1px solid #ddd;
- border-radius: 3px;
- cursor: pointer;
- transition: all 0.2s ease;
- }
-
- .ttv-toolbar button:hover {
- background: #e9e9e9;
- transform: translateY(-1px);
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- }
-
- .ttv-button-group {
- display: flex;
- flex-direction: column;
- gap: 15px;
- }
-
- .ttv-file-label {
- width: 100%;
- padding: 12px;
- background: #5bc0de;
- color: white;
- border-radius: 6px;
- cursor: pointer;
- font-size: 14px;
- text-align: center;
- transition: all 0.2s ease;
- margin: 0;
- }
-
- .ttv-file-label:hover {
- background: #46b8da;
- transform: translateY(-1px);
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- }
-
- .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.auto-generated {
- background: #fafafa;
- border-left: 2px dashed #ddd;
- }
-
- .ttv-chapter-item.auto-generated .chapter-title {
- color: #999;
- }
-
- .ttv-chapter-item.auto-generated .chapter-name {
- color: #aaa;
- font-style: italic;
- }
-
- .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;
- }
- // Tối ưu chế độ toàn màn hình
- .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);
- }
-
- // Phân tích và tách chương từ nội dung
- function parseChapters(content) {
- const lines = content.split('\n');
- const chapters = [];
- let currentChapter = {
- title: '',
- name: '', // Thêm trường name để lưu tên chương
- content: []
- };
-
- const chapterPattern = /^\t*Chương\s+\d+:/;
-
- // Lưu lại tất cả các dòng không phải tiêu đề chương
- let allContent = [];
-
- // Đầu tiên tìm các chương được đánh dấu rõ ràng
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i];
-
- if (chapterPattern.test(line)) {
- // Nếu đã có chương trước đó, lưu lại
- if (currentChapter.title && currentChapter.content.length > 0) {
- chapters.push({...currentChapter});
- currentChapter = {title: '', name: '', content: []};
-
- // Chỉ lấy 10 chương đầu tiên
- if (chapters.length >= 10) {
- break;
- }
- }
-
- // Kiểm tra dòng tiếp theo
- if (i + 1 < lines.length && chapterPattern.test(lines[i + 1])) {
- // Nếu dòng tiếp theo cũng là tiêu đề chương, gộp 2 dòng
- currentChapter.title = line + '\n' + lines[i + 1];
- // Lấy tên chương từ dòng đầu sau dấu :
- let name = line.split(':')[1]?.trim() || '';
- // Xóa dấu ., , hoặc ; ở đầu tên chương nếu có
- name = name.replace(/^[.,;'"]+/, '').trim();
- currentChapter.name = name;
- i++; // Bỏ qua dòng tiếp theo
- } else {
- currentChapter.title = line;
- // Lấy tên chương sau dấu :
- let name = line.split(':')[1]?.trim() || '';
- // Xóa dấu ., , hoặc ; ở đầu tên chương nếu có
- name = name.replace(/^[.,;'"]+/, '').trim();
- currentChapter.name = name;
- }
- } else {
- // Lưu tất cả nội dung không phải tiêu đề chương
- allContent.push(line);
- if (currentChapter.title) {
- currentChapter.content.push(line);
- }
- }
- }
-
- // Thêm chương cuối cùng nếu có và chưa đủ 10 chương
- if (currentChapter.title && currentChapter.content.length > 0 && chapters.length < 10) {
- chapters.push(currentChapter);
- }
-
- return chapters;
- }
-
- // 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');
- }
-
- // 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';
-
- // Đánh dấu chương tự động tạo
- if (chapter.isAutoGenerated) {
- chapterItem.classList.add('auto-generated');
- }
-
- // 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);
- }
-
- // Đọc nội dung file
- function readFileContent(file) {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = (e) => resolve(e.target.result);
- reader.onerror = (e) => reject(new Error('Lỗi đọc file: ' + e.target.error));
- reader.readAsText(file, 'UTF-8');
- });
- }
-
- // Đế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ự`;
- }
-
- // Xử lý khi chọn file
- async function handleFileSelect(event) {
- try {
- const file = event.target.files[0];
- if (!file) return;
-
- // Đọc nội dung file
- const content = await readFileContent(file);
-
- // Phân tích và tách chương
- const chapters = parseChapters(content);
- displayChapters(chapters);
-
- // Điền vào khung soạn thảo nội dung chương đầu tiên nếu có
- if (chapters.length > 0) {
- const contentEditor = document.querySelector('.ttv-content-editor');
- if (contentEditor) {
- const firstChapter = chapters[0];
- contentEditor.value = firstChapter.title + '\n' + firstChapter.content.join('\n');
-
- // Cập nhật số từ
- const wordCountSpan = document.querySelector('.ttv-word-count');
- if (wordCountSpan) {
- wordCountSpan.textContent = updateWordCount(contentEditor.value);
- }
-
- showNotification(`Đã tải ${chapters.length} chương từ file thành công!`);
- }
- } else {
- showNotification('Không tìm thấy chương nào trong file!', true);
- }
- } catch (error) {
- console.error('Lỗi xử lý file:', error);
- showNotification('Có lỗi xảy ra khi đọc file!', true);
- }
- }
-
- // Chuyển nội dung từ khung soạn thảo sang form
- function transferContent() {
- const contentEditor = document.querySelector('.ttv-content-editor');
- const chapterNameInput = document.querySelector('input[name="chap_name[1]"]');
- const contentInput = document.querySelector('textarea[name="introduce[1]"]');
-
- if (contentEditor && contentInput) {
- const content = contentEditor.value;
- const chapters = parseChapters(content);
-
- if (chapters.length > 0) {
- // Điền tên chương vào input tên chương
- if (chapterNameInput && chapters[0].name) {
- chapterNameInput.value = chapters[0].name;
- }
-
- // Điền nội dung vào textarea
- contentInput.value = chapters[0].content.join('\n');
-
- showNotification('Đã chuyển nội dung sang form đăng chương!');
- } else {
- contentInput.value = contentEditor.value;
- showNotification('Đã chuyển toàn bộ nội dung sang form!');
- }
- } else {
- showNotification('Không tìm thấy form đăng chương!', true);
- }
- }
-
- // 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>
- `;
-
- // Container cho các nút
- const buttonGroup = document.createElement('div');
- buttonGroup.className = 'ttv-button-group';
-
- // Input chọn file
- const fileInput = document.createElement('input');
- fileInput.type = 'file';
- fileInput.accept = '.txt';
- fileInput.className = 'ttv-file-input';
- fileInput.id = 'ttv-file-input';
- fileInput.style.display = 'none';
- fileInput.onchange = handleFileSelect;
-
- // Label cho input file
- const fileLabel = document.createElement('label');
- fileLabel.htmlFor = 'ttv-file-input';
- fileLabel.className = 'ttv-file-label';
- fileLabel.innerHTML = '<span>Chọn file txt chứa nội dung</span>';
-
- // 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);
- showNotification(`Đã tìm thấy ${chapters.length} chương!`);
- }
- }, 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(fileInput);
- buttonGroup.appendChild(fileLabel);
- 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();
- });
- })();