Fiction Novel Download Buttons

Adds download buttons for TXT, PDF, EPUB on supported fanfiction sites

// ==UserScript==
// @name         Fiction Novel Download Buttons
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Adds download buttons for TXT, PDF, EPUB on supported fanfiction sites
// @author       AI
// @match        *://*.fanfiction.net/*
// @match        *://archiveofourown.org/works/*
// @match        *://literotica.com/*
// @match        *://asianfanfics.com/*
// @match        *://wattpad.com/*
// @match        *://dreame.com/*
// @match        *://inkitt.com/*
// @match        *://getinkspired.com/*
// @match        *://webnovel.com/*
// @match        *://libri7.com/*
// @match        *://fictionpress.com/*
// @match        *://starslibrary.net/*
// @match        *://fimfiction.net/*
// @grant        GM_download
// @grant        GM_addStyle
// @license      MIT 
// ==/UserScript==

(function() {
    'use strict';

    // Utility function to create buttons
    function createButton(text, onClick) {
        const btn = document.createElement('button');
        btn.innerText = text;
        btn.style.margin = '0 5px';
        btn.style.padding = '5px 10px';
        btn.style.cursor = 'pointer';
        btn.onclick = onClick;
        return btn;
    }

    // Load jsPDF dynamically
    function loadJsPDF(callback) {
        if (window.jspdf) {
            callback();
            return;
        }
        const script = document.createElement('script');
        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
        script.onload = callback;
        document.head.appendChild(script);
    }

    // Helper for waiting for element
    function waitForElement(selector, callback, timeout = 15000) {
        const startTime = Date.now();
        const observer = new MutationObserver(() => {
            const el = document.querySelector(selector);
            if (el) {
                observer.disconnect();
                callback(el);
            } else if (Date.now() - startTime > timeout) {
                observer.disconnect();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // Download functions
    function downloadAsTxt(content, filename) {
        if (!content) {
            alert('No content to download.');
            return;
        }
        alert('Preparing TXT download...');
        const blob = new Blob([content], {type: 'text/plain'});
        GM_download({url: URL.createObjectURL(blob), name: filename, saveAs: true});
        alert('Download started: ' + filename);
    }

    function downloadAsPdf(content, filename) {
        alert('Generating PDF...');
        loadJsPDF(() => {
            const { jsPDF } = window.jspdf;
            const doc = new jsPDF();
            const lines = doc.splitTextToSize(content, 180);
            doc.text(lines, 10, 10);
            const pdfBlob = doc.output('blob');
            GM_download({url: URL.createObjectURL(pdfBlob), name: filename, saveAs: true});
            alert('PDF download started: ' + filename);
        });
    }

    function generateUUID() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
            const r = Math.random() * 16 | 0;
            const v = c === 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

    // Initialize site-specific UI
    function init() {
        const hostname = window.location.hostname;
        if (hostname.includes('fanfiction.net')) {
            injectFanFictionNet();
        } else if (hostname.includes('archiveofourown.org')) {
            injectAO3();
        } else if (hostname.includes('literotica.com')) {
            injectLiterotica();
        } else if (hostname.includes('asianfanfics.com')) {
            injectAsianFanfics();
        } else if (hostname.includes('wattpad.com')) {
            injectWattpad();
        } else if (hostname.includes('dreame.com')) {
            injectDreame();
        } else if (hostname.includes('inkitt.com')) {
            injectInkitt();
        } else if (hostname.includes('getinkspired.com')) {
            injectInkspired();
        } else if (hostname.includes('webnovel.com')) {
            injectWebNovel();
        } else if (hostname.includes('libri7.com')) {
            injectLibri7();
        } else if (hostname.includes('fictionpress.com')) {
            injectFictionPress();
        } else if (hostname.includes('starslibrary.net')) {
            injectStarsLibrary();
        } else if (hostname.includes('fimfiction.net')) {
            injectFimFiction();
        }
    }

    // --------- Site-specific functions ---------

    function injectFanFictionNet() {
        waitForElement('.story-info-right', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getFanFictionContent();
                    downloadAsTxt(content, 'FanFiction.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getFanFictionContent();
                    downloadAsPdf(content, 'FanFiction.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                container.appendChild(dlDiv);
            }
        });
    }

    function getFanFictionContent() {
        const storyDiv = document.querySelector('.storytext');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectAO3() {
        waitForElement('div#chapter-inner, div#workskin', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getAO3Content();
                    downloadAsTxt(content, 'AO3_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getAO3Content();
                    downloadAsPdf(content, 'AO3_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                container.appendChild(dlDiv);
            }
        });
    }

    function getAO3Content() {
        const chapterDiv = document.querySelector('div#chapter-inner, div#workskin');
        if (!chapterDiv) {
            alert('Story content not found.');
            return '';
        }
        return chapterDiv.innerText;
    }

    function injectLiterotica() {
        waitForElement('.storytext, .story', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getLiteroticaContent();
                    downloadAsTxt(content, 'Literotica_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getLiteroticaContent();
                    downloadAsPdf(content, 'Literotica_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getLiteroticaContent() {
        const storyDiv = document.querySelector('.storytext, .story');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectAsianFanfics() {
        waitForElement('.story-content', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getAsianFanficsContent();
                    downloadAsTxt(content, 'AsianFanfics_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getAsianFanficsContent();
                    downloadAsPdf(content, 'AsianFanfics_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getAsianFanficsContent() {
        const storyDiv = document.querySelector('.story-content');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectWattpad() {
        waitForElement('div#story, div.p-story__content', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getWattpadContent();
                    downloadAsTxt(content, 'Wattpad_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getWattpadContent();
                    downloadAsPdf(content, 'Wattpad_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getWattpadContent() {
        const storyDiv = document.querySelector('div#story, div.p-story__content');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectDreame() {
        waitForElement('.reading-content', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getDreameContent();
                    downloadAsTxt(content, 'Dreame_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getDreameContent();
                    downloadAsPdf(content, 'Dreame_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getDreameContent() {
        const storyDiv = document.querySelector('.reading-content');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectInkitt() {
        waitForElement('.story-body', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getInkittContent();
                    downloadAsTxt(content, 'Inkitt_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getInkittContent();
                    downloadAsPdf(content, 'Inkitt_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getInkittContent() {
        const storyDiv = document.querySelector('.story-body');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectInkspired() {
        waitForElement('.story-content, .story-body', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getInkspiredContent();
                    downloadAsTxt(content, 'Inkspired_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getInkspiredContent();
                    downloadAsPdf(content, 'Inkspired_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getInkspiredContent() {
        const storyDiv = document.querySelector('.story-content, .story-body');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectWebNovel() {
        waitForElement('.chapter-inner, .reading-content', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getWebNovelContent();
                    downloadAsTxt(content, 'Webnovel_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getWebNovelContent();
                    downloadAsPdf(content, 'Webnovel_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getWebNovelContent() {
        const storyDiv = document.querySelector('.chapter-inner, .reading-content');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectLibri7() {
        waitForElement('.storytext', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getLibri7Content();
                    downloadAsTxt(content, 'Libri7_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getLibri7Content();
                    downloadAsPdf(content, 'Libri7_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getLibri7Content() {
        const storyDiv = document.querySelector('.storytext');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectFictionPress() {
        waitForElement('.storytext, .story-body', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getFictionPressContent();
                    downloadAsTxt(content, 'FictionPress_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getFictionPressContent();
                    downloadAsPdf(content, 'FictionPress_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getFictionPressContent() {
        const storyDiv = document.querySelector('.storytext, .story-body');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectStarsLibrary() {
        waitForElement('.story-text', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getStarsLibraryContent();
                    downloadAsTxt(content, 'StarsLibrary_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getStarsLibraryContent();
                    downloadAsPdf(content, 'StarsLibrary_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getStarsLibraryContent() {
        const storyDiv = document.querySelector('.story-text');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    function injectFimFiction() {
        waitForElement('#story', (container) => {
            if (!document.getElementById('ficlab-download-container')) {
                const dlDiv = document.createElement('div');
                dlDiv.id = 'ficlab-download-container';
                dlDiv.style.marginTop = '10px';

                const txtBtn = createButton('Download TXT', () => {
                    const content = getFimFictionContent();
                    downloadAsTxt(content, 'FimFiction_Story.txt');
                });
                const pdfBtn = createButton('Download PDF', () => {
                    const content = getFimFictionContent();
                    downloadAsPdf(content, 'FimFiction_Story.pdf');
                });
                const epubBtn = createButton('Download EPUB', () => {
                    alert('EPUB export not implemented yet.');
                });

                dlDiv.appendChild(txtBtn);
                dlDiv.appendChild(pdfBtn);
                dlDiv.appendChild(epubBtn);
                document.body.appendChild(dlDiv);
            }
        });
    }

    function getFimFictionContent() {
        const storyDiv = document.querySelector('#story');
        if (!storyDiv) {
            alert('Story content not found.');
            return '';
        }
        return storyDiv.innerText;
    }

    // --------- Initialize on script load ---------
    init();

})();

// ==UserScript==
// @name         Fiction Novel Download Buttons with EPUB & Improvements
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Adds download buttons (TXT, PDF, EPUB) with improved UX, content extraction, and dynamic handling.
// @author       AI
// @match        *://*.fanfiction.net/*
// @match        *://archiveofourown.org/works/*
// @match        *://literotica.com/*
// @match        *://asianfanfics.com/*
// @match        *://wattpad.com/*
// @match        *://dreame.com/*
// @match        *://inkitt.com/*
// @match        *://getinkspired.com/*
// @match        *://webnovel.com/*
// @match        *://libri7.com/*
// @match        *://fictionpress.com/*
// @match        *://starslibrary.net/*
// @match        *://fimfiction.net/*
// @grant        GM_download
// @grant        GM_addStyle
// @license      MIT 
// ==/UserScript==

(function() {
    'use strict';

    // Load CSS for floating toolbar and notifications
    GM_addStyle(`
        #ficlab-toolbar {
            position: fixed;
            top: 10px;
            right: 10px;
            background: #fff;
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 8px;
            z-index: 9999;
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
            font-family: Arial, sans-serif;
        }
        #ficlab-toolbar button {
            margin: 2px 4px;
            padding: 5px 10px;
            cursor: pointer;
            background: #007bff;
            color: #fff;
            border: none;
            border-radius: 3px;
            font-size: 14px;
        }
        #ficlab-toolbar button:hover {
            background: #0056b3;
        }
        #ficlab-notification {
            position: fixed;
            bottom: 10px;
            right: 10px;
            background: #222;
            color: #fff;
            padding: 8px 12px;
            border-radius: 4px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            z-index: 9999;
            opacity: 0.9;
        }
    `);

    // Create floating toolbar
    const toolbar = document.createElement('div');
    toolbar.id = 'ficlab-toolbar';
    document.body.appendChild(toolbar);

    // Notification function
    function notify(message, duration=3000) {
        let notif = document.getElementById('ficlab-notification');
        if (!notif) {
            notif = document.createElement('div');
            notif.id = 'ficlab-notification';
            document.body.appendChild(notif);
        }
        notif.innerText = message;
        notif.style.display = 'block';
        clearTimeout(notif.timeout);
        notif.timeout = setTimeout(() => {
            notif.style.display = 'none';
        }, duration);
    }

    // Utility to create buttons
    function createButton(text, onClick) {
        const btn = document.createElement('button');
        btn.innerText = text;
        btn.onclick = onClick;
        return btn;
    }

    // Load jsPDF for PDF export
    function loadJsPDF(callback) {
        if (window.jspdf) {
            callback();
            return;
        }
        const script = document.createElement('script');
        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
        script.onload = callback;
        document.head.appendChild(script);
    }

    // Load EPUB library (epub-gen)
    function loadEpubGen(callback) {
        if (window.epubGenLoaded) {
            callback();
            return;
        }
        const script = document.createElement('script');
        script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/build/epub.min.js';
        script.onload = () => {
            window.epubGenLoaded = true;
            callback();
        };
        document.head.appendChild(script);
    }

    // Site-specific handlers
    const siteHandlers = [
        {
            match: /fanfiction\.net/,
            selector: '.storytext',
            getContent: () => {
                const el = document.querySelector('.storytext');
                return el ? el.innerHTML : '';
            },
            filenameBase: 'FanFiction'
        },
        {
            match: /archiveofourown\.org/,
            selector: 'div#chapter-inner, div#workskin',
            getContent: () => {
                const el = document.querySelector('div#chapter-inner, div#workskin');
                return el ? el.innerHTML : '';
            },
            filenameBase: 'AO3_Story'
        },
        {
            match: /literotica\.com/,
            selector: '.storytext, .story',
            getContent: () => {
                const el = document.querySelector('.storytext, .story');
                return el ? el.innerHTML : '';
            },
            filenameBase: 'Literotica_Story'
        },
        {
            match: /asianfanfics\.com/,
            selector: '.story-content',
            getContent: () => {
                const el = document.querySelector('.story-content');
                return el ? el.innerHTML : '';
            },
            filenameBase: 'AsianFanfics_Story'
        },
        // Add more site handlers as needed
    ];

    // Detect current site handler
    function getSiteHandler() {
        const hostname = window.location.hostname;
        for (const handler of siteHandlers) {
            if (handler.match.test(hostname)) {
                return handler;
            }
        }
        return null;
    }

    const currentHandler = getSiteHandler();

    // Basic content extraction with fallback
    function getContent() {
        if (!currentHandler) return '';
        try {
            const content = currentHandler.getContent();
            if (!content || content.trim() === '') {
                notify('Story content not found or empty.');
                return '';
            }
            return content;
        } catch (e) {
            notify('Error extracting content.');
            return '';
        }
    }

    // Create download functions
    async function downloadTxt() {
        const content = getContent();
        if (!content) return;
        const blob = new Blob([content], {type: 'text/plain'});
        GM_download({url: URL.createObjectURL(blob), name: currentHandler.filenameBase + '.txt', saveAs: true});
        notify('TXT download started.');
    }

    async function downloadPdf() {
        const content = getContent();
        if (!content) return;
        notify('Generating PDF...');
        loadJsPDF(() => {
            const { jsPDF } = window.jspdf;
            const doc = new jsPDF();
            const lines = doc.splitTextToSize(content, 180);
            doc.text(lines, 10, 10);
            const blob = doc.output('blob');
            GM_download({url: URL.createObjectURL(blob), name: currentHandler.filenameBase + '.pdf', saveAs: true});
            notify('PDF download started.');
        });
    }

    async function downloadEpub() {
        const content = getContent();
        if (!content) return;
        notify('Generating EPUB...');
        loadEpubGen(() => {
            // Prepare EPUB structure
            const options = {
                title: currentHandler.filenameBase,
                author: 'Author', // Could be improved with site-specific metadata
                content: [
                    {
                        title: 'Chapter 1',
                        data: content
                    }
                ]
            };
            new window.Epub(options).generate().then((epubBlob) => {
                GM_download({url: URL.createObjectURL(epubBlob), name: currentHandler.filenameBase + '.epub', saveAs: true});
                notify('EPUB download started.');
            }).catch((err) => {
                notify('EPUB generation failed.');
                console.error(err);
            });
        });
    }

    // Add buttons to toolbar
    function setupButtons() {
        // TXT
        const btnTxt = createButton('Download TXT', downloadTxt);
        // PDF
        const btnPdf = createButton('Download PDF', downloadPdf);
        // EPUB
        const btnEpub = createButton('Download EPUB', downloadEpub);
        // Append to toolbar
        toolbar.innerHTML = ''; // Clear previous
        toolbar.appendChild(btnTxt);
        toolbar.appendChild(btnPdf);
        toolbar.appendChild(btnEpub);
    }

    // Initialize
    function init() {
        // Add buttons
        setupButtons();

        // Optional: Observe for content load if needed
        // For now, we assume static content or user clicks to download
    }

    // Run init
    init();

})();

// ==UserScript==
// @name         Fiction Novel Download Buttons with EPUB & Improvements
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Adds download buttons (TXT, PDF, EPUB) with improved UX, content extraction, and dynamic handling.
// @author       AI
// @match        *://*.fanfiction.net/*
// @match        *://archiveofourown.org/works/*
// @match        *://literotica.com/*
// @match        *://asianfanfics.com/*
// @match        *://wattpad.com/*
// @match        *://dreame.com/*
// @match        *://inkitt.com/*
// @match        *://getinkspired.com/*
// @match        *://webnovel.com/*
// @match        *://libri7.com/*
// @match        *://fictionpress.com/*
// @match        *://starslibrary.net/*
// @match        *://fimfiction.net/*
// @grant        GM_download
// @grant        GM_addStyle
// @license      MIT 
// ==/UserScript==

(function() {
    'use strict';

    // Load CSS for floating toolbar and notifications
    GM_addStyle(`
        #ficlab-toolbar {
            position: fixed;
            top: 10px;
            right: 10px;
            background: #fff;
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 8px;
            z-index: 9999;
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
            font-family: Arial, sans-serif;
        }
        #ficlab-toolbar button {
            margin: 2px 4px;
            padding: 5px 10px;
            cursor: pointer;
            background: #007bff;
            color: #fff;
            border: none;
            border-radius: 3px;
            font-size: 14px;
        }
        #ficlab-toolbar button:hover {
            background: #0056b3;
        }
        #ficlab-notification {
            position: fixed;
            bottom: 10px;
            right: 10px;
            background: #222;
            color: #fff;
            padding: 8px 12px;
            border-radius: 4px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            z-index: 9999;
            opacity: 0.9;
        }
    `);

    // Create floating toolbar
    const toolbar = document.createElement('div');
    toolbar.id = 'ficlab-toolbar';
    document.body.appendChild(toolbar);

    // Notification function
    function notify(message, duration=3000) {
        let notif = document.getElementById('ficlab-notification');
        if (!notif) {
            notif = document.createElement('div');
            notif.id = 'ficlab-notification';
            document.body.appendChild(notif);
        }
        notif.innerText = message;
        notif.style.display = 'block';
        clearTimeout(notif.timeout);
        notif.timeout = setTimeout(() => {
            notif.style.display = 'none';
        }, duration);
    }

    // Utility to create buttons
    function createButton(text, onClick) {
        const btn = document.createElement('button');
        btn.innerText = text;
        btn.onclick = onClick;
        return btn;
    }

    // Load jsPDF for PDF export
    function loadJsPDF(callback) {
        if (window.jspdf) {
            callback();
            return;
        }
        const script = document.createElement('script');
        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
        script.onload = callback;
        document.head.appendChild(script);
    }

    // Load EPUB library (epub-gen)
    function loadEpubGen(callback) {
        if (window.epubGenLoaded) {
            callback();
            return;
        }
        const script = document.createElement('script');
        script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/build/epub.min.js';
        script.onload = () => {
            window.epubGenLoaded = true;
            callback();
        };
        document.head.appendChild(script);
    }

    // Site-specific handlers array
    const siteHandlers = [
        {
            match: /fanfiction\.net/,
            selector: '.storytext',
            getContent: () => {
                const el = document.querySelector('.storytext');
                return el ? el.innerText : '';
            },
            filenameBase: 'FanFiction'
        },
        {
            match: /archiveofourown\.org/,
            selector: 'div#chapter-inner, div#workskin',
            getContent: () => {
                const el = document.querySelector('div#chapter-inner, div#workskin');
                return el ? el.innerText : '';
            },
            filenameBase: 'AO3_Story'
        },
        {
            match: /literotica\.com/,
            selector: '.storytext, .story',
            getContent: () => {
                const el = document.querySelector('.storytext, .story');
                return el ? el.innerText : '';
            },
            filenameBase: 'Literotica_Story'
        },
        {
            match: /asianfanfics\.com/,
            selector: '.story-content',
            getContent: () => {
                const el = document.querySelector('.story-content');
                return el ? el.innerText : '';
            },
            filenameBase: 'AsianFanfics_Story'
        },
        {
            match: /wattpad\.com/,
            selector: 'div#story, div.p-story__content',
            getContent: () => {
                const el = document.querySelector('div#story, div.p-story__content');
                return el ? el.innerText : '';
            },
            filenameBase: 'Wattpad_Story'
        },
        {
            match: /dreame\.com/,
            selector: '.reading-content',
            getContent: () => {
                const el = document.querySelector('.reading-content');
                return el ? el.innerText : '';
            },
            filenameBase: 'Dreame_Story'
        },
        {
            match: /inkitt\.com/,
            selector: '.story-body',
            getContent: () => {
                const el = document.querySelector('.story-body');
                return el ? el.innerText : '';
            },
            filenameBase: 'Inkitt_Story'
        },
        {
            match: /getinkspired\.com/,
            selector: '.story-content, .story-body',
            getContent: () => {
                const el = document.querySelector('.story-content, .story-body');
                return el ? el.innerText : '';
            },
            filenameBase: 'Inkspired_Story'
        },
        {
            match: /webnovel\.com/,
            selector: '.chapter-inner, .reading-content',
            getContent: () => {
                const el = document.querySelector('.chapter-inner, .reading-content');
                return el ? el.innerText : '';
            },
            filenameBase: 'Webnovel_Story'
        },
        {
            match: /libri7\.com/,
            selector: '.storytext',
            getContent: () => {
                const el = document.querySelector('.storytext');
                return el ? el.innerText : '';
            },
            filenameBase: 'Libri7_Story'
        },
        {
            match: /fictionpress\.com/,
            selector: '.storytext',
            getContent: () => {
                const el = document.querySelector('.storytext');
                return el ? el.innerText : '';
            },
            filenameBase: 'FictionPress_Story'
        },
        {
            match: /starslibrary\.net/,
            selector: '.story-text',
            getContent: () => {
                const el = document.querySelector('.story-text');
                return el ? el.innerText : '';
            },
            filenameBase: 'StarsLibrary_Story'
        },
        {
            match: /fimfiction\.net/,
            selector: '#story',
            getContent: () => {
                const el = document.querySelector('#story');
                return el ? el.innerText : '';
            },
            filenameBase: 'FimFiction_Story'
        }
    ];

    // Detect current site handler
    function getSiteHandler() {
        const hostname = window.location.hostname;
        for (const handler of siteHandlers) {
            if (handler.match.test(hostname)) {
                return handler;
            }
        }
        return null;
    }

    const currentHandler = getSiteHandler();

    // Content extraction
    function getContent() {
        if (!currentHandler) {
            notify('Site not supported for content extraction.');
            return '';
        }
        try {
            const content = currentHandler.getContent();
            if (!content || content.trim() === '') {
                notify('Story content not found or empty.');
                return '';
            }
            return content;
        } catch (e) {
            notify('Error extracting content.');
            return '';
        }
    }

    // Download functions
    async function downloadTxt() {
        const content = getContent();
        if (!content) return;
        const blob = new Blob([content], {type: 'text/plain'});
        GM_download({url: URL.createObjectURL(blob), name: currentHandler.filenameBase + '.txt', saveAs: true});
        notify('TXT download started.');
    }

    async function downloadPdf() {
        const content = getContent();
        if (!content) return;
        notify('Generating PDF...');
        loadJsPDF(() => {
            const { jsPDF } = window.jspdf;
            const doc = new jsPDF();
            const lines = doc.splitTextToSize(content, 180);
            doc.text(lines, 10, 10);
            const blob = doc.output('blob');
            GM_download({url: URL.createObjectURL(blob), name: currentHandler.filenameBase + '.pdf', saveAs: true});
            notify('PDF download started.');
        });
    }

    async function downloadEpub() {
        const content = getContent();
        if (!content) return;
        notify('Generating EPUB...');
        loadEpubGen(() => {
            const options = {
                title: currentHandler.filenameBase,
                author: 'Author', // Could be improved with site-specific metadata
                content: [
                    {
                        title: 'Chapter 1',
                        data: content
                    }
                ]
            };
            new window.Epub(options).generate().then((epubBlob) => {
                GM_download({url: URL.createObjectURL(epubBlob), name: currentHandler.filenameBase + '.epub', saveAs: true});
                notify('EPUB download started.');
            }).catch((err) => {
                notify('EPUB generation failed.');
                console.error(err);
            });
        });
    }

    // Setup buttons
    function setupButtons() {
        // Clear previous buttons
        toolbar.innerHTML = '';

        // Create buttons
        const btnTxt = createButton('Download TXT', downloadTxt);
        const btnPdf = createButton('Download PDF', downloadPdf);
        const btnEpub = createButton('Download EPUB', downloadEpub);

        // Append to toolbar
        toolbar.appendChild(btnTxt);
        toolbar.appendChild(btnPdf);
        toolbar.appendChild(btnEpub);
    }

    // Initialize
    function init() {
        setupButtons();
    }

    // Run on script load
    init();

})();