Adds a button to copy SteamCMD workshop download commands
// ==UserScript==
// @name Steam Workshop Copy SteamCMD Button
// @namespace http://steamcommunity.com/
// @version 1.0
// @description Adds a button to copy SteamCMD workshop download commands
// @author TheFantasticLoki
// @match https://steamcommunity.com/sharedfiles/filedetails/*
// @grant GM_setClipboard
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Function to extract item ID from URL
function getWorkshopItemIdFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('id');
}
// Function to find game ID
function getGameId() {
// Look for game ID in breadcrumbs
const breadcrumbs = document.querySelectorAll('.breadcrumbs a');
for (const crumb of breadcrumbs) {
const href = crumb.getAttribute('href');
if (href && href.includes('/app/')) {
const match = href.match(/\/app\/(\d+)/);
if (match && match[1]) {
return match[1];
}
}
}
// Alternative method: look for game app link
const appLinks = document.querySelectorAll('a[href*="/app/"]');
for (const link of appLinks) {
const match = link.href.match(/\/app\/(\d+)/);
if (match && match[1]) {
return match[1];
}
}
return null;
}
function createCopyButton(gameId, workshopItemId) {
// Create styles for our custom elements
const styleEl = document.createElement('style');
styleEl.textContent = `
.steam_cmd_copy_btn {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
width: 33px;
height: 33px;
background: #3d6c8d;
border-radius: 4px;
margin: 0 5px;
transition: background 0.2s;
position: relative;
}
.steam_cmd_copy_btn:hover {
background: #67c1f5;
}
.steam_cmd_copy_btn svg {
width: 25px;
height: 25px;
vertical-align: middle;
fill: white;
}
.steam_cmd_copy_tooltip {
display: none;
position: fixed;
background-color: #171a21;
color: #fff;
text-align: center;
border-radius: 3px;
padding: 8px 12px;
z-index: 9999;
border: 1px solid #4d4d4d;
white-space: nowrap;
font-size: 12px;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
pointer-events: none;
}
.steam_cmd_command {
font-family: monospace;
background: #32353c;
padding: 4px 6px;
border-radius: 2px;
margin-top: 5px;
display: block;
}
`;
document.head.appendChild(styleEl);
// Create the button element
const copyButton = document.createElement('div');
copyButton.className = 'steam_cmd_copy_btn';
// Create SVG icon for clipboard
copyButton.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<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"/>
</svg>
`;
// Create tooltip as a separate element attached to body
const tooltip = document.createElement('div');
tooltip.className = 'steam_cmd_copy_tooltip';
tooltip.innerHTML = `Copy SteamCMD<span class="steam_cmd_command">workshop_download_item ${gameId} ${workshopItemId}</span>`;
document.body.appendChild(tooltip);
// Handle tooltip positioning
copyButton.addEventListener('mouseenter', function(e) {
const rect = copyButton.getBoundingClientRect();
tooltip.style.display = 'block';
// Position tooltip above the button
tooltip.style.left = rect.left + (rect.width / 2) - (tooltip.offsetWidth / 2) + 'px';
tooltip.style.top = rect.top - tooltip.offsetHeight - 10 + 'px';
// If tooltip would go off the top, show it below instead
if (rect.top - tooltip.offsetHeight < 0) {
tooltip.style.top = rect.bottom + 10 + 'px';
}
});
copyButton.addEventListener('mouseleave', function() {
tooltip.style.display = 'none';
});
// Add click event to copy the command
copyButton.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
const command = `workshop_download_item ${gameId} ${workshopItemId}`;
GM_setClipboard(command);
// Visual feedback
tooltip.innerHTML = 'Copied!';
setTimeout(function() {
tooltip.innerHTML = `Copy SteamCMD<span class="steam_cmd_command">workshop_download_item ${gameId} ${workshopItemId}</span>`;
}, 2000);
});
// Clean up tooltip when button is removed
const cleanupTooltip = new MutationObserver(function(mutations) {
if (!document.body.contains(copyButton)) {
tooltip.remove();
cleanupTooltip.disconnect();
}
});
cleanupTooltip.observe(document.body, { childList: true, subtree: true });
return copyButton;
}
// Main function to run when page is loaded
function addCopyButtons() {
// Check if we've already run
if (document.querySelector('.steam_cmd_copy_btn')) {
return; // Skip if buttons already exist
}
const gameId = getGameId();
if (!gameId) {
console.log('Could not find game ID');
return;
}
// First check for single item page
const singleSubscribeButton = document.getElementById('SubscribeItemBtn');
if (singleSubscribeButton) {
const workshopItemId = getWorkshopItemIdFromUrl();
if (workshopItemId) {
// Find the parent div of the subscribe button
const subscribeButtonContainer = singleSubscribeButton.closest('div');
if (subscribeButtonContainer && subscribeButtonContainer.parentNode) {
// Create a new container div for our button
const copyButtonContainer = document.createElement('div');
copyButtonContainer.style.display = 'inline-block';
copyButtonContainer.style.marginLeft = '5px';
// Add the copy button to its container
const copyButton = createCopyButton(gameId, workshopItemId);
copyButtonContainer.appendChild(copyButton);
// Insert after the subscribe button's container
subscribeButtonContainer.parentNode.insertBefore(
copyButtonContainer,
subscribeButtonContainer.nextSibling
);
}
}
return;
}
// Collection page handling - targeting the exact structure seen in collections
const subscriptionControls = document.querySelectorAll('.subscriptionControls');
subscriptionControls.forEach(controlsDiv => {
// Check if we already added a button here
if (controlsDiv.querySelector('.steam_cmd_copy_btn')) {
return;
}
// Find the subscribe button inside this controls div
const subscribeButton = controlsDiv.querySelector('[id^="SubscribeItemBtn"]');
if (subscribeButton) {
// Extract workshop ID from button ID (format: SubscribeItemBtn###)
const match = subscribeButton.id.match(/SubscribeItemBtn(\d+)/);
if (match && match[1]) {
const workshopItemId = match[1];
// Create copy button
const copyButton = createCopyButton(gameId, workshopItemId);
// Directly append to the controls div instead of creating a new container
controlsDiv.appendChild(copyButton);
}
}
});
}
// Use mutation observer to run when content changes (for dynamic loading)
function initObserver() {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length) {
addCopyButtons();
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
// Check if the page is already loaded
if (document.readyState === 'complete' || document.readyState === 'interactive') {
addCopyButtons();
initObserver();
} else {
// Wait for page to load
window.addEventListener('DOMContentLoaded', () => {
addCopyButtons();
initObserver();
});
}
})();