您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Extract Links, Subscribe to All, or Unsubscribe from All items on any Steam Workshop page.
// ==UserScript== // @name Steam Workshop Links Extractor & Auto-Subscriptions // @namespace SteamWorkshopLinksExtractorandAutoSubscriptions // @version 1.00 // @description Extract Links, Subscribe to All, or Unsubscribe from All items on any Steam Workshop page. // @author Dragonking69 // @match https://steamcommunity.com/workshop/browse/* // @grant none // @run-at document-idle // @license MIT // ==/UserScript== (function() { 'use strict'; // --- CONFIGURATION --- const maxPagesToProcess = 100; // <--- SET YOUR MAXIMUM NUMBER OF PAGES TO PROCESS const outputFilename = 'steam_workshop_links.txt'; // Filename for extracted links // ------------------- let pageTimeoutId = null; // To keep track of the safety timeout // --- ACTION-SPECIFIC LOGIC --- function runExtractionProcess() { console.log('ACTION: Extracting Links...'); const storedLinks = JSON.parse(sessionStorage.getItem('workshopExtractedLinks') || '[]'); const currentPageLinks = new Set(storedLinks); const linkElements = document.querySelectorAll('a.ugc[href*="sharedfiles/filedetails/"]'); linkElements.forEach(el => currentPageLinks.add(el.href)); sessionStorage.setItem('workshopExtractedLinks', JSON.stringify([...currentPageLinks])); console.log(`Found ${linkElements.length} links on this page. Total unique links: ${currentPageLinks.size}`); proceedOrStop(); } function runSubscriptionProcess() { console.log('ACTION: Subscribing to all...'); const buttonsToClick = document.querySelectorAll('span[id^="SubscribeItemBtn"]:not(.toggled)'); if (buttonsToClick.length > 0) { console.log(`Found ${buttonsToClick.length} items to subscribe to. Clicking now...`); buttonsToClick.forEach(btn => btn.click()); waitForCompletion('subscribe'); } else { console.log("All items on this page are already subscribed."); proceedOrStop(); } } function runUnsubscriptionProcess() { console.log('ACTION: Unsubscribing from all...'); const buttonsToClick = document.querySelectorAll('span[id^="SubscribeItemBtn"].toggled'); if (buttonsToClick.length > 0) { console.log(`Found ${buttonsToClick.length} items to unsubscribe from. Clicking now...`); buttonsToClick.forEach(btn => btn.click()); waitForCompletion('unsubscribe'); } else { console.log("No items on this page were subscribed."); proceedOrStop(); } } // --- CORE & HELPER FUNCTIONS --- function waitForCompletion(expectedState) { const totalItems = document.querySelectorAll('.workshopItem').length; const isSubscribing = expectedState === 'subscribe'; console.log(`Waiting for ${totalItems} items to reach '${expectedState}' state...`); const intervalCheck = setInterval(() => { const subscribedItemsCount = document.querySelectorAll('.user_action_history_icon.subscribed[style*="block"]').length; const unsubscribedItemsCount = totalItems - subscribedItemsCount; const goalReached = isSubscribing ? (subscribedItemsCount >= totalItems) : (unsubscribedItemsCount >= totalItems); console.log(`Progress: ${isSubscribing ? subscribedItemsCount : unsubscribedItemsCount} / ${totalItems} completed.`); if (goalReached) { clearInterval(intervalCheck); clearTimeout(pageTimeoutId); console.log('All actions confirmed.'); proceedOrStop(); } }, 2000); pageTimeoutId = setTimeout(() => { clearInterval(intervalCheck); console.warn(`Page timeout reached. Moving to next page anyway.`); proceedOrStop(); }, 30000); // 30-second timeout } function goToNextPage() { const urlParams = new URLSearchParams(window.location.search); const currentPage = parseInt(urlParams.get('p'), 10) || 1; const nextPage = currentPage + 1; console.log(`Navigating from page ${currentPage} to ${nextPage}...`); urlParams.set('p', nextPage); window.location.search = urlParams.toString(); } function proceedOrStop() { clearTimeout(pageTimeoutId); const urlParams = new URLSearchParams(window.location.search); const currentPage = parseInt(urlParams.get('p'), 10) || 1; if (currentPage >= maxPagesToProcess) { stopAction(true, `Reached the maximum page limit of ${maxPagesToProcess}.`); return; } const nextPageButton = document.querySelector('.pagebtn:not(.disabled):last-child'); if (!nextPageButton || nextPageButton.textContent !== '>') { stopAction(true, 'Finished scanning all available pages.'); return; } console.log('Page complete. Moving to the next page in 2 seconds...'); setTimeout(goToNextPage, 2000); } function downloadLinksAsTxt() { const links = JSON.parse(sessionStorage.getItem('workshopExtractedLinks') || '[]'); if (links.length === 0) return; console.log(`Preparing to download ${links.length} links.`); const fileContent = links.join('\n'); const blob = new Blob([fileContent], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = outputFilename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } // --- UI AND STATE MANAGEMENT --- function startAction(action) { const confirmation = confirm(`This will start the "${action}" process and continue automatically up to page ${maxPagesToProcess}.\n\nAre you sure?`); if (confirmation) { sessionStorage.setItem('workshopToolAction', action); if (action === 'extract') { sessionStorage.setItem('workshopExtractedLinks', '[]'); } runAction(action); } } function stopAction(finished = false, message = 'Script stopped manually.') { const action = sessionStorage.getItem('workshopToolAction'); if (action === 'extract') { if (finished) { alert(message + ' Downloading collected links now.'); } else { alert(message + ' Downloading any links collected so far.'); } downloadLinksAsTxt(); } else { alert(message); } clearTimeout(pageTimeoutId); sessionStorage.removeItem('workshopToolAction'); sessionStorage.removeItem('workshopExtractedLinks'); updateUIState(null); } function updateUIState(runningAction) { const panel = document.getElementById('workshopToolPanel'); if (!panel) return; const buttons = panel.querySelectorAll('button'); buttons.forEach(btn => { const action = btn.getAttribute('data-action'); if (runningAction) { // A task is running if (action === 'stop') { btn.style.display = 'block'; let stopText = 'Stop'; if (runningAction === 'extract') stopText += ' & Download'; btn.textContent = stopText; } else { btn.style.display = 'none'; } } else { // No task is running btn.style.display = action === 'stop' ? 'none' : 'block'; } }); } function createControllerPanel() { const panel = document.createElement('div'); panel.id = 'workshopToolPanel'; Object.assign(panel.style, { position: 'fixed', bottom: '20px', right: '20px', zIndex: '9999', backgroundColor: 'rgba(0, 0, 0, 0.7)', padding: '10px', borderRadius: '5px', display: 'flex', flexDirection: 'column', gap: '8px' }); const actions = [ { id: 'extract', text: 'Extract Links', color: '#4c6e81' }, { id: 'subscribe', text: 'Subscribe All', color: '#5ba32b' }, { id: 'unsubscribe', text: 'Unsubscribe All', color: '#a03c2a' }, { id: 'stop', text: 'Stop', color: '#d94126' } ]; actions.forEach(({ id, text, color }) => { const button = document.createElement('button'); button.textContent = text; button.setAttribute('data-action', id); Object.assign(button.style, { padding: '8px 16px', fontSize: '14px', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer', backgroundColor: color, width: '100%' }); if (id === 'stop') { button.addEventListener('click', () => stopAction(false)); } else { button.addEventListener('click', () => startAction(id)); } panel.appendChild(button); }); document.body.appendChild(panel); } function runAction(action) { updateUIState(action); switch (action) { case 'extract': runExtractionProcess(); break; case 'subscribe': runSubscriptionProcess(); break; case 'unsubscribe': runUnsubscriptionProcess(); break; } } // --- SCRIPT INITIALIZATION --- createControllerPanel(); const activeAction = sessionStorage.getItem('workshopToolAction'); updateUIState(activeAction); if (activeAction) { setTimeout(() => runAction(activeAction), 2000); } })();