Steam Workshop Copy SteamCMD Button

Adds a button to copy SteamCMD workshop download commands

  1. // ==UserScript==
  2. // @name Steam Workshop Copy SteamCMD Button
  3. // @namespace http://steamcommunity.com/
  4. // @version 1.0
  5. // @description Adds a button to copy SteamCMD workshop download commands
  6. // @author TheFantasticLoki
  7. // @match https://steamcommunity.com/sharedfiles/filedetails/*
  8. // @grant GM_setClipboard
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14. // Function to extract item ID from URL
  15. function getWorkshopItemIdFromUrl() {
  16. const urlParams = new URLSearchParams(window.location.search);
  17. return urlParams.get('id');
  18. }
  19. // Function to find game ID
  20. function getGameId() {
  21. // Look for game ID in breadcrumbs
  22. const breadcrumbs = document.querySelectorAll('.breadcrumbs a');
  23. for (const crumb of breadcrumbs) {
  24. const href = crumb.getAttribute('href');
  25. if (href && href.includes('/app/')) {
  26. const match = href.match(/\/app\/(\d+)/);
  27. if (match && match[1]) {
  28. return match[1];
  29. }
  30. }
  31. }
  32. // Alternative method: look for game app link
  33. const appLinks = document.querySelectorAll('a[href*="/app/"]');
  34. for (const link of appLinks) {
  35. const match = link.href.match(/\/app\/(\d+)/);
  36. if (match && match[1]) {
  37. return match[1];
  38. }
  39. }
  40. return null;
  41. }
  42. function createCopyButton(gameId, workshopItemId) {
  43. // Create styles for our custom elements
  44. const styleEl = document.createElement('style');
  45. styleEl.textContent = `
  46. .steam_cmd_copy_btn {
  47. display: flex;
  48. align-items: center;
  49. justify-content: center;
  50. cursor: pointer;
  51. width: 33px;
  52. height: 33px;
  53. background: #3d6c8d;
  54. border-radius: 4px;
  55. margin: 0 5px;
  56. transition: background 0.2s;
  57. position: relative;
  58. }
  59. .steam_cmd_copy_btn:hover {
  60. background: #67c1f5;
  61. }
  62. .steam_cmd_copy_btn svg {
  63. width: 25px;
  64. height: 25px;
  65. vertical-align: middle;
  66. fill: white;
  67. }
  68. .steam_cmd_copy_tooltip {
  69. display: none;
  70. position: fixed;
  71. background-color: #171a21;
  72. color: #fff;
  73. text-align: center;
  74. border-radius: 3px;
  75. padding: 8px 12px;
  76. z-index: 9999;
  77. border: 1px solid #4d4d4d;
  78. white-space: nowrap;
  79. font-size: 12px;
  80. box-shadow: 0 0 5px rgba(0,0,0,0.3);
  81. pointer-events: none;
  82. }
  83. .steam_cmd_command {
  84. font-family: monospace;
  85. background: #32353c;
  86. padding: 4px 6px;
  87. border-radius: 2px;
  88. margin-top: 5px;
  89. display: block;
  90. }
  91. `;
  92. document.head.appendChild(styleEl);
  93. // Create the button element
  94. const copyButton = document.createElement('div');
  95. copyButton.className = 'steam_cmd_copy_btn';
  96. // Create SVG icon for clipboard
  97. copyButton.innerHTML = `
  98. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  99. <path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
  100. </svg>
  101. `;
  102. // Create tooltip as a separate element attached to body
  103. const tooltip = document.createElement('div');
  104. tooltip.className = 'steam_cmd_copy_tooltip';
  105. tooltip.innerHTML = `Copy SteamCMD<span class="steam_cmd_command">workshop_download_item ${gameId} ${workshopItemId}</span>`;
  106. document.body.appendChild(tooltip);
  107. // Handle tooltip positioning
  108. copyButton.addEventListener('mouseenter', function(e) {
  109. const rect = copyButton.getBoundingClientRect();
  110. tooltip.style.display = 'block';
  111. // Position tooltip above the button
  112. tooltip.style.left = rect.left + (rect.width / 2) - (tooltip.offsetWidth / 2) + 'px';
  113. tooltip.style.top = rect.top - tooltip.offsetHeight - 10 + 'px';
  114. // If tooltip would go off the top, show it below instead
  115. if (rect.top - tooltip.offsetHeight < 0) {
  116. tooltip.style.top = rect.bottom + 10 + 'px';
  117. }
  118. });
  119. copyButton.addEventListener('mouseleave', function() {
  120. tooltip.style.display = 'none';
  121. });
  122. // Add click event to copy the command
  123. copyButton.addEventListener('click', function(e) {
  124. e.preventDefault();
  125. e.stopPropagation();
  126. const command = `workshop_download_item ${gameId} ${workshopItemId}`;
  127. GM_setClipboard(command);
  128. // Visual feedback
  129. tooltip.innerHTML = 'Copied!';
  130. setTimeout(function() {
  131. tooltip.innerHTML = `Copy SteamCMD<span class="steam_cmd_command">workshop_download_item ${gameId} ${workshopItemId}</span>`;
  132. }, 2000);
  133. });
  134. // Clean up tooltip when button is removed
  135. const cleanupTooltip = new MutationObserver(function(mutations) {
  136. if (!document.body.contains(copyButton)) {
  137. tooltip.remove();
  138. cleanupTooltip.disconnect();
  139. }
  140. });
  141. cleanupTooltip.observe(document.body, { childList: true, subtree: true });
  142. return copyButton;
  143. }
  144. // Main function to run when page is loaded
  145. function addCopyButtons() {
  146. // Check if we've already run
  147. if (document.querySelector('.steam_cmd_copy_btn')) {
  148. return; // Skip if buttons already exist
  149. }
  150. const gameId = getGameId();
  151. if (!gameId) {
  152. console.log('Could not find game ID');
  153. return;
  154. }
  155. // First check for single item page
  156. const singleSubscribeButton = document.getElementById('SubscribeItemBtn');
  157. if (singleSubscribeButton) {
  158. const workshopItemId = getWorkshopItemIdFromUrl();
  159. if (workshopItemId) {
  160. // Find the parent div of the subscribe button
  161. const subscribeButtonContainer = singleSubscribeButton.closest('div');
  162. if (subscribeButtonContainer && subscribeButtonContainer.parentNode) {
  163. // Create a new container div for our button
  164. const copyButtonContainer = document.createElement('div');
  165. copyButtonContainer.style.display = 'inline-block';
  166. copyButtonContainer.style.marginLeft = '5px';
  167. // Add the copy button to its container
  168. const copyButton = createCopyButton(gameId, workshopItemId);
  169. copyButtonContainer.appendChild(copyButton);
  170. // Insert after the subscribe button's container
  171. subscribeButtonContainer.parentNode.insertBefore(
  172. copyButtonContainer,
  173. subscribeButtonContainer.nextSibling
  174. );
  175. }
  176. }
  177. return;
  178. }
  179. // Collection page handling - targeting the exact structure seen in collections
  180. const subscriptionControls = document.querySelectorAll('.subscriptionControls');
  181. subscriptionControls.forEach(controlsDiv => {
  182. // Check if we already added a button here
  183. if (controlsDiv.querySelector('.steam_cmd_copy_btn')) {
  184. return;
  185. }
  186.  
  187. // Find the subscribe button inside this controls div
  188. const subscribeButton = controlsDiv.querySelector('[id^="SubscribeItemBtn"]');
  189. if (subscribeButton) {
  190. // Extract workshop ID from button ID (format: SubscribeItemBtn###)
  191. const match = subscribeButton.id.match(/SubscribeItemBtn(\d+)/);
  192. if (match && match[1]) {
  193. const workshopItemId = match[1];
  194.  
  195. // Create copy button
  196. const copyButton = createCopyButton(gameId, workshopItemId);
  197.  
  198. // Directly append to the controls div instead of creating a new container
  199. controlsDiv.appendChild(copyButton);
  200. }
  201. }
  202. });
  203. }
  204. // Use mutation observer to run when content changes (for dynamic loading)
  205. function initObserver() {
  206. const observer = new MutationObserver((mutations) => {
  207. for (const mutation of mutations) {
  208. if (mutation.addedNodes.length) {
  209. addCopyButtons();
  210. }
  211. }
  212. });
  213. observer.observe(document.body, { childList: true, subtree: true });
  214. }
  215. // Check if the page is already loaded
  216. if (document.readyState === 'complete' || document.readyState === 'interactive') {
  217. addCopyButtons();
  218. initObserver();
  219. } else {
  220. // Wait for page to load
  221. window.addEventListener('DOMContentLoaded', () => {
  222. addCopyButtons();
  223. initObserver();
  224. });
  225. }
  226. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址