SWDD - Steam Workshop Description Downloader

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

当前为 2024-01-10 提交的版本,查看 最新版本

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