Anitsu Downloader Menu

Adiciona um Menu de Gerenciamento de Downloads para o Anitsu Cloud. Para uso no IDM desative a opção "Ver mensagem no inicio do download" e ative a opção "somente adicionar arquivos a fila".

// ==UserScript==
// @name         Anitsu Downloader Menu
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  Adiciona um Menu de Gerenciamento de Downloads para o Anitsu Cloud. Para uso no IDM desative a opção "Ver mensagem no inicio do download" e ative a opção "somente adicionar arquivos a fila".
// @author       Jack/Kingvegeta
// @match        https://cloud.anitsu.moe/nextcloud/s*
// @grant        none
// @license       MIT
// ==/UserScript==

(function () {
    'use strict';

    // ===== CONFIGURAÇÕES PERSONALIZÁVEIS =====

    // Timing e Performance
    const SCROLL_DELAY = 2000; // Tempo entre scrolls para carregar arquivos (ms)
    const ITEM_PROCESSING_DELAY = 2000; // Tempo entre downloads de cada arquivo (ms)
    const CLICK_DELAY = 500; // Tempo de espera após cliques (ms)
    const MAX_SCROLL_ATTEMPTS = 20; // Máximo de tentativas de scroll
    const MINIMUM_FILE_COUNT = 10; // Mínimo de arquivos para considerar carregamento completo
    const PROGRESS_UPDATE_INTERVAL = 500; // Intervalo de atualização do contador (ms)
    const PROGRESS_AUTO_CLOSE_DELAY = 3000; // Tempo para fechar tela de progresso (ms)

    // Interface e Cores
    const COLORS = {
        primary: '#007bff',
        primaryDark: '#003366',
        secondary: '#00c6ff',
        success: '#28a745',
        successLight: '#20c997',
        danger: '#dc3545',
        warning: '#ffc107',
        gray: '#6c757d',
        lightGray: '#e9ecef',
        darkGray: '#495057',
        white: '#fff',
        background: '#f8f9fa',
        border: '#dee2e6'
    };

    // Tamanhos e Dimensões
    const SIZES = {
        buttonPadding: '10px 32px',
        popupPadding: '40px',
        contentPadding: '24px',
        borderRadius: '16px',
        buttonBorderRadius: '32px',
        minPopupWidth: '500px',
        maxPopupWidth: '90vw',
        maxPopupHeight: '85vh',
        progressBarHeight: '20px',
        buttonMinWidth: '180px'
    };

    // Fontes e Texto
    const FONTS = {
        primary: 'Segoe UI, sans-serif',
        title: 'Montserrat, Arial Black, sans-serif'
    };

    const FONT_SIZES = {
        buttonText: '18px',
        popupTitle: '26px',
        contentTitle: '20px',
        progressTitle: '24px',
        regular: '16px',
        small: '14px',
        closeButton: '18px'
    };

    // Z-Index Layers
    const Z_INDEX = {
        mainButton: 9998,
        overlay: 9999,
        popup: 10000,
        popupTitle: 10002,
        closeButton: 10003,
        progressOverlay: 10100
    };

    // Textos e Labels
    const TEXTS = {
        mainButton: 'Anitsu Downloader Menu',
        popupTitle: 'Anitsu Downloader Menu',
        loadingTitle: 'Listando arquivos, aguarde...',
        progressTitle: 'Processando Downloads',
        selectAll: 'Selecionar Todos',
        downloadSelected: 'Download Selected',
        downloadAll: 'Download All',
        processing: 'Processando...',
        filesFound: 'arquivos encontrados',
        filesFoundCounter: 'Arquivos encontrados:',
        noFilesFound: 'Nenhum arquivo encontrado.',
        selectAtLeastOne: 'Selecione pelo menos um arquivo para baixar!',
        confirmDownloadAll: '\nTem certeza que deseja baixar TODOS os {count} arquivos encontrados?',
        processingFile: 'Processando:',
        filesProcessed: 'arquivos processados',
        completed: '✅ Concluído! {count} arquivos enviados para download',
        noNewFiles: 'Nenhum arquivo novo para processar.',
        progressInit: 'Iniciando...',
        noFunction: 'Função showPopup não está definida!',
        fileDefaultName: 'Arquivo'
    };

    // Seletores CSS
    const SELECTOR_FILE_ROW = 'tr[data-file]';
    const SELECTOR_MENU_BUTTON = 'a.action-menu';
    const SELECTOR_DOWNLOAD_LINK = 'a.menuitem.action.action-download';

    // ===== CÓDIGO PRINCIPAL =====

    let processedFiles = new Set();

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    async function scrollToBottom() {
        let scrollAttempts = 0;
        let previousHeight = document.documentElement.scrollHeight;
        let initialItemsCount = document.querySelectorAll(SELECTOR_FILE_ROW).length;

        while (scrollAttempts < MAX_SCROLL_ATTEMPTS) {
            window.scrollTo(0, document.body.scrollHeight);
            await sleep(SCROLL_DELAY);

            let currentHeight = document.documentElement.scrollHeight;
            let currentItemsCount = document.querySelectorAll(SELECTOR_FILE_ROW).length;

            if (currentHeight === previousHeight && currentItemsCount === initialItemsCount) {
                break;
            }

            previousHeight = currentHeight;
            initialItemsCount = currentItemsCount;
            scrollAttempts++;
        }
    }

    // Função para processar arquivos com atualização periódica da barra de progresso
    async function processFiles(selectedFiles) {
        console.log("[Nextcloud DL] Iniciando processo de download...");
        await scrollToBottom();
        await sleep(2000);

        const fileRows = document.querySelectorAll(SELECTOR_FILE_ROW);
        if (fileRows.length === 0) {
            alert("Nenhum arquivo encontrado.");
            return;
        }

        // Filtra apenas arquivos (com extensão)
        const filesToProcess = Array.from(fileRows).filter(row => {
            const extensionElement = row.querySelector('.extension');
            if (!extensionElement) return false; // Pula pastas

            const fileNameElement = row.querySelector('.innernametext');
            const fileName = fileNameElement ? fileNameElement.innerText.trim() : 'Unknown File';
            const fileExtension = extensionElement ? extensionElement.innerText.trim() : '';
            const fullFileName = fileName + fileExtension;

            if (selectedFiles && !selectedFiles.includes(fullFileName)) return false;
            if (processedFiles.has(fullFileName)) return false;
            return true;
        });

        if (filesToProcess.length === 0) {
            alert("Nenhum arquivo novo para processar.");
            return;
        }

        // Cria tela de progresso
        const progressOverlay = createProgressScreen(filesToProcess.length);
        document.body.appendChild(progressOverlay);

        let processedCount = 0;
        let currentFile = '';
        let intervalId = setInterval(() => {
            updateProgressScreen(progressOverlay, processedCount, filesToProcess.length, currentFile);
        }, PROGRESS_UPDATE_INTERVAL);

        for (const row of filesToProcess) {
            const fileNameElement = row.querySelector('.innernametext');
            const extensionElement = row.querySelector('.extension');
            const fileName = fileNameElement ? fileNameElement.innerText.trim() : 'Unknown File';
            const fileExtension = extensionElement ? extensionElement.innerText.trim() : '';
            const fullFileName = fileName + fileExtension;

            processedCount++;
            currentFile = fullFileName;

            const menuButton = row.querySelector(SELECTOR_MENU_BUTTON);
            if (menuButton) {
                menuButton.click();
                await sleep(CLICK_DELAY);

                const downloadLink = document.querySelector(SELECTOR_DOWNLOAD_LINK);
                if (downloadLink) {
                    downloadLink.click();
                    processedFiles.add(fullFileName);
                }

                document.dispatchEvent(new KeyboardEvent('keydown', {
                    key: 'Escape',
                    keyCode: 27,
                    which: 27,
                    bubbles: true
                }));
                await sleep(CLICK_DELAY);
            }

            await sleep(ITEM_PROCESSING_DELAY);
        }

        clearInterval(intervalId);
        updateProgressScreen(progressOverlay, processedCount, filesToProcess.length, currentFile);
        finalizeProgressScreen(progressOverlay, processedCount);

        setTimeout(() => {
            if (progressOverlay && progressOverlay.parentNode) {
                progressOverlay.remove();
            }
        }, PROGRESS_AUTO_CLOSE_DELAY);
    }

    // Função para criar tela de progresso
    function createProgressScreen(totalFiles) {
        const overlay = document.createElement('div');
        overlay.id = 'anitsu-progress-overlay';
        Object.assign(overlay.style, {
            position: 'fixed',
            top: '0',
            left: '0',
            width: '100vw',
            height: '100vh',
            background: 'rgba(0,0,0,0.85)',
            zIndex: Z_INDEX.progressOverlay,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center'
        });

        const progressPopup = document.createElement('div');
        Object.assign(progressPopup.style, {
            background: COLORS.white,
            borderRadius: SIZES.borderRadius,
            boxShadow: '0 8px 32px rgba(0,0,0,0.25)',
            padding: SIZES.popupPadding,
            textAlign: 'center',
            minWidth: '400px',
            maxWidth: SIZES.maxPopupWidth
        });

        // Título
        const title = document.createElement('div');
        title.textContent = TEXTS.progressTitle;
        Object.assign(title.style, {
            fontSize: FONT_SIZES.progressTitle,
            fontWeight: 'bold',
            fontFamily: FONTS.title,
            color: COLORS.primary,
            textShadow: '2px 2px 0px rgba(0,0,0,0.8), -1px -1px 0px rgba(0,0,0,0.8), 1px -1px 0px rgba(0,0,0,0.8), -1px 1px 0px rgba(0,0,0,0.8)',
            marginBottom: '20px'
        });

        // Status atual
        const status = document.createElement('div');
        status.textContent = TEXTS.progressInit;
        status.id = 'progress-status';
        Object.assign(status.style, {
            fontSize: FONT_SIZES.regular,
            color: COLORS.darkGray,
            marginBottom: '20px',
            minHeight: '20px'
        });

        // Progress bar container
        const progressContainer = document.createElement('div');
        Object.assign(progressContainer.style, {
            width: '100%',
            height: SIZES.progressBarHeight,
            background: COLORS.lightGray,
            borderRadius: '10px',
            overflow: 'hidden',
            marginBottom: '15px',
            boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.1)'
        });

        // Progress bar
        const progressBar = document.createElement('div');
        progressBar.id = 'progress-bar';
        Object.assign(progressBar.style, {
            width: '0%',
            height: '100%',
            background: `linear-gradient(90deg, ${COLORS.primary} 0%, ${COLORS.secondary} 100%)`,
            borderRadius: '10px',
            transition: 'width 0.3s ease',
            position: 'relative'
        });

        // Progress text
        const progressText = document.createElement('div');
        progressText.id = 'progress-text';
        progressText.textContent = '0%';
        Object.assign(progressText.style, {
            fontSize: FONT_SIZES.small,
            fontWeight: 'bold',
            color: COLORS.darkGray,
            marginTop: '10px'
        });

        // Contador de arquivos
        const counter = document.createElement('div');
        counter.id = 'progress-counter';
        counter.textContent = `0 de ${totalFiles} ${TEXTS.filesProcessed}`;
        Object.assign(counter.style, {
            fontSize: FONT_SIZES.small,
            color: COLORS.gray,
            marginTop: '10px'
        });

        progressContainer.appendChild(progressBar);
        progressPopup.appendChild(title);
        progressPopup.appendChild(status);
        progressPopup.appendChild(progressContainer);
        progressPopup.appendChild(progressText);
        progressPopup.appendChild(counter);
        overlay.appendChild(progressPopup);

        return overlay;
    }

    // Função para atualizar progresso (agora usando PROGRESS_UPDATE_INTERVAL)
    function updateProgressScreen(overlay, current, total, currentFile) {
        const percentage = Math.round((current / total) * 100);

        const status = overlay.querySelector('#progress-status');
        const progressBar = overlay.querySelector('#progress-bar');
        const progressText = overlay.querySelector('#progress-text');
        const counter = overlay.querySelector('#progress-counter');

        if (status) status.textContent = `${TEXTS.processingFile} ${currentFile}`;
        if (progressBar) progressBar.style.width = `${percentage}%`;
        if (progressText) progressText.textContent = `${percentage}%`;
        if (counter) counter.textContent = `${current} de ${total} ${TEXTS.filesProcessed}`;
    }

    // Função para finalizar progresso (usando PROGRESS_AUTO_CLOSE_DELAY)
    function finalizeProgressScreen(overlay, totalProcessed) {
        const status = overlay.querySelector('#progress-status');
        const progressBar = overlay.querySelector('#progress-bar');
        const progressText = overlay.querySelector('#progress-text');

        if (status) {
            status.textContent = TEXTS.completed.replace('{count}', totalProcessed);
            status.style.color = COLORS.success;
            status.style.fontWeight = 'bold';
        }

        if (progressBar) {
            progressBar.style.background = `linear-gradient(90deg, ${COLORS.success} 0%, ${COLORS.successLight} 100%)`;
            progressBar.style.width = '100%';
        }

        if (progressText) {
            progressText.textContent = '100%';
            progressText.style.color = COLORS.success;
        }
    }

    function createCompactDownloadButton() {
        // Botão fixo no topo, encostado no limite superior da tela
        const assistBtn = document.createElement('button');
        assistBtn.textContent = TEXTS.mainButton;
        Object.assign(assistBtn.style, {
            position: 'fixed',
            top: '0px',
            left: '50%',
            transform: 'translateX(-50%)',
            background: 'rgba(0, 51, 102, 0.85)', // Azul escuro transparente
            color: '#fff',
            border: '2px solid #007bff',
            borderRadius: '32px', // Mais arredondado
            fontSize: '18px',
            fontWeight: 'bold',
            fontFamily: 'Segoe UI, sans-serif',
            cursor: 'pointer',
            padding: '10px 32px',
            boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
            zIndex: '9998' // Menor que o overlay e popup
        });

        assistBtn.addEventListener('mouseenter', () => {
            assistBtn.style.opacity = '0.85';
        });
        assistBtn.addEventListener('mouseleave', () => {
            assistBtn.style.opacity = '1';
        });

        document.body.appendChild(assistBtn);

        // Ao clicar no botão, mostra popup
        assistBtn.addEventListener('click', () => {
            if (typeof showPopup === 'function') {
                showPopup();
            } else {
                alert(TEXTS.noFunction);
            }
        });
    }

    // Função para carregar todos os arquivos fazendo scroll
    async function loadAllFiles() {
        let lastFileCount = 0;
        let currentFileCount = document.querySelectorAll(SELECTOR_FILE_ROW).length;
        let attempts = 0;
        const maxAttempts = 50; // Reduzido para ser mais eficiente

        console.log("[Nextcloud DL] Iniciando carregamento de todos os arquivos...");

        // Encontra o container de arquivos correto - prioriza #app-content
        const filesContainer = document.querySelector('#app-content') ||
                              document.querySelector('.files-fileList') ||
                              document.querySelector('tbody.files-fileList') ||
                              document.querySelector('#app-content-files') ||
                              document.body;

        console.log("[Nextcloud DL] Container encontrado:", filesContainer.id || filesContainer.className || 'body');

        // Se já temos menos de 12 arquivos, provavelmente não há mais para carregar
        if (currentFileCount > 0 && currentFileCount < MINIMUM_FILE_COUNT) {
            console.log(`[Nextcloud DL] Poucos arquivos encontrados (${currentFileCount}), assumindo que é o total`);
            return currentFileCount;
        }

        while (attempts < maxAttempts) {
            // Scroll no container específico (prioriza #app-content)
            if (filesContainer.id === 'app-content') {
                filesContainer.scrollTop = filesContainer.scrollHeight;
            } else if (filesContainer !== document.body) {
                filesContainer.scrollTop = filesContainer.scrollHeight;
            }

            // Também faz scroll na janela principal como backup
            window.scrollTo(0, document.documentElement.scrollHeight);

            // Tempo reduzido para ser mais rápido
            await sleep(1000);

            // Conta quantos arquivos temos agora
            currentFileCount = document.querySelectorAll(SELECTOR_FILE_ROW).length;

            console.log(`[Nextcloud DL] Arquivos encontrados: ${currentFileCount} (tentativa ${attempts + 1})`);

            // Se o número de arquivos não mudou por algumas tentativas consecutivas
            if (currentFileCount === lastFileCount) {
                attempts++;
                // Reduzido para 3 tentativas se não há mudança
                if (attempts >= 3) {
                    console.log("[Nextcloud DL] Parando - nenhum arquivo novo encontrado por 3 tentativas");
                    break;
                }
            } else {
                attempts = 0; // Reset contador se encontrou novos arquivos
                console.log(`[Nextcloud DL] Novos arquivos encontrados! Total: ${currentFileCount}`);
            }

            // Se carregamos exatamente múltiplos de 18, provavelmente há mais
            // Mas se não é múltiplo de 18, provavelmente chegamos ao fim
            if (currentFileCount > lastFileCount && currentFileCount % 18 !== 0 && currentFileCount > 18) {
                console.log(`[Nextcloud DL] Número de arquivos (${currentFileCount}) não é múltiplo de 18, provavelmente chegamos ao fim`);
                break;
            }

            lastFileCount = currentFileCount;
        }

        // Volta ao topo
        if (filesContainer.id === 'app-content') {
            filesContainer.scrollTop = 0;
        } else if (filesContainer !== document.body) {
            filesContainer.scrollTop = 0;
        }
        window.scrollTo(0, 0);

        console.log(`[Nextcloud DL] Carregamento concluído. Total final: ${currentFileCount} arquivos`);
        return currentFileCount;
    }

    // Função melhorada para atualizar o popup de carregamento com progresso
    async function showPopup() {
        // Remove popups antigos
        const oldPopup = document.getElementById('anitsu-dl-popup');
        if (oldPopup) oldPopup.remove();
        const oldOverlay = document.getElementById('anitsu-dl-overlay');
        if (oldOverlay) oldOverlay.remove();

        // Overlay escuro
        const overlay = document.createElement('div');
        overlay.id = 'anitsu-dl-overlay';
        Object.assign(overlay.style, {
            position: 'fixed',
            top: '0',
            left: '0',
            width: '100vw',
            height: '100vh',
            background: 'rgba(0,0,0,0.92)',
            zIndex: '9999'
        });

        // Fechar popup ao clicar fora
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                popup.remove();
                overlay.remove();
            }
        });

        document.body.appendChild(overlay);

        // Popup de carregamento
        const loadingPopup = document.createElement('div');
        loadingPopup.id = 'anitsu-dl-loading-popup';
        Object.assign(loadingPopup.style, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            background: '#fff',
            borderRadius: '16px',
            boxShadow: '0 8px 32px rgba(0,0,0,0.25)',
            zIndex: '10000',
            padding: '40px',
            textAlign: 'center',
            minWidth: '350px'
        });

        // Título de carregamento
        const loadingTitle = document.createElement('div');
        loadingTitle.textContent = TEXTS.loadingTitle;
        Object.assign(loadingTitle.style, {
            fontSize: '20px',
            fontWeight: 'bold',
            fontFamily: 'Montserrat, Arial Black, sans-serif',
            color: COLORS.primary,
            textShadow: '2px 2px 0px rgba(0,0,0,0.8), -1px -1px 0px rgba(0,0,0,0.8), 1px -1px 0px rgba(0,0,0,0.8), -1px 1px 0px rgba(0,0,0,0.8)',
            marginBottom: '20px'
        });

        // Contador de arquivos em tempo real
        const fileCounter = document.createElement('div');
        fileCounter.textContent = `${TEXTS.filesFoundCounter} 0`;
        Object.assign(fileCounter.style, {
            fontSize: '16px',
            color: '#666',
            marginBottom: '20px'
        });

        // Spinner de carregamento
        const spinner = document.createElement('div');
        spinner.innerHTML = '⏳'; // Ícone pode ficar fixo

        // Adiciona animação CSS para o spinner
        const style = document.createElement('style');
        style.textContent = `
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
        `;
        document.head.appendChild(style);

        loadingPopup.appendChild(loadingTitle);
        loadingPopup.appendChild(fileCounter);
        loadingPopup.appendChild(spinner);
        document.body.appendChild(loadingPopup);

        // Monitora o progresso do carregamento
        const progressInterval = setInterval(() => {
            const currentCount = document.querySelectorAll(SELECTOR_FILE_ROW).length;
            fileCounter.textContent = `Arquivos encontrados: ${currentCount}`;
        }, 500);

        // Carrega todos os arquivos
        const totalFiles = await loadAllFiles();

        // Conta apenas arquivos (com extensão), não pastas
        const actualFileRows = document.querySelectorAll(SELECTOR_FILE_ROW);
        const filesOnly = Array.from(actualFileRows).filter(row => row.querySelector('.extension'));
        const actualFileCount = filesOnly.length;

        // Para o monitoramento
        clearInterval(progressInterval);

        // Remove popup de carregamento
        loadingPopup.remove();

        // Popup principal
        const popup = document.createElement('div');
        popup.id = 'anitsu-dl-popup';
        Object.assign(popup.style, {
            position: 'fixed',
            top: '3%',
            left: '50%',
            transform: 'translate(-50%, 0)',
            background: '#fff',
            borderRadius: '16px',
            boxShadow: '0 8px 32px rgba(0,0,0,0.25)',
            zIndex: '10000',
            padding: '0',
            minWidth: '500px',
            maxWidth: '90vw',
            maxHeight: '85vh',
            overflowY: 'auto',
            overflowX: 'hidden',
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center'
        });

        // Título do popup
        const title = document.createElement('div');
        title.textContent = TEXTS.popupTitle;
        Object.assign(title.style, {
            fontSize: '26px',
            fontWeight: 'bold',
            fontFamily: 'Montserrat, Arial Black, sans-serif',
            color: '#fff',
            textShadow: '3px 3px 0px rgba(0,0,0,1), -1px -1px 0px rgba(0,0,0,1), 1px -1px 0px rgba(0,0,0,1), -1px 1px 0px rgba(0,0,0,1)',
            background: 'linear-gradient(90deg, #007bff 60%, #003366 100%)',
            padding: '16px 60px 16px 16px',
            width: '100%',
            textAlign: 'center',
            borderRadius: '16px 16px 0 0',
            marginBottom: '0px',
            letterSpacing: '2px',
            boxShadow: '0 2px 8px rgba(0,0,0,0.10)',
            border: 'none',
            position: 'sticky',
            top: '0',
            zIndex: '10002'
        });

        // Botão fechar melhorado
        const closeBtn = document.createElement('button');
        closeBtn.textContent = '✖';
        Object.assign(closeBtn.style, {
            position: 'absolute',
            top: '50%',
            right: '40px', // Movido ainda mais para a esquerda para evitar a barra de scroll
            transform: 'translateY(-50%)',
            background: 'rgba(255,255,255,0.1)',
            border: '2px solid #ff0000', // Borda vermelha quadrada
            borderRadius: '4px', // Bordas ligeiramente arredondadas para ficar quadrada
            fontSize: '18px',
            color: '#fff',
            cursor: 'pointer',
            width: '32px',
            height: '32px',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            zIndex: '10003',
            transition: 'all 0.2s ease'
        });

        // Efeitos hover no botão X
        closeBtn.addEventListener('mouseenter', () => {
            closeBtn.style.background = 'rgba(255,0,0,0.2)';
            closeBtn.style.borderColor = '#ff4444';
        });
        closeBtn.addEventListener('mouseleave', () => {
            closeBtn.style.background = 'rgba(255,255,255,0.1)';
            closeBtn.style.borderColor = '#ff0000';
        });

        closeBtn.onclick = () => {
            popup.remove();
            overlay.remove();
        };

        // Adiciona título e botão fechar
        title.appendChild(closeBtn);
        popup.appendChild(title);

        // Conteúdo do popup
        const content = document.createElement('div');
        content.style.padding = '24px';
        content.style.width = '100%';
        content.style.boxSizing = 'border-box';

        content.innerHTML = `<b style="font-size:20px;color:${COLORS.primary};">${TEXTS.selectAll} (${actualFileCount} ${TEXTS.filesFound})</b>`;

        // Lista de arquivos com checkboxes
        const filesDiv = document.createElement('div');
        filesDiv.style.width = '100%';
        filesDiv.style.marginTop = '24px';
        filesDiv.style.wordBreak = 'break-word';

        const fileRows = document.querySelectorAll(SELECTOR_FILE_ROW);
        let checkboxes = [];
        if (fileRows.length === 0) {
            filesDiv.textContent = TEXTS.noFilesFound;
        } else {
            // Caixa de seleção global
            const selectAllContainer = document.createElement('div');
            selectAllContainer.style.display = 'flex';
            selectAllContainer.style.alignItems = 'center';
            selectAllContainer.style.marginBottom = '12px';
            selectAllContainer.style.width = '100%';

            const selectAllCheckbox = document.createElement('input');
            selectAllCheckbox.type = 'checkbox';
            selectAllCheckbox.id = 'anitsu-dl-select-all';
            selectAllCheckbox.style.marginRight = '10px';
            selectAllCheckbox.style.flexShrink = '0';

            const selectAllLabel = document.createElement('label');
            selectAllLabel.htmlFor = 'anitsu-dl-select-all';
            selectAllLabel.textContent = TEXTS.selectAll;
            selectAllLabel.style.flexGrow = '1';

            selectAllContainer.appendChild(selectAllCheckbox);
            selectAllContainer.appendChild(selectAllLabel);
            filesDiv.appendChild(selectAllContainer);

            fileRows.forEach((row, idx) => {
                const fileNameElement = row.querySelector('.innernametext');
                const extensionElement = row.querySelector('.extension');

                // Se não tem extensão, é uma pasta - pula
                if (!extensionElement) return;

                const fileName = fileNameElement ? fileNameElement.innerText.trim() : `${TEXTS.fileDefaultName} ${idx+1}`; // Corrigido
                const fileExtension = extensionElement ? extensionElement.innerText.trim() : '';
                const fullFileName = fileName + fileExtension;

                const label = document.createElement('label');
                label.style.display = 'flex';
                label.style.alignItems = 'flex-start';
                label.style.marginBottom = '8px';
                label.style.cursor = 'pointer';
                label.style.width = '100%';

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.value = fullFileName;
                checkbox.checked = false;
                checkbox.style.marginRight = '10px';
                checkbox.style.flexShrink = '0';
                checkbox.style.marginTop = '2px';

                const textSpan = document.createElement('span');
                textSpan.textContent = fullFileName;
                textSpan.style.flexGrow = '1';
                textSpan.style.wordBreak = 'break-word';

                checkboxes.push(checkbox);

                label.appendChild(checkbox);
                label.appendChild(textSpan);
                filesDiv.appendChild(label);
            });

            // Sincroniza seleção global com as individuais
            selectAllCheckbox.addEventListener('change', () => {
                checkboxes.forEach(cb => cb.checked = selectAllCheckbox.checked);
            });
            checkboxes.forEach(cb => {
                cb.addEventListener('change', () => {
                    selectAllCheckbox.checked = checkboxes.length > 0 && checkboxes.every(c => c.checked);
                });
            });
        }

        // Botões de ação - FIXOS NA PARTE INFERIOR
        const actionsDiv = document.createElement('div');
        Object.assign(actionsDiv.style, {
            display: 'flex',
            gap: '16px',
            padding: '20px 24px',
            flexWrap: 'wrap',
            justifyContent: 'center',
            background: '#f8f9fa',
            borderTop: '1px solid #dee2e6',
            borderRadius: '0 0 16px 16px',
            position: 'sticky',
            bottom: '0',
            zIndex: '10002',
            boxShadow: '0 -2px 8px rgba(0,0,0,0.10)',
            width: '100%',
            boxSizing: 'border-box'
        });

        // Download Selected
        const btnSelected = document.createElement('button');
        btnSelected.innerHTML = `<span style="font-size:18px;vertical-align:middle;">&#x2193;</span> ${TEXTS.downloadSelected}`;
        Object.assign(btnSelected.style, {
            background: 'linear-gradient(90deg, #007bff 60%, #00c6ff 100%)',
            color: '#fff',
            border: 'none',
            borderRadius: '32px',
            fontSize: '16px',
            fontWeight: 'bold',
            fontFamily: 'Segoe UI, sans-serif',
            cursor: 'pointer',
            padding: '12px 28px',
            boxShadow: '0 4px 12px rgba(0,123,255,0.25)',
            minWidth: '180px',
            transition: 'all 0.2s ease'
        });

        // Efeitos hover no botão Selected
        btnSelected.addEventListener('mouseenter', () => {
            btnSelected.style.transform = 'translateY(-2px)';
            btnSelected.style.boxShadow = '0 6px 16px rgba(0,123,255,0.35)';
        });
        btnSelected.addEventListener('mouseleave', () => {
            btnSelected.style.transform = 'translateY(0)';
            btnSelected.style.boxShadow = '0 4px 12px rgba(0,123,255,0.25)';
        });

        // Download All
        const btnAll = document.createElement('button');
        btnAll.innerHTML = `<span style="font-size:18px;vertical-align:middle;">&#x2193;</span> ${TEXTS.downloadAll}`;
        Object.assign(btnAll.style, {
            background: 'linear-gradient(90deg, #28a745 60%, #20c997 100%)',
            color: '#fff',
            border: 'none',
            borderRadius: '32px',
            fontSize: '16px',
            fontWeight: 'bold',
            fontFamily: 'Segoe UI, sans-serif',
            cursor: 'pointer',
            padding: '12px 28px',
            boxShadow: '0 4px 12px rgba(40,167,69,0.25)',
            minWidth: '180px',
            transition: 'all 0.2s ease'
        });

        // Efeitos hover no botão All
        btnAll.addEventListener('mouseenter', () => {
            btnAll.style.transform = 'translateY(-2px)';
            btnAll.style.boxShadow = '0 6px 16px rgba(40,167,69,0.35)';
        });
        btnAll.addEventListener('mouseleave', () => {
            btnAll.style.transform = 'translateY(0)';
            btnAll.style.boxShadow = '0 4px 12px rgba(40,167,69,0.25)';
        });

        // Função para baixar todos
        btnAll.onclick = async () => {
            const confirmed = confirm(TEXTS.confirmDownloadAll.replace('{count}', actualFileCount));
            if (!confirmed) {
                return;
            }
            btnAll.disabled = true;
            btnSelected.disabled = true;
            btnAll.innerHTML = `<span style="font-size:18px;vertical-align:middle;">&#x231B;</span> ${TEXTS.processing}`;
            btnAll.style.background = COLORS.gray;

            await processFiles();

            btnAll.innerHTML = `<span style="font-size:18px;vertical-align:middle;">&#x2193;</span> ${TEXTS.downloadAll}`;
            btnAll.style.background = 'linear-gradient(90deg, #28a745 60%, #20c997 100%)';
            btnAll.disabled = false;
            btnSelected.disabled = false;
        };

        // Função para baixar selecionados
        btnSelected.onclick = async () => {
            const selectedNames = checkboxes.filter(cb => cb.checked).map(cb => cb.value);
            if (selectedNames.length === 0) {
                alert(TEXTS.selectAtLeastOne);
                return;
            }
            btnAll.disabled = true;
            btnSelected.disabled = true;
            btnSelected.innerHTML = `<span style="font-size:18px;vertical-align:middle;">&#x231B;</span> ${TEXTS.processing}`;
            btnSelected.style.background = COLORS.gray;

            await processFiles(selectedNames);

            btnSelected.innerHTML = `<span style="font-size:18px;vertical-align:middle;">&#x2193;</span> ${TEXTS.downloadSelected}`;
            btnSelected.style.background = 'linear-gradient(90deg, #007bff 60%, #00c6ff 100%)';
            btnSelected.disabled = false;
            btnAll.disabled = false;
        };

        // Adiciona os botões
        actionsDiv.appendChild(btnSelected);
        actionsDiv.appendChild(btnAll);

        // Remove o marginTop dos botões do conteúdo e adiciona o actionsDiv diretamente ao popup
        content.appendChild(filesDiv);
        popup.appendChild(content);
        popup.appendChild(actionsDiv); // Botões fixos na parte inferior

        document.body.appendChild(popup);
    }

    // Espera a página estar carregada para injetar o botão
    const observer = new MutationObserver((mutations, obs) => {
        if (document.querySelector(SELECTOR_FILE_ROW)) {
            obs.disconnect();
            createCompactDownloadButton();
            console.log("[Nextcloud DL] Botão compacto inserido.");
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });
})();


// ===== Seleção por intervalo usando Ctrl =====
let lastCheckedIndex = null;

document.addEventListener('click', function(e) {
    if (e.target && e.target.type === 'checkbox') {
        const checkboxes = Array.from(document.querySelectorAll('input[type="checkbox"]'));
        const currentIndex = checkboxes.indexOf(e.target);

        if (e.ctrlKey && lastCheckedIndex !== null) {
            const [start, end] = [lastCheckedIndex, currentIndex].sort((a, b) => a - b);
            const shouldCheck = e.target.checked;
            for (let i = start; i <= end; i++) {
                checkboxes[i].checked = shouldCheck;
            }
        }

        lastCheckedIndex = currentIndex;
    }
});