// ==UserScript==
// @name SWDD - Steam Workshop Description Downloader
// @namespace https://criskkky.carrd.co/
// @version 1.0.3
// @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.
// @description:ro Adaugă butoane pentru a descărca descrierile Workshop-ului Steam în format .MD și .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/SWDD/stable/static/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==
/*
I RECOMMEND TAKE A LOOK TO SUPPORTED SYNTAX CONVERSIONS HERE:
https://github.com/criskkky/SWDD?tab=readme-ov-file#supported-conversions
ANYWAYS, YOU CAN HELP ME TO IMPROVE THIS SCRIPT
BY DOING A PULL REQUEST OR OPENING AN ISSUE ON GITHUB :D
https://github.com/criskkky/SWDD
AVOID USING OFUSCATED CODE OR LIBS, PLEASE,
OR YOUR PULL REQUEST 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 class="bb_link_host">([\\s\\S]*?)<\/span>': '',
'<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
var mdReplacements = {
// Essential
'<br>': '\n',
'<span class="bb_link_host">([\\s\\S]*?)<\/span>': '',
'<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/SWDD/stable/static/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/SWDD/adb175e273c563b3ffad4e81e7afe7f76449fd04/static/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/SWDD/adb175e273c563b3ffad4e81e7afe7f76449fd04/static/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();
});