Copy hash for YGG

Copy easly the hash from a torrent on YGG in one click and more...

As of 2024-05-17. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @namespace       https://greatest.deepsurf.us/fr/users/868328-invincible812
// @name            Copy hash for YGG
// @name:fr         Copieur de hash pour YGG
// @include         https://*.yggtorrent.*/*
// @include         https://yggtorrent.*/*
// @include         https://*.yggtorrent.*
// @include         https://yggtorrent.*
// @include         https://*.ygg.*/*
// @include         https://ygg.*/*
// @include         https://*.ygg.*
// @include         https://ygg.*
// @grant           GM.xmlHttpRequest
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM_deleteValue
// @grant           GM_registerMenuCommand
// @grant           GM_addStyle
// @compatible      firefox Violentmonkey
// @compatible      chrome Violentmonkey
// @compatible      brave Violentmonkey
// @compatible      opera Violentmonkey
// @version         3.2
// @author          Invincible812
// @license         Free
// @icon            https://i.ibb.co/nzW3KXp/Copy-hash.png
// @description     Copy easly the hash from a torrent on YGG in one click and more...
// @description:fr  Copier facilement le hash d'un torrent sur YGG en un clique et bien plus...
// @supportURL      https://www3.ygg.re/profile/9385666-invincible813
// @run-at          document-end
// @require         https://code.jquery.com/jquery-3.6.3.min.js
// ==/UserScript==


