SWDD - Steam Workshop Description Downloader

Añade botones para descargar descripciones de la Workshop de Steam en formato .MD y .BBCode.

Versión del día 31/12/2023. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==

// @name                SWDD - Steam Workshop Description Downloader
// @namespace           https://criskkky.carrd.co/
// @version             1.0.0
// @description         Adds buttons to download Steam Workshop descriptions in .MD and .BBCode format.
// @description:en      Adds buttons to download Steam Workshop descriptions in .MD and .BBCode format.
// @description:es      Añade botones para descargar descripciones de la Workshop de Steam en formato .MD y .BBCode.
// @description:pt      Adiciona botões para baixar descrições da Workshop do Steam em formato .MD e .BBCode.
// @description:fr      Ajoute des boutons pour télécharger les descriptions de la Workshop Steam au format .MD et .BBCode.
// @description:it      Aggiunge pulsanti per scaricare le descrizioni della Workshop di Steam in formato .MD e .BBCode.
// @description:uk      Додає кнопки для завантаження описів Workshop Steam у форматі .MD та .BBCode.
// @description:ru      Добавляет кнопки для загрузки описаний Workshop Steam в формате .MD и .BBCode.

// @author              https://criskkky.carrd.co/
// @supportURL          https://github.com/criskkky/SWDD/issues
// @homepageURL         https://github.com/criskkky/SWDD/
// @icon                https://raw.githubusercontent.com/criskkky/criskkky.github.io/main/media/icons/swdd.png
// @copyright           https://github.com/criskkky/SWDD/tree/stable?tab=readme-ov-file#-license
// @license             https://github.com/criskkky/SWDD/tree/stable?tab=readme-ov-file#-license

// @grant               none
// @match               https://steamcommunity.com/sharedfiles/filedetails/*
// ==/UserScript==

/* <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!>
BEFORE USING THIS SCRIPT, YOU MUST KNOW THAT:
  ✗ Unsupported:
  -> Tables
  -> Noparse

  ⌾ May be unaccurate:
  -> Blockquotes

ANYWAYS, YOU CAN HELP ME IMPROVE THIS SCRIPT BY DOING A PULL REQUEST
OR OPENING AN ISSUE ON GITHUB (https://github.com/criskkky/SWDD) :D
AVOID USING OFUSCATED CODE, PLEASE, OR YOUR PR WILL BE REJECTED. THANKS!
*/

// Function to download content as a file
function downloadContent(content, fileName) {
  var blob = new Blob([content], { type: 'text/plain' });
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = fileName;
  link.click();
}

function getDescription() {
  var descriptionElement = document.querySelector('.workshopItemDescription');
  if (descriptionElement) {
    // Get the HTML content of the description
    var descriptionHTML = descriptionElement.innerHTML;

    return descriptionHTML;
  }
  return null;
}

function getHTMLtoBBC(descriptionHTML) {
  // Custom replacements for Steam HTML to BBCode conversion
  var bbReplacements = {
    // Essential
    '<br>': '\n',
    '<span>([\\s\\S]*?)<\/span>': '$1',
    // Headers
    '<div class="bb_h1">([^<]+)<\/div>': '[h1]$1[/h1]\n',
    '<div class="bb_h2">([^<]+)<\/div>': '[h2]$1[/h2]\n',
    '<div class="bb_h3">([^<]+)<\/div>': '[h3]$1[/h3]\n',
    // Font styling
    '<b>([\\s\\S]*?)<\/b>': '[b]$1[/b]',
    '<u>([\\s\\S]*?)<\/u>': '[u]$1[/u]',
    '<i>([\\s\\S]*?)<\/i>': '[i]$1[/i]',
    // Font formatting
    '<span class="bb_strike">([\\s\\S]*?)<\/span>': '[strike]$1[/strike]',
    '<span class="bb_spoiler">([\\s\\S]*?)<\/span>': '[spoiler]$1[/spoiler]',
    '<a[^>]*class="bb_link"[^>]*href="([^"]+)"(?:[^>]*target="([^"]+)")?(?:[^>]*rel="([^"]+)")?[^>]*>([\\s\\S]*?)<\/a>': '[url=$1]$4[/url]',
    // Lists
    '<ul class="bb_ul">([\\s\\S]*?)<\/ul>': '\n[list]\n$1\n[/list]',
    '<li>([\\s\\S]*?)<\/li>': '[*]$1',
    '<ol>([\\s\\S]*?)<\/ol>': '\n[olist]\n$1\n[/olist]',
    // +Font formatting
    '<div class="bb_code">([\\s\\S]*?)<\/div>': '\n[code]\n$1[/code]\n',
    // TODO: Fix noparse. It's not working properly. Do PR if you can fix it.
    // Tables
    // TODO: Fix bb_table. It's not working properly. Do PR if you can fix it.
    '<div class="bb_table_tr">([\\s\\S]*?)<\/div>': '\n[tr]\n$1\n[/tr]\n',
    '<div class="bb_table_th">([\\s\\S]*?)<\/div>': '[th]$1[/th]',
    '<div class="bb_table_td">([\\s\\S]*?)<\/div>': '[td]$1[/td]',
    // Images
    '<img src="([^"]+)"[^>]*>': '[img]$1[/img]',
    // Others
    '<hr>': '[hr]',
    '<blockquote class="bb_blockquote">([\\s\\S]*?)</blockquote>' : '[quote]$1[/quote]',
  };

  // Apply custom replacements
  for (var pattern in bbReplacements) {
    var regex = new RegExp(pattern, 'gi');
    descriptionHTML = descriptionHTML.replace(regex, bbReplacements[pattern]);
  }

  // Clear unsupported tags
  descriptionHTML = descriptionHTML.replace(/<(?!\/?(h1|h2|h3|b|u|i|strike|spoiler|ul|li|ol|code|tr|th|td|img|hr|blockquote|\/blockquote))[^>]+>/g, '');

  var bbcodeContent = descriptionHTML;

  return bbcodeContent;
}

