Musescore Free PDF Downloader

An automatic and free score downloader for musescore

// ==UserScript==
// @name         Musescore Free PDF Downloader
// @namespace    http://tampermonkey.net/
// @version      1.0
// @author       malatia
// @match        https://musescore.com/*/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=musescore.com
// @grant        GM_addStyle
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.16.0/pdf-lib.min.js
// @description  An automatic and free score downloader for musescore
// @license MIT
// ==/UserScript==

// Idea from MuseScore Download By flancast90

let DEBUG = true

GM_addStyle(`.glowbutton {
    --glow-color: rgb(176, 252, 255);
    --glow-spread-color: rgba(123, 251, 255, 0.781);
    --enhanced-glow-color: rgb(206, 255, 255);
    --btn-color: rgb(61, 127, 136);
    border: 0.25em solid var(--glow-color);
    padding: 1em 1em;
    color: var(--glow-color);
    font-size: 15px;
    font-weight: bold;
    background-color: var(--btn-color);
    border-radius: 1em;
    outline: none;
    box-shadow: 0 0 1em 0.25em var(--glow-color),
      0 0 4em 1em var(--glow-spread-color),
      inset 0 0 0.75em 0.25em var(--glow-color);
    text-shadow: 0 0 0.5em var(--glow-color);
    position: relative;
    transition: all 0.3s;
  }

  .glowbutton::after {
    pointer-events: none;
    content: "";
    position: absolute;
    top: 120%;
    left: 0;
    height: 100%;
    width: 100%;
    background-color: var(--glow-spread-color);
    filter: blur(2em);
    opacity: 0.7;
    transform: perspective(1.5em) rotateX(35deg) scale(1, 0.6);
  }

  .glowbutton:hover {
    color: var(--btn-color);
    background-color: var(--glow-color);
    box-shadow: 0 0 1em 0.25em var(--glow-color),
      0 0 4em 2em var(--glow-spread-color),
      inset 0 0 0.75em 0.25em var(--glow-color);
  }

  .glowbutton:active {
    box-shadow: 0 0 0.6em 0.25em var(--glow-color),
      0 0 2.5em 2em var(--glow-spread-color),
      inset 0 0 0.5em 0.25em var(--glow-color);
  }
  `)

async function img_to_canvas_to_bytes(img) {
    return new Promise((resolve, reject) => {
        // Création d'un canvas pour dessiner l'image
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');

        // Chargement de l'image dans le canvas
        img.crossOrigin = 'Anonymous'; // Permet d'accéder à l'image cross-origin
        img.onload = () => {
            canvas.width = img.width;
            canvas.height = img.height;
            ctx.drawImage(img, 0, 0);

            // Conversion du contenu du canvas en données PNG
            const pngBytes = canvas.toDataURL('image/png').split(',')[1];
            resolve(pngBytes);
        };
        img.onerror = () => {
            reject(new Error('Échec du chargement de l\'image.'));
        };
        img.src = img.src; // Déclenche le chargement de l'image
    });
}



async function fetchAndAssemblePDF(urls) {
    // Création d'un nouveau document PDF
    const pdfDoc = await PDFLib.PDFDocument.create();

    // Tableau pour stocker les documents PNG générés
    const pngDocs = [];

    // Parcours de chaque URL de fichier img
    console.log("Avant boucle urls")

    for (const url of urls) {

        console.log("url = " + url)
        // Récupération du fichier img à partir de l'URL
        const response = await fetch(url);
        console.log(response)
        const imgText = await response.text();

        if (url.includes(".svg")) {
            const img = new Image();
            const imgLoaded = new Promise((resolve, reject) => {
                img.onload = resolve;
                img.onerror = reject;
            });
            // Création d'un blob à partir du texte SVG
            const svgBlob = new Blob([imgText], { type: 'image/svg+xml' });

            // Création d'une image à partir du blob SVG
            img.src = URL.createObjectURL(svgBlob);

            // Attente du chargement complet de l'image
            try {
                // Attendre le chargement complet de l'image
                await imgLoaded;
                console.log("Image SVG chargée avec succès");
            } catch (error) {
                console.error("Erreur lors du chargement de l'image SVG:", error);
                continue; // Passe à l'itération suivante dans la boucle
            }

            // Création d'un canvas pour dessiner l'image SVG
            const canvas = document.createElement('canvas');
            canvas.width = img.width;
            canvas.height = img.height;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0);

            // Conversion du canvas en format PNG
            const pngBytes = canvas.toDataURL('image/png').split(',')[1];

            // Incorporation du PNG dans le document PDF
            const pngDoc = await pdfDoc.embedPng(pngBytes);
            pngDocs.push(pngDoc); // Ajout du document PNG au tableau
        }

        else if (url.includes(".png")) {
            console.log("Dans la partie PNG");
            const img = new Image();
            const imgLoaded = new Promise((resolve, reject) => {
                img.onload = resolve;
                img.onerror = reject;
            });

            // Chargement de l'image à partir de l'URL
            img.src = url;
            // Promesse basée sur le chargement complet de l'image
            try {
                // Attendre le chargement complet de l'image
                await imgLoaded;
                console.log("Image PNG chargée avec succès");
            } catch (error) {
                console.error("Erreur lors du chargement de l'image PNG:", error);
                continue; // Passe à l'itération suivante dans la boucle
            }

            const pngBytes = await img_to_canvas_to_bytes(img)

            // Incorporation de l'image PNG dans le document PDF
            const pngDoc = await pdfDoc.embedPng(pngBytes);
            pngDocs.push(pngDoc);
        }
    }

    // Parcours des documents PNG pour les ajouter au document PDF
    for (const pngDoc of pngDocs) {
        const page = pdfDoc.addPage([pngDoc.width, pngDoc.height]);
        page.drawImage(pngDoc, {
            x: 0,
            y: 0,
            width: pngDoc.width,
            height: pngDoc.height,
        });
    }

    // Enregistrement du document PDF
    const pdfBytes = await pdfDoc.save();

    // Téléchargement du PDF
    const blob = new Blob([pdfBytes], { type: 'application/pdf' });
    const link = document.createElement('a');
    link.href = window.URL.createObjectURL(blob);
    let title = document.title.replace(" ", "_") + ".pdf"
    link.download = title;
    link.click();
}