window.addEventListener('load', function() {
  console.log('[COPY HASH FOR YGG] Lancement du script');

  const version = `3.2`;
  const date_maj = `17/05/2024`
  const nouveauté = `<p><b><u>Nouveautés de la version ${version} :</u></b></p>
  <div class="nouveaute-list-container" style="font-size: 12x; line-height: 1.2">
    <ul style="margin-top: 10px;">
        <li>• <u>[MÀJ]</u> <b>v3.2 : Changement d'adresse + passage de YGG en privé</b>
        <br>Concernant cela, actuellement vous avez malheureusement besoin d'un compte YGG pour pouvoir continuer à utiliser YGG et également le script (qui continuera d'être maintenu aussi longtemps que possible vous inquiétez pas ;). <br><b>Faites attention aux arnaques aux comptes et autres magouilles pour avoir un compte YGG<b>.</li>
    </ul>
  </div>`;


  function showUpdateNotification() {
    const isClosed = GM_getValue(`updateNotificationClosed_v${version}`, false);
    if (isClosed) {
      return;
    }

    const isNewInstallation = !GM_getValue("scriptInstalled", false);
    GM_setValue("scriptInstalled", true);

    const notificationDiv = document.createElement("div");
    notificationDiv.innerHTML = `
    <div
    style="position: fixed; top: 30px; left: 50%; transform: translateX(-50%); background-color: #fff; padding: 20px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); z-index: 9999;">
    <div style="display: flex; align-items: center; margin-bottom: 20px;">
        <img src="https://i.ibb.co/nzW3KXp/Copy-hash.png" alt="Logo Copieur de Hash pour YGG"
            style="width: 80px; height: 80px; margin-right: 30px;">
        <p style="font-weight: bold; font-size: 17px; margin: 0;">Copieur de Hash pour YGG (Version ${version} - <i>${date_maj}</i>)</p>
    </div>
    <p style="font-weight: bold; color: green;">Nouvelle mise à jour installée !</p>
    ${isNewInstallation
                ? `<p>Bienvenue ! Merci à toi d'avoir installé mon script.</p>
    <p>Utilisez le script pour copier les hashs de torrents sur YGGTorrent et les envoyer sur Alldebrid pour un débridage facile !</p>
    <br>
    ${nouveauté}
    <br>
    <p>À bientôt, </p>
    <p><i>Le créateur du script</i></p>
    <a href="https://greatest.deepsurf.us/fr/scripts/459498-copy-hash-for-ygg/versions" target="_blank"
        style="text-decoration: none; margin-right: 10px;">
        <button id="readPatchNote"
            style="padding: 10px 20px; border: none; background-color: #4CAF50; color: white; cursor: pointer; transition: background-color 0.3s; font-weight: bold; text-decoration: underline;">Lire le Patch Note complet</button>
    </a>
    <button id="closeNotification"
        style="padding: 10px 20px; border: none; background-color: #ccc; color: #333; cursor: pointer; transition: background-color 0.3s; font-weight: bold; text-decoration: underline;">Commencer à s'amuser :\u0029</button>`
                : `<p>Rebienvenue ! Merci à toi de continuer à utiliser mon script.</p>
    <p>Tu viens d'installer la version ${version} du script.</p><br>
    ${nouveauté}
    <br>
    <p>À bientôt, </p>
    <p><i>Le créateur du script</i></p>
    <a href="https://greatest.deepsurf.us/fr/scripts/459498-copy-hash-for-ygg/versions" target="_blank"
        style="text-decoration: none; margin-right: 10px;">
        <button id="readPatchNote"
            style="padding: 10px 20px; border: none; background-color: #4CAF50; color: white; cursor: pointer; transition: background-color 0.3s; font-weight: bold; text-decoration: underline;">Lire le Patch Note complet</button>
    </a>
    <button id="closeNotification"
        style="padding: 10px 20px; border: none; background-color: #ccc; color: #333; cursor: pointer; transition: background-color 0.3s; font-weight: bold; text-decoration: underline;">Compris !</button>`}
    </div>`;

    document.body.appendChild(notificationDiv);

    const closeNotificationButton = document.getElementById("closeNotification");
    closeNotificationButton.addEventListener("click", function() {
      notificationDiv.remove();
      GM_setValue(`updateNotificationClosed_v${version}`, true);
    });

    notificationDiv.addEventListener("click", function(event) {
      if (event.target === notificationDiv) {
        notificationDiv.remove();
        GM_setValue(`updateNotificationClosed_v${version}`, true);
      }
    });

    const readPatchNoteButton = document.getElementById("readPatchNote");
    readPatchNoteButton.addEventListener("mouseover", function() {
      this.style.backgroundColor = "#45A049";
    });
    readPatchNoteButton.addEventListener("mouseout", function() {
      this.style.backgroundColor = "#4CAF50";
    });

    closeNotificationButton.addEventListener("mouseover", function() {
      this.style.backgroundColor = "#999";
      this.style.color = "#fff";
    });
    closeNotificationButton.addEventListener("mouseout", function() {
      this.style.backgroundColor = "#ccc";
      this.style.color = "#333";
    });
  }

  showUpdateNotification();

  const panelMenuList = document.getElementsByClassName('panel-menu-list')[0];

  if (panelMenuList) {
      panelMenuList.insertAdjacentHTML('beforeend', '<li><a href="#" id="menu_settings">Options Copy Hash</a></li>');
  } else if(document.getElementsByClassName('ct')[0]) {
      //console.log("La classe panel-menu-list n'existe pas dans le document.");
      document.getElementsByClassName('ct')[0].children[0].insertAdjacentHTML('beforeend', '<li><a href="#" id="menu_settings">Options Copy Hash</a></li>');
  }

  const sett = document.querySelector('#menu_settings');
    sett.addEventListener('click', function(event) {
      event.preventDefault();
      cfg.open();
    });

  let Alldebrid_API;
  const oldConfigKey = "_MonkeyConfig_Options___Script_Copieur_de_hash_pour_YGG___v1_6_cfg";
  const oldConfig = GM_getValue(oldConfigKey, null);
  const newConfigKey = "_MonkeyConfig_Options___Copieur_de_hash_pour_YGG_cfg";

  if (oldConfig !== null) {
    GM_setValue(newConfigKey, oldConfig);
    GM_deleteValue(oldConfigKey);
  }

  const newConfig = {
    Bouton_Copier_le_Hash: {
      type: 'select',
      choices: ['Oui', 'Non'],
      default: 'Oui'
    },
    Ajouter_URL_Magnet: {
      type: 'select',
      choices: ['Oui', 'Non'],
      default: 'Oui'
    },
    Bouton_Envoyer_sur_Alldebrid: {
      type: 'select',
      choices: ['Oui', 'Non'],
      default: 'Oui'
    },
    Bouton_Envoyer_Torrent: {
      type: 'select',
      choices: ['Oui', 'Non'],
      default: 'Oui'
    },
    API_Alldebrid: {
      type: 'text'
    },
  };

  const cfg = new MonkeyConfig({
    title: 'Options - Copieur de hash pour YGG',
    menuCommand: true,
    params: newConfig,
  });

  Alldebrid_API = cfg.get('API_Alldebrid');

  // --------------------------------------------------------------------------- //

  function isPremium(callback) {
    GM.xmlHttpRequest({
      method: "GET",
      url: `https://api.alldebrid.com/v4/user?agent=ScriptYGGHash&apikey=${Alldebrid_API}`,
      onload: (response) => {
        const userData = JSON.parse(response.responseText);
        const isUserPremium = userData?.data?.user?.isPremium || false;
        callback(isUserPremium);
      },
    });
  }

  //

  function deleteMagnet(magnetId) {
    GM.xmlHttpRequest({
      method: "GET",
      url: `https://api.alldebrid.com/v4/magnet/delete?agent=ScriptYGGHash&apikey=${Alldebrid_API}&id=${magnetId}`,
      onload: (response) => {
        const responseData = JSON.parse(response.responseText);
        if (responseData && responseData.status === "success") {
          //console.log("[COPY HASH FOR YGG] Le magnet a été supprimé avec succès.");
        } else {
          console.error("Erreur lors de la suppression du magnet.");
        }
      },
      onerror: () => {
        console.error("Erreur lors de la suppression du magnet.");
      }
    });
  }

  // --------------------------------------------------------------------------- //

  if (location.pathname.includes('/torrent/')) {

    document.getElementsByTagName('tbody')[0].children[0].children[1].insertAdjacentHTML('afterbegin', `\n<a style="max-width:100%;color:#555555;top:-1px;font-size:11px;font-weight:700;text-transform:uppercase;border:3px solid #555555;border-radius:25px;padding:5px 10px;transition: all 0.3s ease; cursor:pointer;"
    class="butt-settings" onmouseover="this.style.background='#999999';this.style.color='#ffffff';"
    onmouseout="this.style.background='transparent';this.style.color='#555555';">Options <span
        class="ico_gear"></span></a>`);
    const sett = document.querySelector('a.butt-settings');
    sett.addEventListener('click', function(event) {
      event.preventDefault();
      cfg.open();
    });

    let boutons1 = cfg.get('Bouton_Copier_le_Hash');
    let boutons2 = cfg.get('Bouton_Envoyer_sur_Alldebrid');
    let boutons3 = cfg.get('Bouton_Envoyer_Torrent');
    let boutons4 = cfg.get('Ajouter_URL_Magnets');
    Alldebrid_API = cfg.get('API_Alldebrid');
    boutons1 = boutons1 == "Oui" ? true : false;
    boutons2 = boutons2 == "Oui" ? true : false;
    boutons3 = boutons3 == "Oui" ? true : false;
    boutons4 = boutons4 == "Oui" ? true : false;

    let hash = boutons4 ? 'magnet:?xt=urn:btih:' + document.getElementsByClassName('informations')[0].childNodes[1].childNodes[9].childNodes[3].textContent.replace('Tester', '') : document.getElementsByClassName('informations')[0].childNodes[1].childNodes[9].childNodes[3].textContent.replace('Tester', '');

    //

    function uploadMagnet(apikey, magnetHash, callback) {
      const apiUrl = `https://api.alldebrid.com/v4/magnet/upload?agent=ScriptYGGHash&apikey=${apikey}&magnets[]=${magnetHash}`;

      GM.xmlHttpRequest({
        method: "GET",
        url: apiUrl,
        onload: (response) => {
          const responseData = JSON.parse(response.responseText);
          if (responseData && responseData.status === "success" && responseData.data && responseData.data.magnets) {
            callback(responseData.data.magnets[0]);
          } else {
            callback(null);
          }
        },
        onerror: () => {
          callback(null);
        }
      });
    }

    async function uploadTorrent(apikey, agent, downloadLink, callback) {
        // Télécharger le fichier à partir du lien de téléchargement
        const response = await fetch(downloadLink);
        if (!response.ok) {
            alert('Connectez vous à votre compte YGG.');
            return;
        }

        //console.log(response)

        const fileBlob = await response.blob();

        //console.log('fileBlob', fileBlob);
        // Créer un objet FormData et ajouter le fichier
        const formData = new FormData();
        //console.log('formData', formData);

        const file = new File([fileBlob], 'downloadedFile.torrent');
        //console.log('file', file);
        formData.append('files[0]', file);

        // Ajouter les paramètres de requête à l'URL
        const queryParams = new URLSearchParams({
            agent: agent,
            apikey: apikey,
        });

        const apiUrl = 'https://api.alldebrid.com/v4/magnet/upload/file';
        const url = `${apiUrl}?${queryParams.toString()}`;

        try {
            // Envoyer la requête POST avec le fichier
            const uploadResponse = await fetch(url, {
                method: 'POST',
                body: formData,
            });

            if (!uploadResponse.ok) {
                console.error('Failed to upload the file. Status:', uploadResponse.status);
                callback(null);
            }

            const result = await uploadResponse.json();
            //console.log('File upload successful:', result);
            if(result.data.files[0].error){
                alert('Connectez vous à votre compte YGG.');
                return
            }

            callback(true)
        } catch (error) {
            console.error('Error during file upload:', error.message);
        }
    }

    function formatSize(size) {
      if (size < 1024) {
        return size + " B";
      } else if (size < 1024 * 1024) {
        return (size / 1024).toFixed(2) + " Ko";
      } else if (size < 1024 * 1024 * 1024) {
        return (size / (1024 * 1024)).toFixed(2) + " Mo";
      } else {
        return (size / (1024 * 1024 * 1024)).toFixed(2) + " Go";
      }
    }

    if (boutons1) {
      document.getElementsByTagName('tbody')[0].children[0].children[1].insertAdjacentHTML('beforeend', `\n<a
    style="max-width:100%;color:#d9a65a;top:-1px;font-size:11px;font-weight:700;text-transform:uppercase;border:3px solid #d9a65a;border-radius:25px;padding:5px 10px;transition: all 0.3s ease; cursor:pointer;"
    onclick="this.textContent='Copié !'" class="butt-hash"
    onmouseover="this.style.background='#d9a85a';this.style.color='#ffffff';"
    onmouseout="this.style.background='transparent';this.style.color='#d9ad5a';">Copier le hash <span class="ico_copy"></span>`);
      document.getElementsByClassName('butt-hash')[0].addEventListener('click', function() {
        navigator.clipboard.writeText(hash);
      });
    }
    if (boutons2) {
      document.getElementsByTagName('tbody')[0].children[0].children[1].insertAdjacentHTML('beforeend', `\n<a
        style="max-width:100%;color:#ffae42;top:-1px;font-size:11px;font-weight:700;text-transform:uppercase;border:3px solid #ffae42;border-radius:25px;padding:5px 10px;transition: all 0.3s ease; cursor:pointer;"
        class="butt-alledebrid" onmouseover="this.style.background='#ffc658';this.style.color='#ffffff';"
        onmouseout="this.style.background='transparent';this.style.color='#ffae42';">Hash vers Alldebrid <span class="ico_link"></span></a>
    `);
      const alldebridButton = document.getElementsByClassName('butt-alledebrid')[0];
      alldebridButton.addEventListener('click', function() {
        if (boutons2) {
          isPremium((userIsPremium) => {
            if (userIsPremium) {
              uploadMagnet(Alldebrid_API, hash, (responseMagnet) => {
                //console.log(responseMagnet);
                if (responseMagnet !== null) {
                  alldebridButton.textContent = "Envoyé !";
                } else {
                  alldebridButton.textContent = "Erreur Magnet :/";
                }
              });
            } else {
              alert("L'utilisateur de cette API n'est pas premium.");
            }
          });
        }
      });
    }
    if (boutons3) {
      document.getElementsByTagName('tbody')[0].children[0].children[1].insertAdjacentHTML('beforeend', `\n<a
        style="max-width:100%;color:#ffae42;top:-1px;font-size:11px;font-weight:700;text-transform:uppercase;border:3px solid #ffae42;border-radius:25px;padding:5px 10px;transition: all 0.3s ease; cursor:pointer;"
        class="butt-torrent" onmouseover="this.style.background='#ffc658';this.style.color='#ffffff';"
        onmouseout="this.style.background='transparent';this.style.color='#ffae42';">Fichier Torrent vers Alldebrid <span class="ico_file"></span></a>
    `);
      const torrentButton = document.getElementsByClassName('butt-torrent')[0];
      const link = "https://www.ygg.re/engine/download_torrent?id="+document.getElementById('saveFav').attributes[2].value.match(/id=(\d+)/)[1];
      torrentButton.addEventListener('click', function() {
        if (boutons2) {
          isPremium((userIsPremium) => {
            if (userIsPremium) {
              uploadTorrent(Alldebrid_API, "ScriptHashYGG", link, (responseMagnet) => {
                //console.log('responseMagnet', responseMagnet);
                if (responseMagnet !== null) {
                  //console.log('ODDDD');
                  torrentButton.textContent = "Envoyé !";
                } else {
                  //console.log('GGGGGGGG');
                  torrentButton.textContent = "Erreur Torrent :/";
                }
              });
            } else {
              alert("L'utilisateur de cette API n'est pas premium.");
            }
          });
        }
      });
    }

    const hash0 = document.getElementsByClassName('informations')[0].children[0].children[4].children[1].textContent.replace('Tester', '');

    const newRow = document.createElement('tr');

    const progressBar = document.createElement('div');
    progressBar.classList.add('progress-bar'); // Ajoutez la classe de barre de progression de Bootstrap
    progressBar.setAttribute('role', 'progressbar');
    progressBar.style.width = '0%'; // Initialisez la largeur de la barre de progression à 0%
    progressBar.style.display = 'none';

    const statusCell = document.createElement('td');
    statusCell.style.verticalAlign = 'top';
    statusCell.insertAdjacentText('beforeend', 'Alldebrid');

    const progressBarStyle = document.createElement('style');
    progressBarStyle.textContent = `
        .progress-bar {
            position: relative;
            background: linear-gradient(to right, rgb(255, 237, 165) 100%, white 0px) 0% 0% / 28.2% no-repeat white;
            transition: all 1.6s linear 0s;
            display: none; /* Par défaut, la barre de progression est cachée */
            width: 0%; /* Largeur initiale à 0% */
        }
    `;
    // Ajout du style au header du document
    document.head.appendChild(progressBarStyle);

    const infoCell = document.createElement('td');
    infoCell.colSpan = 5;
    infoCell.style.position = 'relative'; // Ajoutez un style pour positionner les éléments de la barre de progression
    infoCell.classList.add('alldebrid-container');

    const statusElement = document.createElement('p');
    const downloadSpeedElement = document.createElement('p');
    const downloadedSizeElement = document.createElement('p');
    const progressElement = document.createElement('p');
    const totalSizeElement = document.createElement('p');
    const linkElement = document.createElement('p');
    const remainingTimeElement = document.createElement('p');

    statusElement.textContent = "Statut du torrent : N/A";
    downloadSpeedElement.textContent = "";
    downloadedSizeElement.textContent = "";
    progressElement.textContent = "";
    totalSizeElement.textContent = "";
    linkElement.textContent = "";
    remainingTimeElement.textContent = "";

    infoCell.appendChild(statusElement);
    infoCell.appendChild(downloadSpeedElement);
    infoCell.appendChild(progressBar);
    infoCell.appendChild(progressElement);
    infoCell.appendChild(downloadedSizeElement);
    infoCell.appendChild(totalSizeElement);
    infoCell.appendChild(remainingTimeElement);
    infoCell.appendChild(linkElement);

    newRow.appendChild(statusCell);
    newRow.appendChild(infoCell);

    const table = document.querySelector('.infos-torrent');
    table.querySelector('tbody').appendChild(newRow);

    const buttAllButton = document.getElementsByClassName('butt-alledebrid')[0];
    const torrentAll = document.getElementsByClassName('butt-torrent')[0];
    let intervalId;

    let isButtonClicked = false;

    //

    function updateProgressBar(progress) {
        const container = document.getElementsByClassName('alldebrid-container')[0];
        if (!container) return; // Sortir de la fonction si aucun conteneur n'est trouvé

        const progressBar = container.querySelector('.progress-bar');
        if (!progressBar) return; // Sortir de la fonction si aucune barre de progression n'est trouvée dans le conteneur

        if (progress === undefined) {
            // Si aucun argument n'est fourni, masquer la barre de progression
            progressBar.style.display = 'none';
        } else {
            // Sinon, mettre à jour la barre de progression avec la valeur fournie et le style donné
            //container.style.display = 'block';
            //container.style.position = 'relative';
            container.style.background = 'linear-gradient(to right, rgb(255, 237, 165) 100%, white 0px) 0% 0% / '+progress+'% no-repeat white';
            container.style.transition = 'all 1.6s linear 0s';
            //container.style.width = progress + '%';
            //container.textContent = progress + '%';
        }
    }


    function onClickHandler() {
      if (!isButtonClicked) {
        isButtonClicked = true;

        function getMagnetStatusAndUnlockLinks(magnetHash) {
          GM.xmlHttpRequest({
            method: "GET",
            url: `https://api.alldebrid.com/v4/magnet/status?agent=ScriptYGGHash&apikey=${Alldebrid_API}`,
            onload: function(response) {
              try {
                const torrentData = JSON.parse(response.responseText);
                const magnets = torrentData.data.magnets;
                const matchingMagnet = magnets.find((magnet) => magnet.hash === magnetHash);

                if (!matchingMagnet) {
                  console.error("Aucun magnet correspondant trouvé.");
                  return;
                }

                const status = matchingMagnet.status;

                const viewLinksElement = document.createElement('a');
                viewLinksElement.id = 'viewLinks';
                viewLinksElement.classList.add('noselect');
                viewLinksElement.textContent = 'Voir les liens';
                viewLinksElement.style.display = 'block';

                const linksContainer = document.createElement('div');
                linksContainer.id = 'linksContainer';
                linksContainer.style.display = 'none';

                infoCell.appendChild(viewLinksElement);
                infoCell.appendChild(linksContainer);

                if (status === "Ready") {
                  viewLinksElement.addEventListener('click', async () => {
                    linksContainer.innerHTML = '';

                    const links = matchingMagnet.links;
                    if (links && links.length > 0) {
                      const unlockedLinkPromises = [];

                      function unlockLink(linkData) {
                        const filename = linkData.filename || "Inconnu";
                        const extension = filename.split('.').pop();

                        if (![".nfo", ".NFO"].includes(`.${extension}`)) {
                          const unlockPromise = new Promise((resolve, reject) => {
                            GM.xmlHttpRequest({
                              method: "GET",
                              url: `https://api.alldebrid.com/v4/link/unlock?agent=ScriptYGGHash&apikey=${Alldebrid_API}&link=${linkData.link}`,
                              onload: function(unlockResponse) {
                                try {
                                  const unlockData = JSON.parse(unlockResponse.responseText);
                                  const unlockedLink = unlockData.data.link;

                                  if (unlockedLink) {
                                    resolve({
                                      filename,
                                      link: unlockedLink
                                    });
                                  } else {
                                    console.error(`Échec du débridage du lien: ${linkData.link}`);
                                    reject(`Échec du débridage du lien: ${linkData.link}`);
                                  }
                                } catch (error) {
                                  console.error("Une erreur s'est produite lors du débridage du lien.", error);
                                  reject(error);
                                }
                              },
                              onerror: () => {
                                console.error("Une erreur s'est produite lors du débridage du lien.");
                                reject("Une erreur s'est produite lors du débridage du lien.");
                              },
                            });
                          });

                          unlockedLinkPromises.push(unlockPromise);
                        }
                      }

                      links.forEach(unlockLink);

                      try {
                        const unlockedLinks = await Promise.all(unlockedLinkPromises);

                        unlockedLinks.forEach((link) => {
                          const unlockedLinkElement = document.createElement('a');
                          const lineBreakElement = document.createElement('br');
                          unlockedLinkElement.href = link.link;
                          unlockedLinkElement.textContent = link.filename;
                          linksContainer.appendChild(unlockedLinkElement);
                          linksContainer.appendChild(lineBreakElement);
                        });

                        linksContainer.style.maxHeight = "0px";
                        linksContainer.style.transition = "max-height 0.8s ease-in-out";
                        linksContainer.style.display = 'block';
                        linksContainer.style.maxHeight = linksContainer.scrollHeight + 18 + "px";

                        const copyAllButton = document.createElement('a');
                        copyAllButton.textContent = 'Copier tous les liens';
                        const strongElement = document.createElement('strong');
                        const underlineElement = document.createElement('u');
                        underlineElement.appendChild(copyAllButton);
                        strongElement.appendChild(underlineElement);
                        copyAllButton.addEventListener('click', () => {
                          const allLinks = unlockedLinks.map((link) => link.link).join('\n');
                          const tempTextArea = document.createElement('textarea');
                          tempTextArea.value = allLinks;
                          document.body.appendChild(tempTextArea);
                          tempTextArea.select();
                          document.execCommand('copy');
                          document.body.removeChild(tempTextArea);
                          alert('Tous les liens ont été copiés dans le presse-papiers.');
                        });
                        linksContainer.appendChild(copyAllButton);
                      } catch (error) {
                        console.error("Une erreur s'est produite lors du débridage des liens.", error);
                      }
                    } else {
                      console.error("Aucun lien trouvé pour le magnet prêt.");
                    }
                  });
                } else {
                  console.error(`Le magnet n'est pas prêt (Statut: ${status})`);
                }
              } catch (error) {
                console.error("Une erreur s'est produite lors de la récupération du statut du magnet.", error);
              }
            },
            onerror: () => {
              console.error("Une erreur s'est produite lors de la récupération du statut du magnet.");
            },
          });
        }

        function calculateRemainingTime(downloadSpeed, downloadedSize, totalSize) {
          if (downloadSpeed === "N/A" || downloadedSize === "N/A" || totalSize === "N/A" || isNaN(downloadSpeed) || downloadSpeed <= 0 || isNaN(downloadedSize) || downloadedSize <= 0 || isNaN(totalSize) || totalSize <= 0) {
            return "N/A";
          }

          const remainingSeconds = (totalSize - downloadedSize) / downloadSpeed;
          const hours = Math.floor(remainingSeconds / 3600);
          const minutes = Math.floor((remainingSeconds % 3600) / 60);
          const seconds = Math.floor(remainingSeconds % 60);

          return `${hours}h ${minutes}m ${seconds}s`;
        }

        function getMagnetStatus() {
          GM.xmlHttpRequest({
            method: "GET",
            url: `https://api.alldebrid.com/v4/magnet/status?agent=ScriptYGGHash&apikey=${Alldebrid_API}`,
            onload: (response) => {
              const torrentData = JSON.parse(response.responseText);
              const magnets = torrentData.data.magnets;
              const matchingMagnet = magnets.find((magnet) => magnet.hash === hash0);
              const nondeb = `Non débridable via le hash, veuillez attendre un peu si c'est un torrent récent, sinon faites via le fichier torrent`;

              if (matchingMagnet) {
                const torrentStatus = matchingMagnet.status || "N/A";
                statusElement.textContent = `Statut du torrent : ${torrentStatus}`;

                if (torrentStatus === "In Queue") {
                  if (matchingMagnet.filename == "noname") {
                    statusElement.textContent = `Statut du torrent : ${nondeb}`;
                  } else {
                    statusElement.textContent = `Statut du torrent : En attente`;
                  }
                } else if (torrentStatus === "Uploading") {
                  if (matchingMagnet.filename == "noname") {
                    statusElement.textContent = `Statut du torrent : ${nondeb}`;
                  } else {
                    statusElement.textContent = `Statut du torrent : Upload en cours`;
                    const uploadedSpeed = matchingMagnet.uploadSpeed ? `${formatSize(matchingMagnet.uploadSpeed)} /s` : "N/A";
                    const uploadedSize = matchingMagnet.uploaded ? formatSize(matchingMagnet.uploaded) : "N/A";
                    const totalSize = matchingMagnet.size ? formatSize(matchingMagnet.size) : "N/A";
                    const progress = uploadedSize !== "N/A" && totalSize !== "N/A" ? ((matchingMagnet.uploaded / matchingMagnet.size) *
                      100).toFixed(2) + "%" : "N/A";
                    const remainingTime = calculateRemainingTime(matchingMagnet.uploadSpeed, matchingMagnet.uploaded, matchingMagnet.size);
                    downloadSpeedElement.textContent = `Vitesse d'upload : ${uploadedSpeed}`;
                    progressElement.textContent = `Progression : ${progress}`;
                    updateProgressBar(progress.replace('%',''));
                    downloadedSizeElement.textContent = `Taille de l'upload : ${uploadedSize}`;
                    totalSizeElement.textContent = `Taille totale : ${totalSize}`;
                    remainingTimeElement.textContent = `Temps restant : ${remainingTime}`;
                  }
                } else if (torrentStatus === "Downloading") {
                  if (matchingMagnet.filename == "noname") {
                    statusElement.textContent = `Statut du torrent : ${nondeb}`;
                  } else {
                    statusElement.textContent = `Statut du torrent : Téléchargement en cours`;
                    const downloadSpeed = matchingMagnet.downloadSpeed ? `${formatSize(matchingMagnet.downloadSpeed)} /s` : "N/A";
                    const downloadedSize = matchingMagnet.downloaded ? formatSize(matchingMagnet.downloaded) : "N/A";
                    const totalSize = matchingMagnet.size ? formatSize(matchingMagnet.size) : "N/A";
                    const progress = downloadedSize !== "N/A" && totalSize !== "N/A" ? ((matchingMagnet.downloaded /
                      matchingMagnet.size) * 100).toFixed(2) + "%" : "N/A";
                    const remainingTime = calculateRemainingTime(matchingMagnet.downloadSpeed, matchingMagnet.downloaded, matchingMagnet.size);

                    downloadSpeedElement.textContent = `Vitesse de téléchargement : ${downloadSpeed}`;
                    updateProgressBar(progress.replace('%',''));
                    progressElement.textContent = `Progression : ${progress}`;
                    downloadedSizeElement.textContent = `Taille du téléchargement : ${downloadedSize}`;
                    totalSizeElement.textContent = `Taille totale : ${totalSize}`;
                    remainingTimeElement.textContent = `Temps restant : ${remainingTime}`;
                  }
                } else if (torrentStatus === "Ready") {
                  getMagnetStatusAndUnlockLinks(matchingMagnet.hash);
                  downloadSpeedElement.textContent = ``;
                  updateProgressBar();
                  downloadedSizeElement.textContent = ``;
                  remainingTimeElement.textContent = "";
                  statusElement.textContent = `Statut du torrent : Prêt`;
                  const totalSize = matchingMagnet.size ? formatSize(matchingMagnet.size) : "N/A";
                  totalSizeElement.textContent = `Taille totale : ${totalSize}`;
                  linkElement.innerHTML = ``;
                } else if (torrentStatus === "Proccessing failed (bad torrent ?)") {
                  if (matchingMagnet.filename == "noname") {
                    statusElement.textContent = `Statut du torrent : ${nondeb}`;
                  } else {
                    statusElement.textContent = `Débridage du torrent échoué`;
                  }
                } else {
                  downloadSpeedElement.textContent = "";
                  updateProgressBar();
                  downloadedSizeElement.textContent = "";
                  totalSizeElement.textContent = "";
                  linkElement.textContent = "";
                }
                if (matchingMagnet.status == "Ready" || matchingMagnet.filename == "noname") {
                  clearInterval(intervalId);
                }
                if (matchingMagnet.filename == "noname") {
                  deleteMagnet(matchingMagnet.id);
                }
              }
            },
            onerror: () => {
              console.error("Erreur de requête à l'API Alldebrid.");
            }
          });
        }

        if (buttAllButton.getAttribute('data-clicked') !== 'true') {
          buttAllButton.setAttribute('data-clicked', 'true');
          intervalId = setInterval(getMagnetStatus, 1000);
        }
      }
    };

    buttAllButton.addEventListener('click', onClickHandler);
    torrentAll.addEventListener('click', onClickHandler);


    function doesMagnetExist(magnetHash) {
        return new Promise((resolve, reject) => {
            GM.xmlHttpRequest({
                  method: "GET",
                  url: `https://api.alldebrid.com/v4/magnet/status?agent=ScriptYGGHash&apikey=${Alldebrid_API}`,
                  onload: (statusResponse) => {
                    try {
                      const torrentData = JSON.parse(statusResponse.responseText);
                      const magnets = torrentData.data.magnets;
                      const matchingMagnet = magnets.find((magnet) => magnet.hash === magnetHash);

                      if (matchingMagnet) {
                        const magnetExists = false;
                        resolve(magnetExists);
                      }else{
                        const magnetExists = true;
                        resolve(magnetExists);
                      }
                    } catch (error) {
                        reject(error);
                    }
                },
                onerror: (error) => {
                    reject(error);
                }
            });
        });
    }

    function isTorrentDebridable(magnetHash) {
      const nondeb = `Non débridable via le hash, veuillez attendre un peu si c'est un torrent récent, sinon faites via le fichier torrent`;
      return new Promise((resolve, reject) => {
        statusElement.textContent = "Statut du torrent : Vérification du torrent en cours";

        const intervalId = setInterval(() => {
          statusElement.textContent += ".";
        }, 1000);

        let deleteTheMagnet;

        doesMagnetExist(magnetHash)
          .then((exists) => {
            if (exists) {
              deleteTheMagnet=true;
            } else {
              deleteTheMagnet=false;
            }
          })
          .catch((error) => {
            console.error("Une erreur s'est produite lors de la vérification du magnet:", error);
          });

        GM.xmlHttpRequest({
          method: "GET",
          url: `https://api.alldebrid.com/v4/magnet/upload?agent=ScriptYGGHash&apikey=${Alldebrid_API}&magnets[]=${magnetHash}`,
          onload: (uploadResponse) => {
            clearInterval(intervalId);
            try {
              const uploadData = JSON.parse(uploadResponse.responseText);
              const magnetId = uploadData?.data?.magnets[0]?.id || null;

              if (!magnetId) {
                resolve({
                  debriable: false,
                  status: "Upload failed"
                });
              } else {
                GM.xmlHttpRequest({
                  method: "GET",
                  url: `https://api.alldebrid.com/v4/magnet/status?agent=ScriptYGGHash&apikey=${Alldebrid_API}`,
                  onload: (statusResponse) => {
                    try {
                      const torrentData = JSON.parse(statusResponse.responseText);
                      const magnets = torrentData.data.magnets;
                      const matchingMagnet = magnets.find((magnet) => magnet.id === magnetId);

                      if (matchingMagnet) {
                        const torrentStatus = matchingMagnet.status || "N/A";
                        //console.log(matchingMagnet);

                        if (
                          (torrentStatus === "In Queue" && matchingMagnet.filename === "noname") ||
                          (torrentStatus === "Uploading" && matchingMagnet.filename === "noname") ||
                          (torrentStatus === "Downloading" && matchingMagnet.filename === "noname") ||
                          (torrentStatus === "N/A" && matchingMagnet.filename === "noname")
                        ) {
                          resolve({
                            debriable: false,
                            status: nondeb
                          });
                          deleteMagnet(magnetId);
                        } else {
                          resolve({
                            debriable: true,
                            status: `Débridable`
                          });
                          deleteTheMagnet === true ? deleteMagnet(magnetId) : '';
                        }
                      } else {
                        resolve({
                          debriable: false,
                          status: "N/A"
                        });
                      }
                    } catch (error) {
                      resolve({
                        debriable: false,
                        status: "N/A"
                      });
                    }
                  },
                  onerror: () => {
                    resolve({
                      debriable: false,
                      status: "N/A"
                    });
                  },
                });
              }
            } catch (error) {
              resolve({
                debriable: false,
                status: "Upload failed"
              });
            }
          },
          onerror: () => {
            clearInterval(intervalId);
            resolve({
              debriable: false,
              status: "Upload failed"
            });
          },
        });
      });
    }

    isTorrentDebridable(hash)
      .then((result) => {
        if (result.debriable) {
          statusElement.innerHTML = `Statut du torrent : <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512" style="vertical-align: middle; margin-right: 5px;"><path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>Torrent débridable via hash.`;
        } else {
          statusElement.innerHTML = `Statut du torrent : <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 384 512" style="vertical-align: middle; margin-right: 5px;"><path d="M376.6 84.5c11.3-13.6 9.5-33.8-4.1-45.1s-33.8-9.5-45.1 4.1L192 206 56.6 43.5C45.3 29.9 25.1 28.1 11.5 39.4S-3.9 70.9 7.4 84.5L150.3 256 7.4 427.5c-11.3 13.6-9.5 33.8 4.1 45.1s33.8 9.5 45.1-4.1L192 306 327.4 468.5c11.3 13.6 31.5 15.4 45.1 4.1s15.4-31.5 4.1-45.1L233.7 256 376.6 84.5z"/></svg>${result.status}`;
        }

      })
      .catch((error) => {
        statusElement.textContent = "Une erreur s'est produite lors de la vérification du torrent.";
        console.error("Une erreur s'est produite lors de la vérification du torrent.", error);
      });

  }

  function copyTorrentHash(event) {
    event.preventDefault();

    const torrentLink = this.getAttribute('href');

    GM.xmlHttpRequest({
      method: "GET",
      url: torrentLink,
      onload: function(response) {
        //console.log(response.responseText);
        const parser = new DOMParser();
        const temp = parser.parseFromString(response.responseText, "text/html");
        const hash = temp.querySelector('#informationsContainer > div > table > tbody > tr:nth-child(5) > td:nth-child(2)').textContent.replace('Tester', '').trim();

        const dummyElement = document.createElement('textarea');
        dummyElement.value = hash;
        document.body.appendChild(dummyElement);
        dummyElement.select();
        document.execCommand('copy');
        document.body.removeChild(dummyElement);
      }
    });
    this.textContent = 'Copié !';
  }

  async function sendHashtoAllederid(event) {
    event.preventDefault();

    function getHash(torrentLink) {
      return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({
          method: "GET",
          url: torrentLink,
          onload: function(response) {
            const parser = new DOMParser();
            const temp = parser.parseFromString(response.responseText, "text/html");
            const hash_ygg = temp.querySelector('#informationsContainer > div > table > tbody > tr:nth-child(5) > td:nth-child(2)').textContent.replace('Tester', '').trim();
            resolve(hash_ygg);
          },
          onerror: function(error) {
            reject(error);
          }
        });
      });
    }

    function isTorrentDebridable(magnetHash, event) {
      const nondeb = `Non débridable`;
      return new Promise((resolve, reject) => {

        GM.xmlHttpRequest({
          method: "GET",
          url: `https://api.alldebrid.com/v4/magnet/upload?agent=ScriptYGGHash&apikey=${Alldebrid_API}&magnets[]=${magnetHash}`,
          onload: (uploadResponse) => {
            clearInterval(intervalId);
            try {
              const uploadData = JSON.parse(uploadResponse.responseText);
              const magnetId = uploadData?.data?.magnets[0]?.id || null;

              if (!magnetId) {
                resolve({
                  debriable: false,
                  status: "Upload failed"
                });
              } else {
                GM.xmlHttpRequest({
                  method: "GET",
                  url: `https://api.alldebrid.com/v4/magnet/status?agent=ScriptYGGHash&apikey=${Alldebrid_API}`,
                  onload: (statusResponse) => {
                    try {
                      const torrentData = JSON.parse(statusResponse.responseText);
                      const magnets = torrentData.data.magnets;
                      const matchingMagnet = magnets.find((magnet) => magnet.id === magnetId);

                      if (matchingMagnet) {
                        const torrentStatus = matchingMagnet.status || "N/A";

                        console.log(matchingMagnet);

                        if (
                          (torrentStatus === "In Queue" && matchingMagnet.filename === "noname" || matchingMagnet.filename === matchingMagnet.hash) ||
                          (torrentStatus === "Uploading" && matchingMagnet.filename === "noname" || matchingMagnet.filename === matchingMagnet.hash) ||
                          (torrentStatus === "Downloading" && matchingMagnet.filename === "noname" || matchingMagnet.filename === matchingMagnet.hash) ||
                          (torrentStatus === "N/A" && matchingMagnet.filename === "noname" || matchingMagnet.filename === matchingMagnet.hash)
                        ) {
                          resolve({
                            debriable: false,
                            status: nondeb
                          });
                        } else {
                          resolve({
                            debriable: true,
                            status: `Débridable`
                          });
                        }
                      } else {
                        resolve({
                          debriable: false,
                          status: "N/A"
                        });
                      }
                    } catch (error) {
                      resolve({
                        debriable: false,
                        status: "N/A"
                      });
                    }
                  },
                  onerror: () => {
                    resolve({
                      debriable: false,
                      status: "N/A"
                    });
                  },
                });
              }
            } catch (error) {
              resolve({
                debriable: false,
                status: "Upload failed"
              });
            }
          },
          onerror: () => {
            resolve({
              debriable: false,
              status: "Upload failed"
            });
          },
        });
      });
    }

    const torrentLink = this.getAttribute('href');
    console.log(torrentLink)
    let intervalId;
    if (Alldebrid_API != "") {
      isPremium((userIsPremium) => {
        if (userIsPremium) {
          getHash(torrentLink)
            .then((hashh) => {
              this.textContent = "Vérification en cours";
              intervalId = setInterval(() => {
                this.textContent += ".";
              }, 1000);
              isTorrentDebridable(hashh)
                .then((result) => {
                  clearInterval(intervalId);
                  if (result.debriable) {
                    this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512" style="vertical-align: middle; margin-right: 5px;"><path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>${result.status}`;
                  } else {
                    this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 384 512" style="vertical-align: middle; margin-right: 5px;"><path d="M376.6 84.5c11.3-13.6 9.5-33.8-4.1-45.1s-33.8-9.5-45.1 4.1L192 206 56.6 43.5C45.3 29.9 25.1 28.1 11.5 39.4S-3.9 70.9 7.4 84.5L150.3 256 7.4 427.5c-11.3 13.6-9.5 33.8 4.1 45.1s33.8 9.5 45.1-4.1L192 306 327.4 468.5c11.3 13.6 31.5 15.4 45.1 4.1s15.4-31.5 4.1-45.1L233.7 256 376.6 84.5z"/></svg>Non débridable`;
                  }
                })
                .catch((error) => {
                  clearInterval(intervalId);
                  this.textContent = "Erreur";
                  console.error("Une erreur s'est produite lors de la vérification du torrent.", error);
                });
            })
            .catch((error) => {
              console.error('Une erreur s\'est produite :', error);
            });
        } else {
          alert("L'utilisateur de cette API n'est pas premium.");
        }
      });
    } else {
      this.textContent = 'API non renseignée';
      cfg.open();
    }
  }

  if (location.pathname.includes('/engine/search') || location.pathname.includes('/top') || location.pathname.includes('/engine/mostseededs')) {
    for (let i = 0; i < 30; i++) {
      const functionName = `start_hash_${i}`;
      window[functionName] = function() {
        const tableId = `DataTables_Table_${i}`;
        const table = document.getElementById(tableId);
        if (table) {
          const torrentLinks = table.querySelectorAll('tbody tr td:nth-child(2) a');
          torrentLinks.forEach(function(link) {
            const copyButton = document.createElement('a');
            copyButton.setAttribute('href', link.href);
            const imgElement0 = document.createElement('img');
            imgElement0.setAttribute('src', 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT6YglfyfbNmrFWRZSkwUegLc_YRW6zWDGR1D6uPCuxSYHEUvRzPYpIQy27E2BREn22C5I&usqp=CAU');
            imgElement0.setAttribute('alt', 'Copy Hash');
            imgElement0.style.width = '20px';
            imgElement0.style.height = '20px';
            copyButton.appendChild(imgElement0);
            copyButton.style.marginLeft = '10px';
            copyButton.addEventListener('click', copyTorrentHash);
            link.parentNode.appendChild(copyButton);

           const sendButton = document.createElement('a');
           sendButton.setAttribute('href', link.href);
           const imgElement = document.createElement('img');
           imgElement.setAttribute('src', 'https://cdn-icons-png.flaticon.com/512/561/561226.png');
           imgElement.setAttribute('alt', 'Send Alldebrid');
           imgElement.style.width = '20px';
           imgElement.style.height = '20px';
           sendButton.appendChild(imgElement);
           sendButton.style.marginLeft = '10px';
           sendButton.addEventListener('click', sendHashtoAllederid);
           link.parentNode.appendChild(sendButton);
          });
        };
      }
      setTimeout(window[functionName], 1000);
    }
  } else if(location.pathname.includes('/torrents/exclus')) {
      function exclus() {
        const tableId = `table`;
        const table = document.getElementsByClassName(tableId)[0];
        if (table) {
          const torrentLinks = table.querySelectorAll('tbody tr td:nth-child(2) a');
          torrentLinks.forEach(function(link) {
            const copyButton = document.createElement('a');
            copyButton.setAttribute('href', link.href);
            const imgElement0 = document.createElement('img');
            imgElement0.setAttribute('src', 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT6YglfyfbNmrFWRZSkwUegLc_YRW6zWDGR1D6uPCuxSYHEUvRzPYpIQy27E2BREn22C5I&usqp=CAU');
            imgElement0.setAttribute('alt', 'Copy Hash');
            imgElement0.style.width = '20px';
            imgElement0.style.height = '20px';
            copyButton.appendChild(imgElement0);
            copyButton.style.marginLeft = '10px';
            copyButton.addEventListener('click', copyTorrentHash);
            link.parentNode.appendChild(copyButton);

           const sendButton = document.createElement('a');
           sendButton.setAttribute('href', link.href);
           const imgElement = document.createElement('img');
           imgElement.setAttribute('src', 'https://cdn-icons-png.flaticon.com/512/561/561226.png');
           imgElement.setAttribute('alt', 'Send Alldebrid');
           imgElement.style.width = '20px';
           imgElement.style.height = '20px';
           sendButton.appendChild(imgElement);
           sendButton.style.marginLeft = '10px';
           sendButton.addEventListener('click', sendHashtoAllederid);
           link.parentNode.appendChild(sendButton);
          });
        };
      }
      setTimeout(exclus, 1000);
  };

});