function getHTMLtoMD(descriptionHTML) {
  // Custom replacements for Steam HTML to Markdown conversion
  // Didn't used turndown because it doesn't support some tags
  var mdReplacements = {
    // Essential
    '<br>': '\n',
    '<span>([\\s\\S]*?)<\/span>': '$1',
    // Headers
    '<div class="bb_h1">([\\s\\S]*?)<\/div>': '# $1\n',
    '<div class="bb_h2">([\\s\\S]*?)<\/div>': '## $1\n',
    '<div class="bb_h3">([\\s\\S]*?)<\/div>': '### $1\n',
    // Font styling
    '<b>([\\s\\S]*?)<\/b>': '**$1**',
    '<u>([\\s\\S]*?)<\/u>': '__$1__',
    '<i>([\\s\\S]*?)<\/i>': '*$1*',
    // Font formatting
    '<span class="bb_strike">([\\s\\S]*?)<\/span>': '~~$1~~',
    '<span class="bb_spoiler">([\\s\\S]*?)<\/span>': '<details><summary>Spoiler</summary>$1</details>',
    '<a[^>]*class="bb_link"[^>]*href="([^"]+)"(?:[^>]*target="([^"]+)")?(?:[^>]*rel="([^"]+)")?[^>]*>([\\s\\S]*?)<\/a>': '[$4]($1)',
    // Lists
    '<li>([\\s\\S]*?)<\/li>': '* $1',
    '<ol>([\\s\\S]*?)<\/ol>': (match, p1, offset, string) => {
      const lines = p1.trim().split('\n');
      let currentIndex = 1;
    
      const formattedLines = lines.map((line, index) => {
        // Check if the current line starts a new ordered list
        const isNewList = line.trim().startsWith('<li>');
    
        // If it's a new list, reset currentIndex to 1
        if (isNewList) {
          currentIndex = 1;
        }
    
        // Replace asterisks (*) or hyphens (-) with incremental numbers
        return line.replace(/^\s*[\*\-]/, () => {
          const updatedNumber = currentIndex++;
          return `${updatedNumber}.`;
        });
      });
    
      return `<ol>\n${formattedLines.join('\n')}\n</ol>`;
    },        
    // +Font formatting
    '<div class="bb_code">([\\s\\S]*?)<\/div>': '\n```\n$1\n```\n',
    // TODO: Fix noparse. It's not working properly. Do PR if you can fix it.
    // Tables
    // TODO: Fix bb_table. It's not working properly. Do PR if you can fix it.
    // TODO: Fix bb_table_tr. It's not working properly. Do PR if you can fix it.
    // TODO: Fix bb_table_th. It's not working properly. Do PR if you can fix it.
    // TODO: Fix bb_table_td. It's not working properly. Do PR if you can fix it.
    // Images
    '<img src="([^"]+)"[^>]*>': '![image]($1)',
    // Others
    '<hr>': '---',
    '<blockquote class="bb_blockquote">([\\s\\S]*?)</blockquote>' : '> $1',
  };

  // Apply custom replacements
  for (var pattern in mdReplacements) {
    var regex = new RegExp(pattern, 'gi');
    descriptionHTML = descriptionHTML.replace(regex, mdReplacements[pattern]);
  }

  // Clear unsupported tags except for <details><summary>Spoiler</summary>$1</details>
  descriptionHTML = descriptionHTML.replace(/<(?!details><summary>Spoiler<\/summary>\$1<\/details>)[^>]+>/g, '');

  var markdownContent = descriptionHTML;

  return markdownContent;
}

