您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhances Steam badges with detailed data, crafted highlight, IndexedDB for local caching, immediate cached display, and optional manual re-queue button. Seamless data hot-swapping during background refresh.
// ==UserScript== // @name Steam Badge Enhancer // @namespace https://github.com/encumber // @version 2.1 // @description Enhances Steam badges with detailed data, crafted highlight, IndexedDB for local caching, immediate cached display, and optional manual re-queue button. Seamless data hot-swapping during background refresh. // @author Nitoned // @match https://steamcommunity.com/*/badges/* // @match https://steamcommunity.com/*/badges* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // ==/UserScript== (function() { 'use strict'; // --- Configuration --- const STEAMSETS_API_KEY = ''; // Get api key from https://steamsets.com/settings/developer-apps const STEAMSETS_API_URL = 'https://api.steamsets.com/v1/app.listBadges'; const STEAM_BADGE_INFO_URL = 'https://steamcommunity.com/my/ajaxgetbadgeinfo/'; const STEAMSETS_API_CALL_DELAY_MS = 1000; // 1 second delay before Steamsets API calls const STEAM_API_CALL_DELAY_MS = 200; // 200ms delay between Steam's ajaxgetbadgeinfo calls const CACHE_EXPIRATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds const SCRIPT_TOGGLE_KEY = 'steamBadgeEnhancerEnabled'; // Key for the main script toggle const REQUEUE_BUTTON_TOGGLE_KEY = 'steamBadgeRequeueButtonEnabled'; // Key for the re-queue button toggle // IndexedDB Configuration const DB_NAME = 'SteamBadgeCacheDB'; const DB_VERSION = 1; // Increment this if you change the database structure const STORE_NAME = 'badgeCache'; // --- End Configuration --- // Get the current state of the toggle settings let isScriptEnabled = GM_getValue(SCRIPT_TOGGLE_KEY, true); // Default script enabled to true let isRequeueButtonEnabled = GM_getValue(REQUEUE_BUTTON_TOGGLE_KEY, true); // Default re-queue button enabled to true // Variable to hold the IndexedDB database instance let db = null; // --- IndexedDB Functions --- function openDatabase() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = (event) => { console.error("IndexedDB database error:", event.target.error); reject(event.target.error); }; request.onupgradeneeded = (event) => { const db = event.target.result; // Create the object store if it doesn't exist if (!db.objectStoreNames.contains(STORE_NAME)) { db.createObjectStore(STORE_NAME, { keyPath: 'appId' }); console.log(`IndexedDB object store "${STORE_NAME}" created.`); } // Future versions could add indexes here if needed for querying }; request.onsuccess = (event) => { db = event.target.result; console.log("IndexedDB database opened successfully."); resolve(db); }; }); } async function getCacheEntry(appId) { if (!db) { console.warn("IndexedDB not initialized. Cannot get cache entry."); return null; } return new Promise((resolve, reject) => { const transaction = db.transaction([STORE_NAME], 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.get(appId); request.onerror = (event) => { console.error(`Error getting cache entry for appId ${appId} from IndexedDB:`, event.target.error); resolve(null); // Resolve with null on error }; request.onsuccess = (event) => { const cachedEntry = event.target.result; // console.log(`IndexedDB get success for appId ${appId}:`, cachedEntry); // Too chatty resolve(cachedEntry); }; }); } async function setCacheEntry(appId, cacheEntry) { if (!db) { console.warn("IndexedDB not initialized. Cannot set cache entry."); return false; } return new Promise((resolve, reject) => { const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); // Add the appId to the object since it's the keyPath cacheEntry.appId = appId; const request = store.put(cacheEntry); // put() adds or updates request.onerror = (event) => { console.error(`Error setting cache entry for appId ${appId} in IndexedDB:`, event.target.error); resolve(false); // Resolve with false on error }; request.onsuccess = (event) => { // console.log(`IndexedDB set success for appId ${appId}.`); // Too chatty resolve(true); // Resolve with true on success }; transaction.oncomplete = () => { // console.log(`IndexedDB transaction complete for appId ${appId}.`); // Too chatty // The resolve is already called in onsuccess }; }); } async function removeCacheEntry(appId) { if (!db) { console.warn("IndexedDB not initialized. Cannot remove cache entry."); return false; } return new Promise((resolve, reject) => { const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.delete(appId); request.onerror = (event) => { console.error(`Error removing cache entry for appId ${appId} from IndexedDB:`, event.target.error); resolve(false); // Resolve with false on error }; request.onsuccess = (event) => { console.log(`Removed cache entry for App ID: ${appId} from IndexedDB.`); resolve(true); // Resolve with true on success }; transaction.oncomplete = () => { // console.log(`IndexedDB delete transaction complete for appId ${appId}.`); // Too chatty // The resolve is already called in onsuccess }; }); } // --- End IndexedDB Functions --- // Add the toggle buttons function addToggleButtons() { const profileHeader = document.querySelector('.profile_header_actions, .profile_header_actions_secondary'); if (!profileHeader) { console.warn("Could not find profile header actions to add toggle buttons."); return; } // Add Main Script Toggle Button const scriptToggleButton = document.createElement('div'); scriptToggleButton.id = 'steam-badge-enhancer-toggle'; scriptToggleButton.style.cssText = ` display: inline-block; margin-left: 10px; padding: 5px 10px; background-color: ${isScriptEnabled ? '#5cb85c' : '#d9534f'}; /* Green for enabled, Red for disabled */ color: white; border-radius: 3px; cursor: pointer; font-size: 12px; line-height: 1.2; user-select: none; margin-bottom: 5px; /* Space between buttons */ `; scriptToggleButton.textContent = `Enhancer: ${isScriptEnabled ? 'Enabled' : 'Disabled'}`; scriptToggleButton.title = `Click to ${isScriptEnabled ? 'disable' : 'enable'} the Steam Badge Enhancer script.`; scriptToggleButton.addEventListener('click', () => { isScriptEnabled = !isScriptEnabled; GM_setValue(SCRIPT_TOGGLE_KEY, isScriptEnabled); scriptToggleButton.style.backgroundColor = isScriptEnabled ? '#5cb85c' : '#d9534f'; scriptToggleButton.textContent = `Enhancer: ${isScriptEnabled ? 'Enabled' : 'Disabled'}`; scriptToggleButton.title = `Click to ${isScriptEnabled ? 'disable' : 'enable'} the Steam Badge Enhancer script.`; console.log(`Steam Badge Enhancer ${isScriptEnabled ? 'enabled' : 'disabled'}. Reload the page for changes to take full effect.`); // Reloading is recommended for the main toggle }); profileHeader.appendChild(scriptToggleButton); // Add Re-queue Button Toggle Button const requeueToggle = document.createElement('div'); requeueToggle.id = 'steam-badge-requeue-toggle'; requeueToggle.style.cssText = ` display: inline-block; margin-left: 10px; padding: 5px 10px; background-color: ${isRequeueButtonEnabled ? '#5cb85c' : '#d9534f'}; /* Green for enabled, Red for disabled */ color: white; border-radius: 3px; cursor: pointer; font-size: 12px; line-height: 1.2; user-select: none; /* Position relative to allow margin-left */ position: relative; top: 0px; /* Align with the first button if needed */ `; requeueToggle.textContent = `Re-queue Button: ${isRequeueButtonEnabled ? 'Shown' : 'Hidden'}`; requeueToggle.title = `Click to ${isRequeueButtonEnabled ? 'hide' : 'show'} the Re-queue Data Fetch buttons.`; requeueToggle.addEventListener('click', () => { isRequeueButtonEnabled = !isRequeueButtonEnabled; GM_setValue(REQUEUE_BUTTON_TOGGLE_KEY, isRequeueButtonEnabled); requeueToggle.style.backgroundColor = isRequeueButtonEnabled ? '#5cb85c' : '#d9534f'; requeueToggle.textContent = `Re-queue Button: ${isRequeueButtonEnabled ? 'Shown' : 'Hidden'}`; requeueToggle.title = `Click to ${isRequeueButtonEnabled ? 'hide' : 'show'} the Re-queue Data Fetch buttons.`; console.log(`Re-queue buttons ${isRequeueButtonEnabled ? 'shown' : 'hidden'}.`); // Immediately hide/show buttons without requiring a reload const buttons = document.querySelectorAll('.requeue_button'); buttons.forEach(button => { button.style.display = isRequeueButtonEnabled ? '' : 'none'; }); }); // Find the parent of the first button and insert the second button after it if (scriptToggleButton.parentNode) { scriptToggleButton.parentNode.insertBefore(requeueToggle, scriptToggleButton.nextSibling); } else { // Fallback if parent not found (less likely) profileHeader.appendChild(requeueToggle); } console.log("Steam Badge Enhancer toggle buttons added."); } // Add script styles GM_addStyle(` /* Style for the container holding the appended badge details */ .enhanced_badge_details_container { display: flex; flex-wrap: wrap; justify-content: center; /* Center the individual badges */ align-items: flex-start; /* Align items to the top */ margin-top: 10px; /* Space between original content and new content */ padding-top: 10px; border-top: 1px solid #303030; /* Separator line */ width: 100%; /* Take full width of the badge row */ min-height: 80px; /* Ensure container has some height even when empty or loading */ position: relative; /* Needed for absolute positioned loading indicators */ } .badge_info_container { display: flex; flex-direction: column; align-items: center; margin: 5px 15px; /* Increased horizontal margin (left and right) */ text-align: center; width: 120px; /* Increased width of each badge container */ flex-shrink: 0; /* Prevent shrinking */ position: relative; /* Needed for highlight pseudo-element */ } .badge_image { width: 64px; /* Adjust image size as needed */ height: 64px; /* Adjust image size as needed */ object-fit: contain; margin-bottom: 5px; } .badge_name { font-weight: bold; margin-bottom: 2px; font-size: 0.8em; /* Adjust font size for name */ /* Added text truncation styles */ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; /* Ensure it respects the container width */ } .badge_xp, .badge_scarcity, .badge_completion_date, .badge_level_display { /* Added .badge_level_display */ font-size: 0.8em; /* Adjust font size for these elements */ color: #8f98a0; /* Adjust secondary text color */ } .badge_completion_date { font-size: 0.75em; /* Slightly smaller font for the date */ white-space: nowrap; /* Prevent wrapping for the date */ overflow: hidden; /* Hide overflow */ text-overflow: ellipsis; /* Show ellipsis if still overflows */ max-width: 100%; /* Ensure it respects the container width */ } /* Style for highlighting the crafted badge */ .badge_info_container.crafted { box-shadow: 0 0 8px 2px rgb(154 155 255 / 50%); border: 1px solid #8e8e95; padding: 0px 5px 15px 5px; } /* Style for the re-queue button */ .requeue_button { padding: 3px 8px; max-width: 15px; background-color: #22558f; color: #ffffff; border: 1px solid #505050; border-radius: 3px; cursor: pointer; font-size: 1.3em; text-align: center; /* IMPORTANT: Stop click event propagation */ z-index: 99999; /* Increased z-index */ position: relative; /* Needed for z-index to work */ } .requeue_button:hover { background-color: #404040; } /* Style for the initial loading/cached indicator */ .initial_indicator { color: #8f98a0; font-style: italic; margin: 10px; width: 100%; display: block; text-align: center; } /* Style for the loading overlay */ .enhancer_loading_overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.7); /* Semi-transparent dark overlay */ display: flex; justify-content: center; align-items: center; color: white; font-size: 1.2em; z-index: -10; /* Above badge details but below the container */ } `); function extractAppIdFromBadgeLink(badgeRow) { const badgeLink = badgeRow.querySelector('a.badge_row_overlay'); if (badgeLink) { const match = badgeLink.href.match(/\/gamecards\/(\d+)\//); if (match && match[1]) { return parseInt(match[1], 10); } } // Fallback if primary link not found or doesn't have appid const badgeImageLink = badgeRow.querySelector('.badge_info_image a'); // Link around the badge image if (badgeImageLink) { const steamStoreLinkMatch = badgeImageLink.href.match(/\/app\/(\d+)\//); if (steamStoreLinkMatch && steamStoreLinkMatch[1]) { return parseInt(steamStoreLinkMatch[1], 10); } } return null; } async function getBadgeData(appId) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: STEAMSETS_API_URL, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${STEAMSETS_API_KEY}` // Use the single API key }, data: JSON.stringify({ appId: appId }), onload: function(response) { try { const data = JSON.parse(response.responseText); if (data && Array.isArray(data.badges)) { console.log(`Successfully fetched Steamsets badge data for appId ${appId}.`); resolve(data.badges); } else { console.error(`Steamsets API response for appId ${appId} did not contain a valid 'badges' array. Response data:`, data); resolve([]); // Resolve with empty array if data is not as expected } } catch (e) { console.error(`Error parsing Steamsets API response for appId ${appId}:`, e); resolve([]); // Resolve with empty array on parsing error } }, onerror: function(error) { console.error(`GM_xmlhttpRequest error for appId ${appId}:`, error); resolve([]); // Resolve with empty array on request error } }); }); } async function getCraftedBadgeInfo(appId, isFoil = false) { return new Promise((resolve, reject) => { let url = `${STEAM_BADGE_INFO_URL}${appId}`; if (isFoil) { url += '?border=1'; // Add parameter for foil badge info } GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { try { const data = JSON.parse(response.responseText); // Parse the JSON response let craftedLevel = 0; let isCrafted = false; // Check if badgedata and level exist in the JSON response if (data && data.badgedata && typeof data.badgedata.level === 'number') { craftedLevel = data.badgedata.level; // Consider it crafted if the level is greater than 0 isCrafted = craftedLevel > 0; } // console.log(`Fetched crafted info for appId ${appId} (Foil: ${isFoil}): Crafted Level = ${craftedLevel}, Is Crafted = ${isCrafted}`); // Too chatty // Return the crafted level and whether a badge (at any level) is crafted resolve({ craftedLevel: craftedLevel, isCrafted: isCrafted }); } catch (e) { console.error(`Error parsing Steam badge info JSON for appId ${appId} (Foil: ${isFoil}):`, e); resolve({ craftedLevel: 0, isCrafted: false }); // Resolve with default values on error } }, onerror: function(error) { console.error(`GM_xmlhttpRequest error fetching Steam badge info for appId ${appId} (Foil: ${isFoil}):`, error); resolve({ craftedLevel: 0, isCrafted: false }); // Resolve with default values on error } }); }); } // Helper function for introducing a delay function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Helper function to format date for display function formatDateForDisplay(dateString) { try { const date = new Date(dateString); if (isNaN(date.getTime())) { return 'Date unavailable'; } const options = { month: 'short', // 'Jan', 'Feb', etc. day: 'numeric', year: 'numeric', hour: 'numeric', // 12-hour format minute: 'numeric', hour12: true // Use 12-hour format with AM/PM }; // Use locale-specific formatting, then replace commas for the desired format let formatted = date.toLocaleString(undefined, options); // The default toLocaleString with 'short' month and numeric day/year // often results in "Month Day, Year, HH:MM AM/PM". // We'll return this format directly. return formatted; } catch (e) { console.error("Error formatting date:", e); return 'Date Formatting Error'; } } // --- IndexedDB Caching Functions (Replacing Local Storage) --- function isCacheValid(cachedEntry) { if (!cachedEntry) { return false; // Entry doesn't exist } // Check for basic structure required // Note: The appId is now part of the stored object due to keyPath if (typeof cachedEntry.timestamp !== 'number' || !cachedEntry.steamsetsData || typeof cachedEntry.craftedNormalInfo !== 'object' || typeof cachedEntry.craftedFoilInfo !== 'object' || typeof cachedEntry.appId !== 'number') { // Check for appId as well console.log("Cache entry has invalid structure:", cachedEntry); return false; // Invalid structure } const now = Date.now(); const isValid = (now - cachedEntry.timestamp) < CACHE_EXPIRATION_MS; return isValid; } // Map to store badge rows grouped by App ID (needed globally for re-queue) const badgeRowsByAppId = new Map(); // Queue for App IDs to process (fetch new data) const fetchQueue = []; // Flag to prevent multiple fetch loops running simultaneously let isFetching = false; // Set to track App IDs currently being fetched const currentlyFetching = new Set(); // Function to add an App ID to the fetch queue function queueAppIdForFetch(appId) { // Only add if not already in the queue or being processed if (!fetchQueue.includes(appId) && !currentlyFetching.has(appId)) { fetchQueue.push(appId); console.log(`App ID ${appId} added to fetch queue. Queue size: ${fetchQueue.length}`); // If not already fetching, start the fetch loop if (!isFetching) { processFetchQueue(); } } else { // console.log(`App ID ${appId} is already in the fetch queue or being fetched.`); // Too chatty } } // Main function to process the fetch queue async function processFetchQueue() { if (isFetching || fetchQueue.length === 0) { return; // Don't start if already fetching or queue is empty } isFetching = true; console.log("Starting fetch queue processing."); while (fetchQueue.length > 0) { const appId = fetchQueue[0]; // Peek at the next App ID // Ensure the App ID is added to the currentlyFetching set before processing currentlyFetching.add(appId); const rowsForApp = badgeRowsByAppId.get(appId) || []; if (rowsForApp.length === 0) { console.log(`No badge rows found for App ID ${appId} in badgeRowsByAppId map for fetch. Removing from queue.`); fetchQueue.shift(); // Remove the item if no rows found currentlyFetching.delete(appId); // Remove from fetching set continue; // Skip } console.log(`Processing App ID ${appId} from fetch queue.`); // Add loading indicator overlay to each row rowsForApp.forEach(badgeRow => { let enhancedBadgeDetailsContainer = badgeRow.querySelector('.enhanced_badge_details_container'); if (!enhancedBadgeDetailsContainer) { console.error(`Container not found for App ID ${appId} during fetch process.`); return; // Skip this row if container is missing (shouldn't happen after initialSetup) } // Check if an overlay already exists let loadingOverlay = enhancedBadgeDetailsContainer.querySelector('.enhancer_loading_overlay'); if (!loadingOverlay) { loadingOverlay = document.createElement('div'); loadingOverlay.classList.add('enhancer_loading_overlay'); loadingOverlay.textContent = "Updating data..."; enhancedBadgeDetailsContainer.appendChild(loadingOverlay); } else { loadingOverlay.textContent = "Updating data..."; // Update text if it exists loadingOverlay.style.display = 'flex'; // Ensure it's visible } }); // --- Fetch Steamsets data with 1-second delay --- await delay(STEAMSETS_API_CALL_DELAY_MS); const badgeData = await getBadgeData(appId); // --- Fetch Crafted Badge Info (Normal) with shorter delay --- await delay(STEAM_API_CALL_DELAY_MS); const craftedNormalInfo = await getCraftedBadgeInfo(appId, false); // --- Fetch Crafted Badge Info (Foil) with shorter delay --- await delay(STEAM_API_CALL_DELAY_MS); const craftedFoilInfo = await getCraftedBadgeInfo(appId, true); // Prepare cache entry const cacheEntry = { timestamp: Date.now(), steamsetsData: badgeData, craftedNormalInfo: craftedNormalInfo, craftedFoilInfo: craftedFoilInfo }; // Store fetched data in IndexedDB await setCacheEntry(appId, cacheEntry); // Use the new IndexedDB set function // Display the updated data *before* removing the overlay displayBadgeDetails(appId, rowsForApp, badgeData, craftedNormalInfo, craftedFoilInfo, false); // Displaying fresh data // Remove the loading overlay after displaying new data rowsForApp.forEach(badgeRow => { const enhancedBadgeDetailsContainer = badgeRow.querySelector('.enhanced_badge_details_container'); const loadingOverlay = enhancedBadgeDetailsContainer ? enhancedBadgeDetailsContainer.querySelector('.enhancer_loading_overlay') : null; if (loadingOverlay) { loadingOverlay.remove(); // Remove the overlay element } }); fetchQueue.shift(); // Remove the item from the queue AFTER successful fetch and cache currentlyFetching.delete(appId); // Remove from fetching set } isFetching = false; console.log("Finished fetch queue processing."); } // Function to display badge details (extracted for reusability) function displayBadgeDetails(appId, rowsForApp, badgeData, craftedNormalInfo, craftedFoilInfo, isCached) { console.log(`Displaying details for App ID ${appId}. Data is cached: ${isCached}`); // Find the existing container (created in initialSetup or processFetchQueue) rowsForApp.forEach(badgeRow => { const container = badgeRow.querySelector('.enhanced_badge_details_container'); if(container) { // Keep the container, but replace its *content* (excluding the overlay if it exists) const loadingOverlay = container.querySelector('.enhancer_loading_overlay'); // Find existing overlay // Create a temporary container to hold the new badge details HTML const tempDiv = document.createElement('div'); if (badgeData.length > 0) { // Sort badges: Levels 1-5 (non-foil) then Foil const sortedBadges = badgeData.sort((a, b) => { if (a.isFoil === b.isFoil) { return a.baseLevel - b.baseLevel; // Sort by level if same foil status } return a.isFoil ? 1 : -1; // Foil comes after non-foil }); // Create the HTML content for the detailed badges const detailedBadgesHtml = sortedBadges.map(badge => { const formattedCompletionDate = badge.firstCompletion ? formatDateForDisplay(badge.firstCompletion) : 'Date unavailable'; // Determine if this badge level should be highlighted let isCraftedHighlight = false; if (!badge.isFoil && craftedNormalInfo.isCrafted && craftedNormalInfo.craftedLevel === badge.baseLevel) { isCraftedHighlight = true; } else if (badge.isFoil && craftedFoilInfo.isCrafted && craftedFoilInfo.craftedLevel === badge.baseLevel) { isCraftedHighlight = true; } // Add 'crafted' class if it needs highlighting const containerClass = isCraftedHighlight ? 'badge_info_container crafted' : 'badge_info_container'; return ` <div class="${containerClass}"> <div class="badge_name" title="${badge.name}">${badge.name}</div> <img class="badge_image" src="https://cdn.fastly.steamstatic.com/steamcommunity/public/images/items/${appId}/${badge.badgeImage}" alt="${badge.name}"> <div class="badge_completion_date">${formattedCompletionDate}</div> <!-- Added First Completion Date --> <div class="badge_scarcity">Scarcity: ${badge.scarcity}</div> <div class="badge_level_display">Level: ${badge.baseLevel}${badge.isFoil ? ' (Foil)' : ''}</div> <!-- Added Level Display --> </div> `; }).join(''); // Join the array of HTML strings into a single string tempDiv.innerHTML = detailedBadgesHtml; } else { console.log(`No detailed badge data found for appId ${appId}. Displaying "No data available".`); // Display a message const noDataMessage = document.createElement('div'); noDataMessage.textContent = `No detailed badge data available for this game (App ID: ${appId}).`; noDataMessage.style.color = '#8f98a0'; noDataMessage.style.margin = '10px auto'; // Center the message tempDiv.appendChild(noDataMessage); } // Add a visual indicator if data was from cache if (isCached) { const cachedIndicator = document.createElement('div'); cachedIndicator.textContent = `⠀`; // Text indicating cache cachedIndicator.style.fontSize = '0.7em'; cachedIndicator.style.color = '#8f98a0'; cachedIndicator.style.textAlign = 'center'; cachedIndicator.style.marginTop = '5px'; // Prepend to the temporary container tempDiv.insertBefore(cachedIndicator, tempDiv.firstChild); } // Replace the *content* of the main container with the content from tempDiv // This avoids removing and re-adding the container itself or the overlay while(container.firstChild && container.firstChild !== loadingOverlay) { container.removeChild(container.firstChild); } while(tempDiv.firstChild) { container.insertBefore(tempDiv.firstChild, loadingOverlay); // Insert before the overlay if it exists } // Add the re-queue button if enabled, ENSURING IT DOESN'T ALREADY EXIST // This part remains the same as it's added to badge_title_stats, not the enhanced_badge_details_container if (isRequeueButtonEnabled) { const badgeTitleStatsContainer = badgeRow.querySelector('.badge_title_stats'); // Check if a re-queue button already exists within this container const existingRequeueButton = badgeTitleStatsContainer ? badgeTitleStatsContainer.querySelector('.requeue_button') : null; if (badgeTitleStatsContainer && !existingRequeueButton) { // Only add if container exists and button doesn't exist const requeueButton = document.createElement('div'); requeueButton.classList.add('requeue_button'); requeueButton.textContent = ' ↻ '; // Store the appId on the button for easy access in the event listener requeueButton.dataset.appId = appId; badgeTitleStatsContainer.appendChild(requeueButton); // Add click listener to the button requeueButton.addEventListener('click', handleRequeueClick); } else if (!badgeTitleStatsContainer) { console.warn(`Could not find .badge_title_stats container for App ID ${appId} to add re-queue button.`); } } } else { console.error(`Could not find .enhanced_badge_details_container for App ID ${appId} during display.`); } }); } // Event handler for the re-queue button function handleRequeueClick(event) { event.preventDefault(); // Prevent default link behavior event.stopPropagation(); // *** IMPORTANT: Stop click event from bubbling up *** const appId = parseInt(event.target.dataset.appId, 10); if (!isNaN(appId)) { console.log(`Manual re-queue requested for App ID: ${appId}`); removeCacheEntry(appId); // Remove the old cache entry queueAppIdForFetch(appId); // Add to the fetch queue } else { console.error("Could not get App ID from re-queue button data."); } } // Function to delete elements with class 'badge_title_stats_drops' and 'badge_title_stats_playtime' function deleteBadgeStats() { const dropsElements = document.querySelectorAll('.badge_title_stats_drops'); dropsElements.forEach(element => { element.remove(); // console.log("Removed element with class 'badge_title_stats_drops'"); // Too chatty }); const playtimeElements = document.querySelectorAll('.badge_title_stats_playtime'); playtimeElements.forEach(element => { element.remove(); // console.log("Removed element with class 'badge_title_stats_playtime'"); // Too chatty }); } // Initial setup: Collect all badge rows, display cached, and queue uncached/expired async function initialSetup() { // Make initialSetup async because it awaits openDatabase and getCacheEntry // Add the toggle buttons first addToggleButtons(); // Check if the script is enabled AFTER adding toggle buttons if (!isScriptEnabled) { console.log("Steam Badge Enhancer is disabled. Toggle buttons are available."); // Still delete the unnecessary stats elements even if enhancement is disabled deleteBadgeStats(); return; // Exit the function if disabled } console.log("Steam Badge Enhancer: Initial setup."); // --- Initialize IndexedDB --- try { await openDatabase(); } catch (error) { console.error("Failed to open IndexedDB. Caching will not be available.", error); // Decide how to proceed if IndexedDB fails. For now, we'll continue // but caching functions will log warnings. } // --- End IndexedDB Initialization --- // Delete the unnecessary stats elements immediately deleteBadgeStats(); const allBadgeRows = document.querySelectorAll('.badge_row.is_link'); // Group badge rows by App ID allBadgeRows.forEach(badgeRow => { const appId = extractAppIdFromBadgeLink(badgeRow); if (appId) { if (!badgeRowsByAppId.has(appId)) { badgeRowsByAppId.set(appId, []); } badgeRowsByAppId.get(appId).push(badgeRow); } else { console.warn("Could not extract App ID from a badge row:", badgeRow); } }); console.log(`Initial setup found ${badgeRowsByAppId.size} unique App IDs.`); const appIdsOnPage = new Set(badgeRowsByAppId.keys()); // Get all App IDs found on the page // Process App IDs on the page for (const appId of appIdsOnPage) { // Use for...of loop to allow await inside const rowsForApp = badgeRowsByAppId.get(appId); if (!rowsForApp || rowsForApp.length === 0) { console.warn(`No rows found for App ID ${appId} during initial setup.`); continue; // Skip if no rows found } // Add the initial container to each row immediately rowsForApp.forEach(badgeRow => { const enhancedBadgeDetailsContainer = document.createElement('div'); enhancedBadgeDetailsContainer.classList.add('enhanced_badge_details_container'); // Add an initial indicator message const initialIndicator = document.createElement('div'); initialIndicator.classList.add('initial_indicator'); initialIndicator.textContent = "Checking cache..."; // Initial state enhancedBadgeDetailsContainer.appendChild(initialIndicator); badgeRow.appendChild(enhancedBadgeDetailsContainer); }); // Try to get cache entry from IndexedDB (async) const cachedEntry = await getCacheEntry(appId); // Await the IndexedDB get if (isCacheValid(cachedEntry)) { console.log(`Found valid cache for App ID: ${appId}. Displaying immediately.`); // Immediately display cached data displayBadgeDetails(appId, rowsForApp, cachedEntry.steamsetsData, cachedEntry.craftedNormalInfo, cachedEntry.craftedFoilInfo, true); // Queue for background refresh if cache is old but still valid const now = Date.now(); if ((now - cachedEntry.timestamp) > (CACHE_EXPIRATION_MS / 2)) { // Example: refresh if cache is older than half the expiration time console.log(`Cache for App ID ${appId} is older than half the expiration, queueing for background refresh.`); queueAppIdForFetch(appId); } } else { console.log(`No valid cache for App ID: ${appId}. Queueing for fetch.`); // Update indicator to reflect API loading rowsForApp.forEach(badgeRow => { const indicator = badgeRow.querySelector('.enhanced_badge_details_container .initial_indicator'); if(indicator) indicator.textContent = "Loading detailed badge data..."; }); // Queue for background fetching queueAppIdForFetch(appId); } } // The processFetchQueue function will start automatically if the fetchQueue is not empty } // Run the initial setup when the page is loaded window.addEventListener('load', initialSetup); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址