// POUR OPTIONS
function MonkeyConfig() {
    var cfg = this,
        /* Data object passed to the constructor */
        data,
        /* Configuration parameters (data.parameters or data.params) */
        params,
        /* Current values of configuration parameters */
        values = {},
        /* Identifier used to store/retrieve configuration */
        storageKey,
        /* Is the configuration dialog displayed? */
        displayed,
        /* Currently displayed window/layer */
        openWin, openLayer,
        /* DOM element wrapping the configuration form */
        container,
        /* Darkened overlay used in the layer display mode */
        overlay;

    /**
     * Initialize configuration
     *
     * @param newData New data object
     */
    function init(newData) {
        data = newData;

        if (data) {
            params = data.parameters || data.params;

            if (data.buttons === undefined)
                /* Set default buttons */
                data.buttons = [ 'save', 'defaults', 'cancel' ];

            if (data.title === undefined)
                /*
                 * If GM_getMetadata is available, get the name of the script
                 * and use it in the dialog title
                 */
                if (typeof GM_getMetadata == 'function') {
                    var scriptName = GM_getMetadata('name');
                    data.title = scriptName + ' Configuration';
                }
                else
                    data.title = 'Configuration';
        }

        /* Make a safe version of title to be used as stored value identifier */
        var safeTitle = data && data.title ?
                data.title.replace(/[^a-zA-Z0-9]/g, '_') : '';

        storageKey = '_MonkeyConfig_' + safeTitle + '_cfg';

        var storedValues;

        /* Load stored values (if present) */
        if (GM_getValue(storageKey))
            storedValues = JSON.parse(GM_getValue(storageKey));

        for (var name in params) {
            /* If there's a value defined in the passed data object, use it */
            if (params[name]['value'] !== undefined)
                set(name, params[name].value);
            /* Check if there's a stored value for this parameter */
            else if (storedValues && storedValues[name] !== undefined)
                set(name, storedValues[name]);
            /* Otherwise, set the default value (if defined) */
            else if (params[name]['default'] !== undefined)
                set(name, params[name]['default']);
            else
                set(name, '');
        }

        if (data.menuCommand) {
            /* Add an item to the User Script Commands menu */
            var caption = data.menuCommand !== true ? data.menuCommand :
                data.title;

            GM_registerMenuCommand(caption, function () { cfg.open(); });
        }

        /* Expose public methods */
        cfg.open = open;
        cfg.close = close;
        cfg.get = get;
        cfg.set = function (name, value) {
            set(name, value);
            update();
        };
    }

    /**
     * Get the value of a configuration parameter
     *
     * @param name Name of the configuration parameter
     * @returns Value of the configuration parameter
     */
    function get(name) {
        return values[name];
    }

    /**
     * Set the value of a configuration parameter
     *
     * @param name Name of the configuration parameter
     * @param value New value of the configuration parameter
     */
    function set(name, value) {
        values[name] = value;
    }

    /**
     * Reset configuration parameters to default values
     */
    function setDefaults() {
        for (var name in params) {
            if (typeof params[name]['default'] !== 'undefined') {
                set(name, params[name]['default']);
            }
        }
    }

    /**
     * Render the configuration dialog
     */
    function render() {
        var html = '<div class="__MonkeyConfig_container">' +
            '<h1>' + data.title + '</h1>' +
            '<table>';

        for (var name in params) {
            html += MonkeyConfig.formatters['tr'](name, params[name]);}

        html += '<tr><td colspan="2" class="__MonkeyConfig_buttons">' +
                '<table><tr>';

        /* Render buttons */
        for (var button in data.buttons) {
            html += '<td>';

            switch (data.buttons[button]) {
            case 'cancel':
                html += '<button type="button" ' +
                    'id="__MonkeyConfig_button_cancel">' +
                    '<img src="data:image/png;base64,' +
                        MonkeyConfig.res.icons.cancel + '" />&nbsp;' +
                    'Annuler</button>';
                break;
            case 'defaults':
                html += '<button type="button" ' +
                    'id="__MonkeyConfig_button_defaults">' +
                    '<img src="data:image/png;base64,' +
                        MonkeyConfig.res.icons.arrow_undo + '" />&nbsp;' +
                    'Par&nbsp;Défaut</button>';
                break;
            case 'save':
                html += '<button type="button" ' +
                    'id="__MonkeyConfig_button_save">' +
                    '<img src="data:image/png;base64,' +
                        MonkeyConfig.res.icons.tick + '" />&nbsp;' +
                    'Sauvegarder</button>';
                break;
            }

            html += '</td>';
        }

        html += '</tr></table></td></tr>';

        html += "</table><div>";

        return html;
    }

    /**
     * Update the fields in the dialog to reflect current values
     */
    function update() {
        /* Do nothing if the dialog is not currently displayed */
        if (!displayed)
            return;

        for (var name in params) {
            var value = values[name];

            switch (params[name].type) {
            case 'checkbox':
                var elem = container.querySelector('[name="' + name + '"]');
                elem.checked = !!value;
                break;
            case 'custom':
                params[name].set(value, container
                        .querySelector('#__MonkeyConfig_parent_' + name));
                break;
            case 'number': case 'text':
                var elem = container.querySelector('[name="' + name + '"]');
                elem.value = value;
                break;
            case 'select':
                var elem = container.querySelector('[name="' + name + '"]');

                if (elem.tagName.toLowerCase() == 'input') {
                    if (elem.type && elem.type == 'radio') {
                        /* Single selection with radio buttons */
                        elem = container.querySelector(
                            '[name="' + name + '"][value="' + value + '"]');
                        elem.checked = true;
                    }
                    else if (elem.type && elem.type == 'checkbox') {
                        /* Multiple selection with checkboxes */
                        var checkboxes = container.querySelectorAll(
                            'input[name="' + name + '"]');

                        for (var i = 0; i < checkboxes.length; i++)
                            checkboxes[i].checked =
                                (value.indexOf(checkboxes[i].value) > -1);
                    }
                }
                else if (elem.tagName.toLowerCase() == 'select')
                    if (elem.multiple) {
                        /* Multiple selection element */
                        var options = container.querySelectorAll(
                            'select[name="' + name + '"] option');

                        for (var i = 0; i < options.length; i++)
                            options[i].selected =
                                (value.indexOf(options[i].value) > -1);
                    }
                    else
                        /* Single selection element */
                        elem.value = value;
                break;
            }
        }
    }

    /**
     * Save button click event handler
     */
    function saveClick() {
        for (name in params) {
            switch (params[name].type) {
            case 'checkbox':
                var elem = container.querySelector('[name="' + name + '"]');
                values[name] = elem.checked;
                break;
            case 'custom':
                values[name] = params[name].get(container
                        .querySelector('#__MonkeyConfig_parent_' + name));
                break;
            case 'number': case 'text':
                var elem = container.querySelector('[name="' + name + '"]');
                values[name] = elem.value;
                break;
            case 'select':
                var elem = container.querySelector('[name="' + name + '"]');

                if (elem.tagName.toLowerCase() == 'input') {
                    if (elem.type && elem.type == 'radio')
                        /* Single selection with radio buttons */
                        values[name] = container.querySelector(
                            '[name="' + name + '"]:checked').value;
                    else if (elem.type && elem.type == 'checkbox') {
                        /* Multiple selection with checkboxes */
                        values[name] = [];
                        var inputs = container.querySelectorAll(
                            'input[name="' + name + '"]');

                        for (var i = 0; i < inputs.length; i++)
                            if (inputs[i].checked)
                                values[name].push(inputs[i].value);
                    }
                }
                else if (elem.tagName.toLowerCase() == 'select' && elem.multiple) {
                    /* Multiple selection element */
                    values[name] = [];
                    var options = container.querySelectorAll(
                        'select[name="' + name + '"] option');

                    for (var i = 0; i < options.length; i++)
                        if (options[i].selected)
                            values[name].push(options[i].value);
                }
                else
                    values[name] = elem.value;
                break;
            }
        }

        GM_setValue(storageKey, JSON.stringify(values));

        close();

        location.reload(true);

        if (data.onSave)
            data.onSave(values);
    }

    /**
     * Cancel button click event handler
     */
    function cancelClick() {
        close();
    }

    /**
     * Set Defaults button click event handler
     */
    function defaultsClick() {
        setDefaults();
        update();
    }

    /**
     * Open configuration dialog
     *
     * @param mode
     *            Display mode ("iframe", "layer", or "window", defaults to
     *            "iframe")
     * @param options
     *            Display mode options
     */
    function open(mode, options) {
        function openDone() {
            /* Attach button event handlers */
            var button;

            if (button = container.querySelector('#__MonkeyConfig_button_save'))
                button.addEventListener('click', saveClick, true);
            if (button = container.querySelector('#__MonkeyConfig_button_cancel'))
                button.addEventListener('click', cancelClick, true);
            if (button = container.querySelector('#__MonkeyConfig_button_defaults'))
                button.addEventListener('click', defaultsClick, true);

            displayed = true;
            update();
        }

        switch (mode) {
        case 'window':
            var windowFeatures = {
                location: 'no',
                status: 'no',
                left: window.screenX,
                top: window.screenY,
                width: 100,
                height: 100
            };

            /* Additional features may be specified as an option */
            if (options && options.windowFeatures)
                for (var name in options.windowFeatures)
                    windowFeatures[name] = options.windowFeatures[name];

            var featuresArray = [];

            for (var name in windowFeatures)
                featuresArray.push(name + '=' + windowFeatures[name]);

            var win = window.open('', data.title, featuresArray.join(','));

            /* Find head and body (then call the blood spatter analyst) */
            var head = win.document.getElementsByTagName('head')[0],
                body = win.document.getElementsByTagName('body')[0];

            head.innerHTML = '<title>' + data.title + '</title>' +
                '<style type="text/css">' +
                MonkeyConfig.res.stylesheets.main + '</style>';

            body.className = '__MonkeyConfig_window';
            /* Place the rendered configuration dialog inside the window body */
            body.innerHTML = render();

            /* Find the container (CBAN-3489) */
            container = win.document.querySelector('.__MonkeyConfig_container');

            /* Resize window to the dimensions of the container div */
            win.innerWidth = container.clientWidth;
            win.resizeBy(0, -win.innerHeight + container.clientHeight);

            /* Place the window centered relative to the parent */
            win.moveBy(Math.round((window.outerWidth - win.outerWidth) / 2),
                Math.round((window.outerHeight - win.outerHeight) / 2));

            openWin = win;

            openDone();

            break;
        case 'layer':
            if (!MonkeyConfig.styleAdded) {
                GM_addStyle(MonkeyConfig.res.stylesheets.main);
                MonkeyConfig.styleAdded = true;
            }

            var body = document.querySelector('body');

            /* Create the layer element */
            openLayer = document.createElement('div');
            openLayer.className = '__MonkeyConfig_layer';

            /* Create the overlay */
            overlay = document.createElement('div');
            overlay.className = '__MonkeyConfig_overlay';
            overlay.style.left = 0;
            overlay.style.top = 0;
            overlay.style.width = window.innerWidth + 'px';
            overlay.style.height = window.innerHeight + 'px';
            overlay.style.zIndex = 9999;

            body.appendChild(overlay);
            body.appendChild(openLayer);

            /*
             * Place the rendered configuration dialog inside the layer element
             */
            openLayer.innerHTML = render();

            /* Position the layer in the center of the viewport */
            openLayer.style.left = Math.round((window.innerWidth -
                    openLayer.clientWidth) / 2) + 'px';
            openLayer.style.top = Math.round((window.innerHeight -
                    openLayer.clientHeight) / 2) + 'px';
            openLayer.style.zIndex = 9999;

            container = document.querySelector('.__MonkeyConfig_container');

            openDone();

            break;
        case 'iframe':
        default:
            if (!MonkeyConfig.styleAdded) {
                GM_addStyle(MonkeyConfig.res.stylesheets.main);
                MonkeyConfig.styleAdded = true;
            }

            var body = document.querySelector('body');
            var iframe = document.createElement('iframe');

            /* Create the layer element */
            openLayer = document.createElement('div');
            openLayer.className = '__MonkeyConfig_layer';

            /* Create the overlay */
            overlay = document.createElement('div');
            overlay.className = '__MonkeyConfig_overlay';
            overlay.style.left = 0;
            overlay.style.top = 0;
            overlay.style.width = window.innerWidth + 'px';
            overlay.style.height = window.innerHeight + 'px';
            overlay.style.zIndex = 9999;

            iframe.id = '__MonkeyConfig_frame';
            /*
             * Make the iframe transparent so that it remains invisible until
             * the document inside it is ready
             */
            iframe.style.opacity = 0;
            iframe.src = 'about:blank';

            /* Make the iframe seamless with no border and no scrollbars */
            if (undefined !== iframe.frameborder)
                iframe.frameBorder = '0';
            if (undefined !== iframe.scrolling)
                iframe.scrolling = 'no';
            if (undefined !== iframe.seamless)
                iframe.seamless = true;

            /* Do the rest in the load event handler */
            iframe.addEventListener('load', function () {
                iframe.contentDocument.body.innerHTML = render();
                iframe.style.opacity = 1;

                /* Append the style to the head */
                var head = iframe.contentDocument.querySelector('head'),
                    style = iframe.contentDocument.createElement('style');
                style.setAttribute('type', 'text/css');
                style.appendChild(iframe.contentDocument.createTextNode(
                        MonkeyConfig.res.stylesheets.main));
                head.appendChild(style);

                var body = iframe.contentDocument.querySelector('body');
                body.className = '__MonkeyConfig_body';

                container = iframe.contentDocument
                    .querySelector('.__MonkeyConfig_container');

                iframe.width = container.clientWidth;
                iframe.height = container.clientHeight;

                /* Position the layer in the center of the viewport */
                openLayer.style.left = Math.round((window.innerWidth -
                        openLayer.clientWidth) / 2.45) + 'px';
                openLayer.style.top = Math.round((window.innerHeight -
                        openLayer.clientHeight) / 2) + 'px';
                openLayer.style.zIndex = 9999;

                openDone();
            }, false);

            setTimeout(function () {
                iframe.width = container.clientWidth;
                iframe.height = container.clientHeight;

                /* Position the layer in the center of the viewport */
                openLayer.style.left = Math.round((window.innerWidth -
                        openLayer.clientWidth) / 2) + 'px';
                openLayer.style.top = Math.round((window.innerHeight -
                        openLayer.clientHeight) / 2) + 'px';
                openLayer.style.zIndex = 9999;
            }, 0);

            body.appendChild(overlay);
            body.appendChild(openLayer);
            openLayer.appendChild(iframe);

            break;
        }
    }

    /**
     * Close configuration dialog
     */
    function close() {
        if (openWin) {
            openWin.close();
            openWin = undefined;
        }
        else if (openLayer) {
            openLayer.parentNode.removeChild(openLayer);
            openLayer = undefined;

            if (overlay) {
                overlay.parentNode.removeChild(overlay);
                overlay = undefined;
            }
        }

        displayed = false;
    }

    init(arguments[0]);
}

