Chapter Downloader

Add a control panel for novel reading on truyen.tangthuvien.net

目前为 2025-04-16 提交的版本。查看 最新版本

// ==UserScript==
// @name         Chapter Downloader
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Add a control panel for novel reading on truyen.tangthuvien.net
// @author       You
// @match        https://tangthuvien.net/doc-truyen/*
// @match        https://tangthuvien.net/doc-truyen/*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Create panel container
    const panel = document.createElement('div');
    panel.id = 'reader-panel';
    panel.style.cssText = `
        position: fixed;
        top: 10%;
        right: 10px;
        width: 300px;
        background-color: #f8f8f8;
        border: 1px solid #ccc;
        border-radius: 5px;
        padding: 10px;
        z-index: 9999;
        box-shadow: 0 0 10px rgba(0,0,0,0.2);
        font-family: Arial, sans-serif;
    `;

    // Create textarea
    const textarea = document.createElement('textarea');
    textarea.id = 'content-textarea';
    textarea.style.cssText = `
        width: 100%;
        height: 300px;
        margin-bottom: 10px;
        padding: 8px;
        border: 1px solid #ddd;
        border-radius: 4px;
        resize: vertical;
    `;

    // Create start button
    const startButton = document.createElement('button');
    startButton.textContent = 'Bắt đầu';
    startButton.style.cssText = `
        width: 100%;
        padding: 8px;
        margin-bottom: 10px;
        background-color: #4CAF50;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
    `;

    // Create container for getTextButton and nextChapterButton (same row)
    const buttonRow1 = document.createElement('div');
    buttonRow1.style.cssText = `
        display: flex;
        justify-content: space-between;
        margin-bottom: 10px;
    `;

    // Create get text button
    const getTextButton = document.createElement('button');
    getTextButton.textContent = 'Lấy Text';
    getTextButton.style.cssText = `
        flex: 1;
        padding: 8px;
        margin-right: 5px;
        background-color: #2196F3;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
    `;

    // Create next chapter button
    const nextChapterButton = document.createElement('button');
    nextChapterButton.textContent = 'Chương Tiếp';
    nextChapterButton.style.cssText = `
        flex: 1;
        padding: 8px;
        margin-left: 5px;
        background-color: #ff9800;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
    `;

    // Create container for clearButton and copyButton (same row)
    const buttonRow2 = document.createElement('div');
    buttonRow2.style.cssText = `
        display: flex;
        justify-content: space-between;
    `;

    // Create clear button
    const clearButton = document.createElement('button');
    clearButton.textContent = 'Xoá';
    clearButton.style.cssText = `
        flex: 1;
        padding: 8px;
        margin-right: 5px;
        background-color: #f44336;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
    `;

    // Create copy button
    const copyButton = document.createElement('button');
    copyButton.textContent = 'Sao Chép';
    copyButton.style.cssText = `
        flex: 1;
        padding: 8px;
        margin-left: 5px;
        background-color: #9c27b0;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
    `;

    // Add buttons to their respective row containers
    buttonRow1.appendChild(getTextButton);
    buttonRow1.appendChild(nextChapterButton);
    buttonRow2.appendChild(clearButton);
    buttonRow2.appendChild(copyButton);

    // Add all elements to panel
    panel.appendChild(textarea);
    panel.appendChild(startButton);
    panel.appendChild(buttonRow1);
    panel.appendChild(buttonRow2);

    // Add panel to body
    document.body.appendChild(panel);

    // Load saved content from localStorage if exists
    const savedContent = localStorage.getItem('chapterDownloaderContent');
    if (savedContent) {
        textarea.value = savedContent;
    }

    // Function to save content to localStorage
    function saveContent() {
        localStorage.setItem('chapterDownloaderContent', textarea.value);
        console.log('Content saved to localStorage');
    }

    // Auto save when textarea value changes
    textarea.addEventListener('input', saveContent);

    // Function to extract and add chapter content
    function extractChapterContent() {
        // Get chapter title
        const titleElement = document.querySelector('h2');
        // Get chapter content 
        const contentElement = document.querySelector('.box-chap');
        
        if (titleElement && contentElement) {
            // Append to existing content rather than replacing
            const title = titleElement.innerText.trim();
            const content = contentElement.innerText.trim();
            
            // Append with proper formatting
            if (textarea.value) {
                textarea.value += '\n\n------------\n\n' + title + '\n\n' + content;
            } else {
                textarea.value = title + '\n\n' + content;
            }
            
            // Save content after adding new chapter
            saveContent();
            return true;
        } else {
            console.log('Could not find title or content elements');
            return false;
        }
    }

    // Function to navigate to next chapter
    function goToNextChapter() {
        // Find the next chapter button with the correct selector
        const nextChapterLink = document.querySelector('.bot-next_chap.bot-control');
        
        if (nextChapterLink) {
            console.log('Found next chapter link, navigating...');
            nextChapterLink.click();
            return true;
        } else {
            console.log('Could not find next chapter link with class ".bot-next_chap.bot-control"');
            // Try alternative selectors if the main one doesn't work
            const alternativeNextLinks = document.querySelectorAll('a[href*="chuong"]');
            for (const link of alternativeNextLinks) {
                if (link.textContent.includes('tiếp') || link.textContent.includes('sau') || link.textContent.includes('next')) {
                    console.log('Found alternative next chapter link, navigating...');
                    link.click();
                    return true;
                }
            }
            return false;
        }
    }

    // Variable to track auto-downloading state
    let isAutoDownloading = false;
    
    // Function to handle automatic downloading
    function startAutoDownloading() {
        if (!isAutoDownloading) return;
        
        console.log('Auto-downloading: Extracting chapter content...');
        if (extractChapterContent()) {
            // After extracting, wait 1 second then navigate to next chapter
            setTimeout(() => {
                if (!isAutoDownloading) return;
                console.log('Auto-downloading: Navigating to next chapter...');
                if (goToNextChapter()) {
                    // After navigation, wait 2 seconds for page to load then extract again
                    setTimeout(() => {
                        if (isAutoDownloading) {
                            startAutoDownloading();
                        }
                    }, 2000); // Wait 2 seconds for page to load before extracting next chapter
                } else {
                    console.log('Auto-downloading: Could not find next chapter, stopping.');
                    isAutoDownloading = false;
                    startButton.textContent = 'Bắt đầu';
                    startButton.style.backgroundColor = '#4CAF50';
                }
            }, 1000); // Wait 1 second before navigating
        } else {
            console.log('Auto-downloading: Could not extract chapter content, stopping.');
            isAutoDownloading = false;
            startButton.textContent = 'Bắt đầu';
            startButton.style.backgroundColor = '#4CAF50';
        }
    }
    
    // Add event listeners
    startButton.addEventListener('click', () => {
        // Toggle auto-downloading state
        isAutoDownloading = !isAutoDownloading;
        
        if (isAutoDownloading) {
            console.log('Start button clicked: Beginning auto-download sequence');
            startButton.textContent = 'Dừng';
            startButton.style.backgroundColor = '#f44336'; // Red color to indicate active
            startAutoDownloading(); // Start the sequence
        } else {
            console.log('Start button clicked: Stopping auto-download sequence');
            startButton.textContent = 'Bắt đầu';
            startButton.style.backgroundColor = '#4CAF50'; // Green color when inactive
        }
    });

    getTextButton.addEventListener('click', () => {
        console.log('Get text button clicked');
        extractChapterContent();
    });

    nextChapterButton.addEventListener('click', () => {
        console.log('Next chapter button clicked');
        goToNextChapter();
    });

    clearButton.addEventListener('click', () => {
        console.log('Clear button clicked');
        textarea.value = '';
        saveContent(); // Save empty content to localStorage
    });

    copyButton.addEventListener('click', () => {
        console.log('Copy button clicked');
        textarea.select();
        document.execCommand('copy');
        
        // Show a temporary copy notification
        const notification = document.createElement('div');
        notification.textContent = 'Đã sao chép!';
        notification.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background-color: rgba(0,0,0,0.8);
            color: white;
            padding: 10px 20px;
            border-radius: 4px;
            z-index: 10000;
        `;
        document.body.appendChild(notification);
        
        // Remove notification after 2 seconds
        setTimeout(() => {
            document.body.removeChild(notification);
        }, 2000);
    });

    // Make panel draggable
    let isDragging = false;
    let offsetX, offsetY;

    panel.addEventListener('mousedown', (e) => {
        if (e.target === panel) {
            isDragging = true;
            offsetX = e.clientX - panel.getBoundingClientRect().left;
            offsetY = e.clientY - panel.getBoundingClientRect().top;
        }
    });

    document.addEventListener('mousemove', (e) => {
        if (isDragging) {
            panel.style.left = (e.clientX - offsetX) + 'px';
            panel.style.top = (e.clientY - offsetY) + 'px';
            panel.style.right = 'auto';
        }
    });

    document.addEventListener('mouseup', () => {
        isDragging = false;
    });

    // Add keyboard shortcuts
    document.addEventListener('keydown', (e) => {
        // Only handle if not typing in a text field
        if (e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'INPUT') {
            // Alt+G: Get text
            if (e.altKey && e.key === 'g') {
                extractChapterContent();
                e.preventDefault();
            }
            // Alt+N: Next chapter
            else if (e.altKey && e.key === 'n') {
                goToNextChapter();
                e.preventDefault();
            }
            // Alt+S: Toggle auto-downloading (same as clicking Start/Stop button)
            else if (e.altKey && e.key === 's') {
                // Simulate clicking the start button
                startButton.click();
                e.preventDefault();
            }
            // Alt+C: Copy text
            else if (e.altKey && e.key === 'c') {
                textarea.select();
                document.execCommand('copy');
                e.preventDefault();
            }
        }
    });

    // Add information about keyboard shortcuts
    const shortcutsInfo = document.createElement('div');
    shortcutsInfo.style.cssText = `
        font-size: 11px;
        color: #666;
        margin-top: 10px;
        padding-top: 5px;
        border-top: 1px solid #ddd;
    `;
    shortcutsInfo.innerHTML = `
        <b>Phím tắt:</b> Alt+G (Lấy text), Alt+N (Chương tiếp), Alt+S (Bắt đầu/Dừng tự động), Alt+C (Sao chép)
    `;
    panel.appendChild(shortcutsInfo);

    console.log('Chapter Downloader script initialized successfully');
})();