function insertButton(downloadButton) {
  var fixedMargin = document.querySelector('.game_area_purchase_margin');
  if (fixedMargin) {
    // Better alignment ...
    fixedMargin.style.marginBottom = 'auto';
    // Find the element after which the button should be inserted
    var targetElement = document.querySelector('.workshopItemDescription');
    if (targetElement) {
      // Insert the button after the target element
      targetElement.parentNode.insertBefore(downloadButton, targetElement.nextSibling);
    }
  }
}
// Create go to repo button
function createGoToRepoButton() {
  var goToRepoButton = document.createElement('a');
  goToRepoButton.innerHTML = '<img src="https://raw.githubusercontent.com/criskkky/criskkky.github.io/main/media/icons/github_line.png" style="vertical-align: middle; margin-right: 5px; margin-left: -4px; max-width: 20px; max-height: 20px;">Repository';
  goToRepoButton.classList.add('btn_darkblue_white_innerfade', 'btn_border_2px', 'btn_medium');
  goToRepoButton.style.marginBottom = '5px';
  goToRepoButton.style.marginRight = '5px';
  goToRepoButton.style.padding = '5px 10px';
  goToRepoButton.style.height = '21px';
  goToRepoButton.style.fontSize = '14px';
  goToRepoButton.href = 'https://github.com/criskkky/SWDD';
  goToRepoButton.target = '_blank';

  insertButton(goToRepoButton);
}

// Create the download button for Markdown
function createDownloadButtonMD() {
  var downloadButton = document.createElement('button');
  downloadButton.innerHTML = '<img src="https://raw.githubusercontent.com/criskkky/criskkky.github.io/main/media/icons/cloud-download-white.svg" style="vertical-align: middle; margin-right: 5px; margin-left: -6px; max-width: 20px; max-height: 20px;">Download .MD';
  downloadButton.classList.add('btn_green_white_innerfade', 'btn_border_2px', 'btn_medium');
  downloadButton.style.marginBottom = '5px';
  downloadButton.style.marginRight = '5px';
  downloadButton.style.padding = '5px 10px';
  downloadButton.style.height = '34.43px';
  downloadButton.style.fontSize = '14px';
  downloadButton.addEventListener('click', function () {
    var markdownContent = getHTMLtoMD(getDescription());
    if (markdownContent) {
      downloadContent(markdownContent, 'WorkshopDownload.md');
    } else {
      alert('No content found in Markdown format.');
    }
  });

  insertButton(downloadButton);
}

// Create the download button for BBCode
function createDownloadButtonBBC() {
  var downloadButton = document.createElement('button');
  downloadButton.innerHTML = '<img src="https://raw.githubusercontent.com/criskkky/criskkky.github.io/main/media/icons/cloud-download-white.svg" style="vertical-align: middle; margin-right: 5px; margin-left: -6px; max-width: 20px; max-height: 20px;">Download .BBCode';
  downloadButton.classList.add('btn_green_white_innerfade', 'btn_border_2px', 'btn_medium');
  downloadButton.style.marginBottom = '5px';
  downloadButton.style.marginRight = '5px';
  downloadButton.style.padding = '5px 10px';
  downloadButton.style.height = '34.43px';
  downloadButton.style.fontSize = '14px';
  downloadButton.addEventListener('click', function () {
    var bbcodeContent = getHTMLtoBBC(getDescription());
    if (bbcodeContent) {
      downloadContent(bbcodeContent, 'WorkshopDownload.bbcode');
    } else {
      alert('No content found in BBCode format.');
    }
  });

  insertButton(downloadButton);
}

// Execute the functions when the page loads
window.addEventListener('load', function () {
  createGoToRepoButton();
  createDownloadButtonMD();
  createDownloadButtonBBC();
});