TTV Auto Upload

Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao

Tính đến 08-03-2025. Xem phiên bản mới nhất.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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