GitHub Repo File Downloader

Allows you to download individual files directly from GitHub repository pages.

Versione datata 09/12/2024. Vedi la nuova versione l'ultima versione.

// ==UserScript==
// @name         GitHub Repo File Downloader
// @description  Allows you to download individual files directly from GitHub repository pages.
// @icon         https://github.githubassets.com/favicons/favicon-dark.svg
// @version      1.0
// @author       afkarxyz
// @namespace    https://github.com/afkarxyz/misc-scripts/
// @supportURL   https://github.com/afkarxyz/misc-scripts/issues
// @license      MIT
// @match        https://github.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    function isIconModified(svg) {
        return svg.dataset.customIcon === 'true';
    }

    function modifySvgContent(svg, newContent, downloadUrl, fileName) {
        if (svg && !isIconModified(svg)) {
            const wrapper = document.createElement('div');
            wrapper.style.cursor = 'pointer';
            wrapper.style.display = 'inline-block';
            wrapper.style.transition = 'transform 0.1s ease-in-out';
            
            const newSvg = svg.cloneNode(true);
            newSvg.innerHTML = newContent;
            newSvg.dataset.customIcon = 'true';
            
            wrapper.appendChild(newSvg);
            wrapper.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                downloadFile(downloadUrl, fileName);
            });
            
            wrapper.addEventListener('mouseenter', () => {
                wrapper.style.transform = 'scale(1.1)';
            });
            wrapper.addEventListener('mouseleave', () => {
                wrapper.style.transform = 'scale(1)';
            });
            
            svg.parentNode.replaceChild(wrapper, svg);
        }
    }

    async function downloadFile(url, fileName) {
        try {
            const response = await fetch(url);
            const blob = await response.blob();
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);
            link.download = fileName;
            link.style.display = 'none';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        } catch (error) {
            console.error('Download failed:', error);
        }
    }

    function updateIcons() {
        const fileRows = document.querySelectorAll('.react-directory-row');
        let modifiedFileCount = 0;

        fileRows.forEach(row => {
            const svgs = row.querySelectorAll('.react-directory-filename-column svg:not([data-custom-icon="true"])');

            svgs.forEach(svg => {
                if (svg.classList.contains('color-fg-muted')) {
                    const fileLink = row.querySelector('a[href]');
                    if (!fileLink) return;
                    
                    const downloadUrl = fileLink.href
                        .replace('github.com', 'raw.githubusercontent.com')
                        .replace('/blob/', '/');
                    
                    const fileName = fileLink.textContent.trim();

                    const newFileContent = `
                        <path d="M14.5,3.4l-2.9-2.9C11.2,0.2,10.8,0,10.3,0H3.8C2.8,0,2,0.8,2,1.8v12.5c0,1,0.8,1.8,1.8,1.8h9.5c1,0,1.8-0.8,1.8-1.8V4.7
                            C15,4.2,14.8,3.8,14.5,3.4z M10.5,1.6L10.5,1.6l2.9,2.9l0,0h-2.7c-0.1,0-0.2-0.1-0.2-0.2V1.6z M13.5,14.2c0,0.1-0.1,0.2-0.2,0.2
                            H3.8c-0.1,0-0.2-0.1-0.2-0.2V1.8c0-0.1,0.1-0.2,0.2-0.2H9v2.8C9,5.2,9.8,6,10.8,6h2.8V14.2z"/>
                        <path d="M9.1,10.6V7.3c0-0.3-0.3-0.6-0.6-0.6S7.9,7,7.9,7.3v3.3L6.5,9.3C6.3,9,5.9,9,5.7,9.3c-0.2,0.2-0.2,0.6,0,0.8l2.4,2.4
                            c0.2,0.2,0.6,0.2,0.8,0h0l2.4-2.4c0.2-0.2,0.2-0.6,0-0.8c-0.2-0.2-0.6-0.2-0.8,0L9.1,10.6z"/>
                    `;
                    modifySvgContent(svg, newFileContent, downloadUrl, fileName);
                    modifiedFileCount++;
                }
            });
        });

        if (modifiedFileCount > 0) {
            console.log(`Modified ${modifiedFileCount} file icons with direct download functionality and hover effects.`);
        }
    }

    function runIconUpdate() {
        function debouncedUpdate() {
            updateIcons();
        }

        debouncedUpdate();

        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    debouncedUpdate();
                }
            });
        });

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

        console.log('GitHub Repo File Downloader: Script is now running.');
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', runIconUpdate);
    } else {
        runIconUpdate();
    }
})();