TTV Auto Upload

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

当前为 2025-03-08 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         TTV Auto Upload
// @namespace    http://tampermonkey.net/
// @version      1.6
// @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:hover {
            background: #f5f5f5;
            color: #333;
            transform: translateY(-1px);
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        .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+:/;

        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: []};
                }

                // 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 {
                // Nếu chưa có tiêu đề chương, tạo chương mới
                if (!currentChapter.title) {
                    currentChapter.title = "Chương không tiêu đề";
                    currentChapter.name = "Chương không tiêu đề";
                }
                currentChapter.content.push(line);
            }
        }

        // Thêm chương cuối cùng
        if (currentChapter.title && currentChapter.content.length > 0) {
            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';

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