Musescore Free PDF Downloader

An automatic and free score downloader for musescore

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Musescore Free PDF Downloader
// @namespace    http://tampermonkey.net/
// @version      1.2
// @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


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
}

function createFloatingIframeButton() {
  if (document.getElementById('tm-fab-frame')) return;

  const iframe = document.createElement('iframe');
  iframe.id = 'tm-fab-frame';
  Object.assign(iframe.style, {
    position: 'fixed',
    bottom: '20px',
    left: '20px',
    width: '160px',
    height: '48px',
    border: '0',
    padding: '0',
    margin: '0',
    zIndex: '2147483647',
    background: 'transparent',
    pointerEvents: 'auto'
  });

  // Contenu interne de l'iframe (même origine → on peut y accéder)
  iframe.srcdoc = `
<!doctype html><html><head><meta charset="utf-8">
<style>
html,body{margin:0;padding:0;background:transparent}
#btn{
  all:unset; display:inline-block; padding:10px 14px; border-radius:9999px;
  box-shadow:0 8px 30px rgba(0,0,0,.25);
  background:#0dbc79; color:#fff; font:14px/1.2 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
  cursor:pointer; user-select:none;
}
#btn:hover{filter:brightness(1.06)}
#btn:active{transform:translateY(1px)}
</style></head><body>
<button id="btn" title="Télécharger le PDF">Télécharger PDF</button>
</body></html>`.trim();

  document.documentElement.appendChild(iframe);

  // Quand l'iframe est prête, on accroche NOTRE handler (dans le contexte userscript)
  iframe.addEventListener('load', () => {
    const doc = iframe.contentDocument;
    const btn = doc && doc.getElementById('btn');
    if (!btn) return;
    btn.addEventListener('click', download_pdf_wrapper); // ici, le site ne peut pas bloquer
    console.log('[TM] Bouton flottant (iframe) prêt.');
  });
}

async function download_pdf_wrapper(e) {
  if (e) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); }
  try {
    // On repart de zéro à chaque run
    scrolled = 0;
    urls = [];

    // Si la page n'a pas encore initialisé height/scrollContainer, on tente ici
    if (!height) {
      const firstImg = document.getElementsByClassName('KfFlO')[0];
      if (firstImg) height = parseInt(firstImg.height);
    }
    if (!scrollContainer) {
      scrollContainer = document.getElementById('jmuse-scroller-component');
    }

    await download_pdf();
  } catch (err) {
    console.error('[TM] download_pdf_wrapper error:', err);
    alert('Échec du téléchargement PDF. Regarde la console pour les détails.');
  }
}


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 () {
    console.log("SCRIPT LOADED");
    setTimeout(() => {
        console.log("Set timeout");
        // 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);
        console.log("height = " + height);
        scrollHeight = (scrollContainer - (scrollContainer % height));
        console.log("scrollHeight = " + scrollHeight);

        // installe le bouton iframe (à la fin de ton $(document).ready(...))
        createFloatingIframeButton();

        // (optionnel) réinstalle si le site détruit le nœud
        const mo = new MutationObserver(() => {
            if (!document.getElementById('tm-fab-frame')) createFloatingIframeButton();
        });
        mo.observe(document.documentElement, { childList: true, subtree: true });

        // (optionnel) menu Tampermonkey en secours
        if (typeof GM_registerMenuCommand === 'function') {
            GM_registerMenuCommand('Télécharger PDF', () => download_pdf_wrapper());
        }

        // (optionnel) raccourci clavier Ctrl+Alt+D
        window.addEventListener('keydown', (e) => {
            if ((e.ctrlKey || e.metaKey) && e.altKey && e.key.toLowerCase() === 'd') {
                e.preventDefault();
                download_pdf_wrapper();
            }
        }, true);


    }, 2000)




});