/**
 * Replace double quotes with entities so that the string can be safely used
 * in a HTML attribute
 *
 * @param string A string
 * @returns String with double quotes replaced with entities
 */
MonkeyConfig.esc = function (string) {
    return string.replace(/"/g, '&quot;');
};

MonkeyConfig.HTML = {
    '_field': function (name, options, data) {
        var html;

        if (options.type && MonkeyConfig.HTML[options.type])
            html = MonkeyConfig.HTML[options.type](name, options, data);
        else
            return;

        if (/\[FIELD\]/.test(options.html)) {
            html = options.html.replace(/\[FIELD\]/, html);
        }

        return html;
    },
    '_label': function (name, options, data) {
        var label = options['label'] ||
            name.substring(0, 1).toUpperCase() + name.substring(1)
                .replace(/_/g, '&nbsp;')
                .replace(/é/g, "\u002F")
                .replace(/0/g, "...")
                .replace(/1/g, "'");

        return '<label for="__MonkeyConfig_field_' + name + '">' + label +
          '</label>';
    },
    'checkbox': function (name, options, data) {
        return '<input id="__MonkeyConfig_field_' + name +
            '" type="checkbox" name="' + name + '" />';
    },
    'custom': function (name, options, data) {
        return options.html;
    },
    'number': function (name, options, data) {
        return '<input id="__MonkeyConfig_field_' + name + '" ' +
            'type="text" class="__MonkeyConfig_field_number" ' +
            'name="' + name + '" />';
    },
    'select': function (name, options, data) {
        var choices = {}, html = '';

        if (options.choices.constructor == Array) {
            /* options.choices is an array -- build key/value pairs */
            for (var i = 0; i < options.choices.length; i++)
                choices[options.choices[i]] = options.choices[i];
        }
        else
            /* options.choices is an object -- use it as it is */
            choices = options.choices;

        if (!options.multiple) {
            /* Single selection */
            if (!/^radio/.test(options.variant)) {
                /* Select element */
                html += '<select id="__MonkeyConfig_field_' + name + '" ' +
                    'class="__MonkeyConfig_field_select" ' +
                    'name="' + name + '">';

                for (var value in choices)
                    html += '<option value="' + MonkeyConfig.esc(value) + '">' +
                        choices[value] + '</option>';

                html += '</select>';
            }
            else {
                /* Radio buttons */
                for (var value in choices) {
                    html += '<label><input type="radio" name="' + name + '" ' +
                        'value="' + MonkeyConfig.esc(value) + '" />&nbsp;' +
                        choices[value] + '</label>' +
                        (/ column/.test(options.variant) ? '<br />' : '');
                }
            }
        }
        else {
            /* Multiple selection */
            if (!/^checkbox/.test(options.variant)) {
                /* Checkboxes */
                html += '<select id="__MonkeyConfig_field_' + name + '" ' +
                    'class="__MonkeyConfig_field_select" ' +
                    'multiple="multiple" ' +
                    'name="' + name + '">';

                for (var value in choices)
                    html += '<option value="' + MonkeyConfig.esc(value) + '">' +
                        choices[value] + '</option>';

                html += '</select>';
            }
            else {
                /* Select element */
                for (var value in choices) {
                    html += '<label><input type="checkbox" ' +
                        'name="' + name + '" ' +
                        'value="' + MonkeyConfig.esc(value) + '" />&nbsp;' +
                        choices[value] + '</label>' +
                        (/ column/.test(options.variant) ? '<br />' : '');
                }
            }
        }

        return html;
    },
    'text': function (name, options, data) {
        if (options.long)
            return '<textarea id="__MonkeyConfig_field_' + name + '" ' +
                'class="__MonkeyConfig_field_text" ' +
                (!isNaN(options.long) ? 'rows="' + options.long + '" ' : '') +
                'name="' + name + '"></textarea>';
        else
            return '<input id="__MonkeyConfig_field_' + name + '" ' +
                'type="text" class="__MonkeyConfig_field_text" ' +
                'name="' + name + '" />';
    }

};

MonkeyConfig.formatters = {
    'tr': function (name, options, data) {
        var html = '<tr>';

        switch (options.type) {
        case 'checkbox':
            /* Checkboxes get special treatment */
            html += '<td id="__MonkeyConfig_parent_' + name + '" colspan="2">';
            html += MonkeyConfig.HTML['_field'](name, options, data) + ' ';
            html += MonkeyConfig.HTML['_label'](name, options, data);
            html += '</td>';
            break;
        default:
            html += '<td>';
            html += MonkeyConfig.HTML['_label'](name, options, data);
            html += '</td><td id="__MonkeyConfig_parent_' + name + '">';
            html += MonkeyConfig.HTML['_field'](name, options, data);
            html += '</td>';
            break;
        }

        html += '</tr>';

        return html;
    }
};

/* Has the stylesheet been added? */
MonkeyConfig.styleAdded = false;

/* Resources */
MonkeyConfig.res = {};

/* Icons */
MonkeyConfig.res.icons = {
    'arrow_undo': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0\
U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIJSURBVDjLpVM9aJNRFD35GsRSoUKKzQ/B\
0NJJF3EQlKrVgijSCBmC4NBFKihIcXBwEZdSHVoUwUInFUEkQ1DQ4CKiFsQsTrb5xNpgaZHw2Uog\
5t5zn0NJNFaw0guX97hwzuPcc17IOYfNlIdNVrhxufR6xJkZjAbSQGXjNAorqixSWFDV3KPhJ+UG\
LtSQMPryrDscPwLnAHOEOQc6gkbUpIagGmApWIb/pZRX4fjj889nWiSQtgYyBZ1BTUEj6AjPa0P7\
1nb0Jfqwa+futIheHrzRn2yRQCUK/lOQhApBJVQJChHfnkCqOwWEQ+iORJHckUyX5ksvAEyGNuJC\
+s6xCRXNHNxzKMmQ4luwgjfvZp69uvr2+IZcyJ8rjIporrxURggetnV0QET3rrPxzMNM2+n7p678\
jUTrCiWhphAjVHR9DlR0WkSzf4IHxg5MSF0zXZEuVKWKSlCBCostS8zeG7oV64wPqxInbw86lbVX\
KEQ8mkAqmUJ4SxieeVhcnANFC02C7N2h69HO2IXeWC8MDj2JnqaFNAMd8f3HKjx6+LxQRmnOz1OZ\
axKIaF1VISYwB9ARZoQaYY6o1WpYCVYxt+zDn/XzVBv/MOWXW5J44ubRyVgkelFpmF/4BJVfOVDl\
VyqLVBZI5manPjajDOdcswfG9k/3X9v3/vfZv7rFBanriIo++J/f+BMT+YWS6hXl7QAAAABJRU5E\
rkJggg==',
    'cancel': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0\
U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHdSURBVDjLpZNraxpBFIb3a0ggISmmNISW\
XmOboKihxpgUNGWNSpvaS6RpKL3Ry//Mh1wgf6PElaCyzq67O09nVjdVlJbSDy8Lw77PmfecMwZg\
/I/GDw3DCo8HCkZl/RlgGA0e3Yfv7+DbAfLrW+SXOvLTG+SHV/gPbuMZRnsyIDL/OASziMxkkKkU\
QTJJsLaGn8/iHz6nd+8mQv87Ahg2H9Th/BxZqxEkEgSrq/iVCvLsDK9awtvfxb2zjD2ARID+lVVl\
babTgWYTv1rFL5fBUtHbbeTJCb3EQ3ovCnRC6xAgzJtOE+ztheYIEkqbFaS3vY2zuIj77AmtYYDu\
sPy8/zuvunJkDKXM7tYWTiyGWFjAqeQnAD6+7ueNx/FLpRGAru7mcoj5ebqzszil7DggeF/DX1nB\
N82rzPqrzbRayIsLhJqMPT2N83Sdy2GApwFqRN7jFPL0tF+10cDd3MTZ2AjNUkGCoyO6y9cRxfQo\
wFUbpufr1ct4ZoHg+Dg067zduTmEbq4yi/UkYidDe+kaTcP4ObJIajksPd/eyx3c+N2rvPbMDPbU\
FPZSLKzcGjKPrbJaDsu+dQO3msfZzeGY2TCvKGYQhdSYeeJjUt21dIcjXQ7U7Kv599f4j/oF55W4\
g/2e3b8AAAAASUVORK5CYII=',
    'tick': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0\
U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLvZPZLkNhFIV75zjvYm7VGFNC\
qoZUJ+roKUUpjRuqp61Wq0NKDMelGGqOxBSUIBKXWtWGZxAvobr8lWjChRgSF//dv9be+9trCwAI\
/vIE/26gXmviW5bqnb8yUK028qZjPfoPWEj4Ku5HBspgAz941IXZeze8N1bottSo8BTZviVWrEh5\
46EO03EXpuJOdG63otJbjBKHkEp/Ml6yNYYzpuezWL4s5VMtT8acCMQcb5XL3eJE8VgBlR7BeMGW\
9Z4yT9y1CeyucuhdTGDxfftaBO7G4L+zg91UocxVmCiy51NpiP3n2treUPujL8xhOjYOzZYsQWAN\
yRYlU4Y9Br6oHd5bDh0bCpSOixJiWx71YY09J5pM/WEbzFcDmHvwwBu2wnikg+lEj4mwBe5bC5h1\
OUqcwpdC60dxegRmR06TyjCF9G9z+qM2uCJmuMJmaNZaUrCSIi6X+jJIBBYtW5Cge7cd7sgoHDfD\
aAvKQGAlRZYc6ltJlMxX03UzlaRlBdQrzSCwksLRbOpHUSb7pcsnxCCwngvM2Rm/ugUCi84fycr4\
l2t8Bb6iqTxSCgNIAAAAAElFTkSuQmCC'
};

/* Stylesheets */
MonkeyConfig.res.stylesheets = {
    'main': '\
body.__MonkeyConfig_window {\
    appearance: window !important;\
    -moz-appearance: window !important;\
    background: auto;\
    font-family: sans-serif !important;\
    height: 100% !important;\
    margin: 0 !important;\
    padding: 0 !important;\
    width: 100% !important;\
}\
\
div.__MonkeyConfig_container {\
    display: table !important;\
    font-family: sans-serif !important;\
    padding: 0.3em !important;\
}\
\
body.__MonkeyConfig_window div.__MonkeyConfig_container {\
    appearance: window !important;\
    -moz-appearance: window !important;\
    height: 100%;\
    width: 100%;\
}\
\
div.__MonkeyConfig_container h1 {\
    border-bottom: solid 1px #999 !important;\
    font-family: sans-serif !important;\
    font-size: 120% !important;\
    margin: 0 !important;\
    padding: 0 0 0.3em 0 !important;\
}\
\
div.__MonkeyConfig_container table {\
    border-spacing: 0 !important;\
    margin: 0 !important;\
}\
\
div.__MonkeyConfig_container table td {\
    border: none !important;\
    line-height: 100% !important;\
    padding: 0.3em !important;\
    text-align: left !important;\
    vertical-align: top !important;\
    white-space: nowrap !important;\
}\
\
div.__MonkeyConfig_container table td.__MonkeyConfig_buttons {\
    padding: 0.2em 0 !important;\
}\
\
.__MonkeyConfig_field_number {\
    width: 5em !important;\
}\
\
div.__MonkeyConfig_container td.__MonkeyConfig_buttons table {\
    border-top: solid 1px #999 !important;\
    width: 100% !important;\
}\
\
div.__MonkeyConfig_container td.__MonkeyConfig_buttons td {\
    padding: 0.6em 0.3em 0.1em 0.3em !important;\
    text-align: center !important;\
    vertical-align: top;\
}\
\
div.__MonkeyConfig_container td.__MonkeyConfig_buttons button {\
    appearance: button !important;\
    -moz-appearance: button !important;\
    background-position: 8px 50% !important;\
    background-repeat: no-repeat !important;\
    padding: 3px 8px 3px 24px !important;\
    padding: 3px 8px !important;\
    white-space: nowrap !important;\
}\
\
div.__MonkeyConfig_container td.__MonkeyConfig_buttons button img {\
    vertical-align: middle !important;\
}\
\
div.__MonkeyConfig_layer {\
    display: table !important;\
    position: fixed !important;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container,\
body.__MonkeyConfig_body > div.__MonkeyConfig_container {\
    background: #eee linear-gradient(180deg,\
        #f8f8f8 0, #ddd 100%) !important;\
    border-radius: 0.5em !important;\
    box-shadow: 2px 2px 16px #000 !important;\
    color: #000 !important;\
    font-family: sans-serif !important;\
    font-size: 11pt !important;\
    padding: 1em 1em 0.4em 1em !important;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container td,\
div.__MonkeyConfig_layer div.__MonkeyConfig_container label,\
div.__MonkeyConfig_layer div.__MonkeyConfig_container input,\
div.__MonkeyConfig_layer div.__MonkeyConfig_container select,\
div.__MonkeyConfig_layer div.__MonkeyConfig_container textarea,\
div.__MonkeyConfig_layer div.__MonkeyConfig_container button {\
    color: #000 !important;\
    font-family: sans-serif !important;\
    font-size: 11pt !important;\
    line-height: 100% !important;\
    margin: 0 !important;\
    vertical-align: baseline !important;\
}\
\
div.__MonkeyConfig_container label {\
    line-height: 120% !important;\
    vertical-align: baseline !important;\
}\
\
div.__MonkeyConfig_container textarea {\
    vertical-align: text-top !important;\
    width: 100%;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container input[type="text"] {\
    appearance: textfield !important;\
    -moz-appearance: textfield !important;\
    background: #fff !important;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container h1 {\
    font-weight: bold !important;\
    text-align: left !important;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container td.__MonkeyConfig_buttons button,\
body > div.__MonkeyConfig_container td.__MonkeyConfig_buttons button {\
    appearance: button !important;\
    -moz-appearance: button !important;\
    background: #ccc linear-gradient(180deg,\
        #ddd 0, #ccc 45%, #bbb 50%, #aaa 100%) !important;\
    border-style: solid !important;\
    border-width: 1px !important;\
    border-radius: 0.5em !important;\
    box-shadow: 0 0 1px #000 !important;\
    color: #000 !important;\
    font-size: 11pt !important;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container td.__MonkeyConfig_buttons button:hover,\
body > div.__MonkeyConfig_container td.__MonkeyConfig_buttons button:hover {\
    background: #d2d2d2 linear-gradient(180deg,\
        #e2e2e2 0, #d2d2d2 45%, #c2c2c2 50%, #b2b2b2 100%) !important;\
}\
\
div.__MonkeyConfig_overlay {\
    background-color: #000 !important;\
    opacity: 0.6 !important;\
    position: fixed !important;\
}\
\
iframe#__MonkeyConfig_frame {\
    border: none !important;\
    box-shadow: 2px 2px 16px #000 !important;\
}\
\
body.__MonkeyConfig_body {\
    margin: 0 !important;\
    padding: 0 !important;\
}\
\
embed, iframe, object {\
    max-width: 200% !important;\
    width: 167% !important;\
}\
'
};