SWDD - Steam Workshop Description Downloader

Adds buttons to download Steam Workshop descriptions in .MD and .BBCode format.

  1. // ==UserScript==
  2.  
  3. // @name SWDD - Steam Workshop Description Downloader
  4. // @namespace https://criskkky.carrd.co/
  5. // @version 1.0.3
  6. // @description Adds buttons to download Steam Workshop descriptions in .MD and .BBCode format.
  7. // @description:en Adds buttons to download Steam Workshop descriptions in .MD and .BBCode format.
  8. // @description:es Añade botones para descargar descripciones de la Workshop de Steam en formato .MD y .BBCode.
  9. // @description:pt Adiciona botões para baixar descrições da Workshop do Steam em formato .MD e .BBCode.
  10. // @description:fr Ajoute des boutons pour télécharger les descriptions de la Workshop Steam au format .MD et .BBCode.
  11. // @description:it Aggiunge pulsanti per scaricare le descrizioni della Workshop di Steam in formato .MD e .BBCode.
  12. // @description:uk Додає кнопки для завантаження описів Workshop Steam у форматі .MD та .BBCode.
  13. // @description:ru Добавляет кнопки для загрузки описаний Workshop Steam в формате .MD и .BBCode.
  14. // @description:ro Adaugă butoane pentru a descărca descrierile Workshop-ului Steam în format .MD și .BBCode.
  15.  
  16. // @author https://criskkky.carrd.co/
  17. // @supportURL https://github.com/criskkky/SWDD/issues
  18. // @homepageURL https://github.com/criskkky/SWDD/
  19. // @icon https://raw.githubusercontent.com/criskkky/SWDD/stable/static/icons/swdd.png
  20. // @copyright https://github.com/criskkky/SWDD/tree/stable?tab=readme-ov-file#license
  21. // @license https://github.com/criskkky/SWDD/tree/stable?tab=readme-ov-file#license
  22.  
  23. // @grant none
  24. // @match https://steamcommunity.com/sharedfiles/filedetails/*
  25. // ==/UserScript==
  26.  
  27. /*
  28. I RECOMMEND TAKE A LOOK TO SUPPORTED SYNTAX CONVERSIONS HERE:
  29. https://github.com/criskkky/SWDD?tab=readme-ov-file#supported-conversions
  30.  
  31. ANYWAYS, YOU CAN HELP ME TO IMPROVE THIS SCRIPT
  32. BY DOING A PULL REQUEST OR OPENING AN ISSUE ON GITHUB :D
  33. https://github.com/criskkky/SWDD
  34.  
  35. AVOID USING OFUSCATED CODE OR LIBS, PLEASE,
  36. OR YOUR PULL REQUEST WILL BE REJECTED. THANKS!
  37. */
  38.  
  39. // Function to download content as a file
  40. function downloadContent(content, fileName) {
  41. var blob = new Blob([content], { type: 'text/plain' });
  42. var link = document.createElement('a');
  43. link.href = window.URL.createObjectURL(blob);
  44. link.download = fileName;
  45. link.click();
  46. }
  47.  
  48. function getDescription() {
  49. var descriptionElement = document.querySelector('.workshopItemDescription');
  50. if (descriptionElement) {
  51. // Get the HTML content of the description
  52. var descriptionHTML = descriptionElement.innerHTML;
  53.  
  54. return descriptionHTML;
  55. }
  56. return null;
  57. }
  58.  
  59. function getHTMLtoBBC(descriptionHTML) {
  60. // Custom replacements for Steam HTML to BBCode conversion
  61. var bbReplacements = {
  62. // Essential
  63. '<br>': '\n',
  64. '<span class="bb_link_host">([\\s\\S]*?)<\/span>': '',
  65. '<span>([\\s\\S]*?)<\/span>': '$1',
  66. // Headers
  67. '<div class="bb_h1">([^<]+)<\/div>': '[h1]$1[/h1]\n',
  68. '<div class="bb_h2">([^<]+)<\/div>': '[h2]$1[/h2]\n',
  69. '<div class="bb_h3">([^<]+)<\/div>': '[h3]$1[/h3]\n',
  70. // Font styling
  71. '<b>([\\s\\S]*?)<\/b>': '[b]$1[/b]',
  72. '<u>([\\s\\S]*?)<\/u>': '[u]$1[/u]',
  73. '<i>([\\s\\S]*?)<\/i>': '[i]$1[/i]',
  74. // Font formatting
  75. '<span class="bb_strike">([\\s\\S]*?)<\/span>': '[strike]$1[/strike]',
  76. '<span class="bb_spoiler">([\\s\\S]*?)<\/span>': '[spoiler]$1[/spoiler]',
  77. '<a[^>]*class="bb_link"[^>]*href="([^"]+)"(?:[^>]*target="([^"]+)")?(?:[^>]*rel="([^"]+)")?[^>]*>([\\s\\S]*?)<\/a>': '[url=$1]$4[/url]',
  78. // Lists
  79. '<ul class="bb_ul">([\\s\\S]*?)<\/ul>': '\n[list]\n$1\n[/list]',
  80. '<li>([\\s\\S]*?)<\/li>': '[*]$1',
  81. '<ol>([\\s\\S]*?)<\/ol>': '\n[olist]\n$1\n[/olist]',
  82. // Font formatting
  83. '<div class="bb_code">([\\s\\S]*?)<\/div>': '\n[code]\n$1[/code]\n',
  84. // TODO: Fix noparse. It's not working properly. Do PR if you can fix it.
  85. // Tables
  86. // TODO: Fix bb_table. It's not working properly. Do PR if you can fix it.
  87. '<div class="bb_table_tr">([\\s\\S]*?)<\/div>': '\n[tr]\n$1\n[/tr]\n',
  88. '<div class="bb_table_th">([\\s\\S]*?)<\/div>': '[th]$1[/th]',
  89. '<div class="bb_table_td">([\\s\\S]*?)<\/div>': '[td]$1[/td]',
  90. // Images
  91. '<img src="([^"]+)"[^>]*>': '[img]$1[/img]',
  92. // Others
  93. '<hr>': '[hr]',
  94. '<blockquote class="bb_blockquote">([\\s\\S]*?)</blockquote>' : '[quote]$1[/quote]',
  95. };
  96.  
  97. // Apply custom replacements
  98. for (var pattern in bbReplacements) {
  99. var regex = new RegExp(pattern, 'gi');
  100. descriptionHTML = descriptionHTML.replace(regex, bbReplacements[pattern]);
  101. }
  102.  
  103. // Clear unsupported tags
  104. descriptionHTML = descriptionHTML.replace(/<(?!\/?(h1|h2|h3|b|u|i|strike|spoiler|ul|li|ol|code|tr|th|td|img|hr|blockquote|\/blockquote))[^>]+>/g, '');
  105.  
  106. var bbcodeContent = descriptionHTML;
  107.  
  108. return bbcodeContent;
  109. }
  110.  
  111. function getHTMLtoMD(descriptionHTML) {
  112. // Custom replacements for Steam HTML to Markdown conversion
  113. var mdReplacements = {
  114. // Essential
  115. '<br>': '\n',
  116. '<span class="bb_link_host">([\\s\\S]*?)<\/span>': '',
  117. '<span>([\\s\\S]*?)<\/span>': '$1',
  118. // Headers
  119. '<div class="bb_h1">([\\s\\S]*?)<\/div>': '# $1\n',
  120. '<div class="bb_h2">([\\s\\S]*?)<\/div>': '## $1\n',
  121. '<div class="bb_h3">([\\s\\S]*?)<\/div>': '### $1\n',
  122. // Font styling
  123. '<b>([\\s\\S]*?)<\/b>': '**$1**',
  124. '<u>([\\s\\S]*?)<\/u>': '__$1__',
  125. '<i>([\\s\\S]*?)<\/i>': '*$1*',
  126. // Font formatting
  127. '<span class="bb_strike">([\\s\\S]*?)<\/span>': '~~$1~~',
  128. '<span class="bb_spoiler">([\\s\\S]*?)<\/span>': '<details><summary>Spoiler</summary>$1</details>',
  129. '<a[^>]*class="bb_link"[^>]*href="([^"]+)"(?:[^>]*target="([^"]+)")?(?:[^>]*rel="([^"]+)")?[^>]*>([\\s\\S]*?)<\/a>': '[$4]($1)',
  130. // Lists
  131. '<li>([\\s\\S]*?)<\/li>': '* $1',
  132. '<ol>([\\s\\S]*?)<\/ol>': (match, p1, offset, string) => {
  133. const lines = p1.trim().split('\n');
  134. let currentIndex = 1;
  135. const formattedLines = lines.map((line, index) => {
  136. // Check if the current line starts a new ordered list
  137. const isNewList = line.trim().startsWith('<li>');
  138. // If it's a new list, reset currentIndex to 1
  139. if (isNewList) {
  140. currentIndex = 1;
  141. }
  142. // Replace asterisks (*) or hyphens (-) with incremental numbers
  143. return line.replace(/^\s*[\*\-]/, () => {
  144. const updatedNumber = currentIndex++;
  145. return `${updatedNumber}.`;
  146. });
  147. });
  148. return `<ol>\n${formattedLines.join('\n')}\n</ol>`;
  149. },
  150. // Font formatting
  151. '<div class="bb_code">([\\s\\S]*?)<\/div>': '\n```\n$1\n```\n',
  152. // TODO: Fix noparse. It's not working properly. Do PR if you can fix it.
  153. // Tables
  154. // TODO: Fix bb_table. It's not working properly. Do PR if you can fix it.
  155. // TODO: Fix bb_table_tr. It's not working properly. Do PR if you can fix it.
  156. // TODO: Fix bb_table_th. It's not working properly. Do PR if you can fix it.
  157. // TODO: Fix bb_table_td. It's not working properly. Do PR if you can fix it.
  158. // Images
  159. '<img src="([^"]+)"[^>]*>': '![image]($1)',
  160. // Others
  161. '<hr>': '---',
  162. '<blockquote class="bb_blockquote">([\\s\\S]*?)</blockquote>' : '> $1',
  163. };
  164.  
  165. // Apply custom replacements
  166. for (var pattern in mdReplacements) {
  167. var regex = new RegExp(pattern, 'gi');
  168. descriptionHTML = descriptionHTML.replace(regex, mdReplacements[pattern]);
  169. }
  170.  
  171. // Clear unsupported tags except for <details><summary>Spoiler</summary>$1</details>
  172. descriptionHTML = descriptionHTML.replace(/<(?!details><summary>Spoiler<\/summary>\$1<\/details>)[^>]+>/g, '');
  173.  
  174. var markdownContent = descriptionHTML;
  175.  
  176. return markdownContent;
  177. }
  178.  
  179. function insertButton(downloadButton) {
  180. var fixedMargin = document.querySelector('.game_area_purchase_margin');
  181. if (fixedMargin) {
  182. // Better alignment ...
  183. fixedMargin.style.marginBottom = 'auto';
  184. // Find the element after which the button should be inserted
  185. var targetElement = document.querySelector('.workshopItemDescription');
  186. if (targetElement) {
  187. // Insert the button after the target element
  188. targetElement.parentNode.insertBefore(downloadButton, targetElement.nextSibling);
  189. }
  190. }
  191. }
  192. // Create go to repo button
  193. function createGoToRepoButton() {
  194. var goToRepoButton = document.createElement('a');
  195. 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';
  196. goToRepoButton.classList.add('btn_darkblue_white_innerfade', 'btn_border_2px', 'btn_medium');
  197. goToRepoButton.style.marginBottom = '5px';
  198. goToRepoButton.style.marginRight = '5px';
  199. goToRepoButton.style.padding = '5px 10px';
  200. goToRepoButton.style.height = '21px';
  201. goToRepoButton.style.fontSize = '14px';
  202. goToRepoButton.href = 'https://github.com/criskkky/SWDD';
  203. goToRepoButton.target = '_blank';
  204.  
  205. insertButton(goToRepoButton);
  206. }
  207.  
  208. // Create the download button for Markdown
  209. function createDownloadButtonMD() {
  210. var downloadButton = document.createElement('button');
  211. 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';
  212. downloadButton.classList.add('btn_green_white_innerfade', 'btn_border_2px', 'btn_medium');
  213. downloadButton.style.marginBottom = '5px';
  214. downloadButton.style.marginRight = '5px';
  215. downloadButton.style.padding = '5px 10px';
  216. downloadButton.style.height = '34.43px';
  217. downloadButton.style.fontSize = '14px';
  218. downloadButton.addEventListener('click', function () {
  219. var markdownContent = getHTMLtoMD(getDescription());
  220. if (markdownContent) {
  221. downloadContent(markdownContent, 'WorkshopDownload.md');
  222. } else {
  223. alert('No content found in Markdown format.');
  224. }
  225. });
  226.  
  227. insertButton(downloadButton);
  228. }
  229.  
  230. // Create the download button for BBCode
  231. function createDownloadButtonBBC() {
  232. var downloadButton = document.createElement('button');
  233. 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';
  234. downloadButton.classList.add('btn_green_white_innerfade', 'btn_border_2px', 'btn_medium');
  235. downloadButton.style.marginBottom = '5px';
  236. downloadButton.style.marginRight = '5px';
  237. downloadButton.style.padding = '5px 10px';
  238. downloadButton.style.height = '34.43px';
  239. downloadButton.style.fontSize = '14px';
  240. downloadButton.addEventListener('click', function () {
  241. var bbcodeContent = getHTMLtoBBC(getDescription());
  242. if (bbcodeContent) {
  243. downloadContent(bbcodeContent, 'WorkshopDownload.bbcode');
  244. } else {
  245. alert('No content found in BBCode format.');
  246. }
  247. });
  248.  
  249. insertButton(downloadButton);
  250. }
  251.  
  252. // Execute the functions when the page loads
  253. window.addEventListener('load', function () {
  254. createGoToRepoButton();
  255. createDownloadButtonMD();
  256. createDownloadButtonBBC();
  257. });