let scrolled = 0;
let urls = [];
let height;
let scrollHeight;
let scrollContainer



$(document).ready(async function () {
    setTimeout(() => {
        // ICI ON PARLE DU CONTENANT AVEC SCROLL
        scrollContainer = parseInt(document.getElementById('jmuse-scroller-component').scrollHeight);
        // ICI ON PARLE DES IMAGES
        height = parseInt(document.getElementsByClassName('KfFlO')[0].height);
        scrollHeight = (scrollContainer - (scrollContainer % height));
        // On cherche le bouton downlad de base pour le remplacer par le nôtre
        const btns = [...document.getElementsByTagName("button")]
        btns.filter(el => {
            const val = el.attributes.getNamedItem("name")?.value
            return val == "download"
        }).forEach(el => {
            const type = el.attributes.getNamedItem("name").value
            const fakeEl = el.cloneNode(true)
            // fakeEl.style.border = "2px #0dbc79 solid"
            fakeEl.value = "Download Free PDF"
            fakeEl.classList.add("glowbutton")
            // fakeEl.style.webkitBoxShadow = "0px 0px 127px 65px rgba(45,255,196,0.8);"
            // fakeEl.style.mozBoxShadow = "0px 0px 127px 65px rgba(45,255,196,0.8);;"
            // fakeEl.style.BoxShadow = "0px 0px 127px 65px rgba(45,255,196,0.8);"
            fakeEl.class =
                fakeEl.onclick = download_pdf
            el.parentNode.replaceChild(fakeEl, el)
        })
    }, 2000)

    // function get_lazy_imgs() {
    //     // Fonction récursive pour récupérer les images
    //     while (scrolled < scrollHeight) {
    //         // Attendre un peu avant de récupérer les images
    //         setTimeout(() => {
    //             // Faire défiler la fenêtre
    //             console.log("Scrolled : " + scrolled)
    //             console.log("scrollHeight : " + scrollHeight)
    //             scrollContainer.scrollTop = scrolled;
    //             scrolled += height;
    //             if (DEBUG) console.log("Get_lazy")
    //             // Récupérer les images
    //             let images = document.getElementsByClassName('KfFlO');
    //             if (images.length > 0) {
    //                 urls.push(images[images.length - 1].src);
    //             }
    //         }, 1000);


    //     }

    // }

    function get_lazy_imgs() {
        console.log("Début get_lazy_images");
        return new Promise((resolve, reject) => {
            // Fonction récursive pour récupérer les images
            function fetchImages() {

                scrollContainer = document.getElementById('jmuse-scroller-component');
                scrollHeight = scrollContainer.scrollHeight;

                if (scrolled < scrollHeight) {
                    // Faire défiler la fenêtre
                    scrollContainer.scrollTop = scrolled;
                    scrolled += height;

                    // Attendre un peu avant de récupérer les images
                    setTimeout(() => {
                        // Récupérer les images
                        const images = document.getElementsByClassName('KfFlO');
                        if (images.length > 0) {
                            // Ajouter les URLs des images au tableau
                            urls.push(images[images.length - 1].src);
                        }

                        // Rappel récursif jusqu'à ce que le scroll atteigne la fin
                        fetchImages();
                    }, 1000);
                } else {
                    // Résoudre la promesse avec les URLs des images
                    resolve(urls);
                    console.log("Fin get_lazy_images");
                }
            }

            // Début de la récupération des images
            fetchImages();
        });
    }

    async function download_pdf() {
        console.log("Avant get_lazy_images");
        await get_lazy_imgs(); // Attendre la récupération de toutes les URLs d'images
        console.log("Après get_lazy_images");
        console.log("Avant fetchAndAssemble");
        await fetchAndAssemblePDF(urls); // Utiliser les URLs pour générer le PDF
        console.log("Après fetchAndAssemble");
        scrolled = 0; // Réinitialiser le défilement
    }



});