// ==UserScript== // @name Unity Asset Store Auto Redeemer // @namespace https://github.com/creosB // @version 1.0 // @description Automatically redeem free Unity Asset Store products with Dynamic Island // @author CreosB // @match https://assetstore.unity.com/* // @grant none // @license This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. // @downloadURL https://update.greasyfork.icu/scripts/543205/Unity%20Asset%20Store%20Auto%20Redeemer.user.js // @updateURL https://update.greasyfork.icu/scripts/543205/Unity%20Asset%20Store%20Auto%20Redeemer.meta.js // ==/UserScript== (function() { 'use strict'; // Configuration const showRedemptions = true; const showNumberOfRedeemableProductsFoundOnCurrentPage = true; const showAgreementAccepting = true; const showMyAssetsOnlyAccepting = true; const timePerProduct = 500; const timeUntilAgreementAcceptance = 1000; const timeUntilMyAssetsOnly = 2500; const timeBeforeGoingToNextPage = 10000; let isRunning = false; let isExpanded = false; let currentStats = { current: 0, total: 0, currentAsset: '' }; let grabbedAssets = []; let isDragging = false; let dragOffset = { x: 0, y: 0 }; // Core functions function getAllProductDetails() { return document.querySelectorAll("div._3YDeD"); } function getNextButton() { return document.querySelector('button[label="Next"]:not(:disabled)'); } function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function redeemCurrentPage(goToNextPage = false) { if (!isRunning) return; console.log(`[INFO] Searching products on current page`); const allProductDetails = Array.from(getAllProductDetails()); const detailsOfFreeProducts = allProductDetails.filter(product => { const productHTML = product.innerHTML; // More precise filtering: must be FREE and have "Add to My Assets" button (not already owned) return productHTML.includes('FREE') && productHTML.includes('Add to My Assets') && !productHTML.includes('Open in Unity') && // Not already owned !productHTML.includes('Import'); // Not already in library }); if (showNumberOfRedeemableProductsFoundOnCurrentPage) { console.log(`[INFO] Found ${detailsOfFreeProducts.length} products to redeem.`); } currentStats.total = detailsOfFreeProducts.length; currentStats.current = 0; currentStats.currentAsset = ''; updateDynamicIsland(); for (let index = 0; index < detailsOfFreeProducts.length; index++) { if (!isRunning) return; const item = detailsOfFreeProducts[index]; const firstTextNode = item.querySelector('div[data-test="package-title"]'); // More robust ID extraction let id = null; const links = item.querySelectorAll("a"); for (const link of links) { if (link.href && link.href.includes('/packages/')) { const hrefParts = link.href.split("-"); if (hrefParts.length > 0) { const potentialId = hrefParts[hrefParts.length - 1]; if (/^\d+$/.test(potentialId)) { // Ensure it's a valid numeric ID id = potentialId; break; } } } } if (!id) { console.log(`[WARNING] Could not extract valid ID for product: ${firstTextNode ? firstTextNode.innerText : 'UNDEFINED'}`); continue; } const productName = firstTextNode ? firstTextNode.innerText : 'UNDEFINED'; currentStats.currentAsset = productName; updateDynamicIsland(); try { const r = await fetch("https://assetstore.unity.com/api/graphql", { "headers": { "operations": "AddToDownload", "x-csrf-token": getCookie('_csrf'), "x-requested-with": "XMLHttpRequest", "x-source": "storefront, storefront" }, "body": `{\"query\":\"mutation AddToDownload($id: String!) {\\n addToDownload(id: $id) {\\n id\\n userOverview {\\n lastDownloadAt: last_downloaded_at\\n __typename\\n }\\n userEntitlement {\\n id\\n orderId\\n grantTime\\n __typename\\n }\\n __typename\\n }\\n}\\n\",\"variables\":{\"id\":\"${id}\"},\"operationName\":\"AddToDownload\"}`, "method": "POST", }); if (r.ok) { const responseData = await r.json(); // Check if the GraphQL response contains the expected data structure if (responseData && responseData.data && responseData.data.addToDownload) { if (showRedemptions) { console.log(`[REDEEMED] ${index+1}/${detailsOfFreeProducts.length} - ${productName} (${id})`); } grabbedAssets.push({ name: productName, id: id, timestamp: new Date().toLocaleString() }); } else { // Handle GraphQL errors or unexpected response structure console.log(`[FAILED] ${index+1}/${detailsOfFreeProducts.length} - ${productName} (${id}) - Invalid response or already owned`); if (responseData.errors) { console.error('[ERROR] GraphQL errors:', responseData.errors); } } } else { console.log(`[FAILED] ${index+1}/${detailsOfFreeProducts.length} - ${productName} (${id}) - HTTP ${r.status}`); } } catch (err) { console.error(`[ERROR] ${index+1}/${detailsOfFreeProducts.length} - ${productName} (${id}) - ${err.message}`); } currentStats.current = index + 1; updateDynamicIsland(); await sleep(timePerProduct); } console.log('[INFO] Page should be done by now'); const nextButton = getNextButton(); if (goToNextPage && nextButton && isRunning) { console.log('[INFO] Going to next page'); nextButton.click(); while (getAllProductDetails().length <= 0 && isRunning) { await sleep(timeBeforeGoingToNextPage); } if (isRunning) { console.log('[INFO] Start redeeming new page'); redeemCurrentPage(goToNextPage); } } } async function redeemAll() { if (isRunning) return; isRunning = true; updateDynamicIsland(); console.log('[INFO] Starting auto redemption process...'); try { await redeemCurrentPage(true); } catch (error) { console.error('[ERROR] Auto redemption failed:', error); } isRunning = false; currentStats = { current: 0, total: 0, currentAsset: '' }; updateDynamicIsland(); console.log('[INFO] Auto redemption process completed.'); } function stopRedemption() { isRunning = false; currentStats = { current: 0, total: 0, currentAsset: '' }; updateDynamicIsland(); console.log('[INFO] Auto redemption process stopped.'); } // UI Creation function createDynamicIsland() { // Add glassmorphism SVG filter const svgFilter = document.createElement('div'); svgFilter.innerHTML = ` `; document.head.appendChild(svgFilter); // Create container const container = document.createElement('div'); container.id = 'unity-dynamic-island'; container.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; transition: all 0.4s cubic-bezier(0.25, 0.1, 0.25, 1); user-select: none; pointer-events: auto; `; // Create the main glass island const island = document.createElement('div'); island.id = 'unity-island'; island.style.cssText = ` position: relative; border-radius: 20px; overflow: hidden; cursor: pointer; transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3); min-width: 200px; `; // Glass layers const glassFilter = document.createElement('div'); glassFilter.className = 'glass-filter'; glassFilter.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 1; backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); filter: url(#glass-distortion) saturate(120%) brightness(1.15); `; const glassOverlay = document.createElement('div'); glassOverlay.className = 'glass-overlay'; glassOverlay.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 2; background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.2); `; const glassSpecular = document.createElement('div'); glassSpecular.className = 'glass-specular'; glassSpecular.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 3; box-shadow: inset 1px 1px 1px rgba(255, 255, 255, 0.4); `; // Glass content const glassContent = document.createElement('div'); glassContent.className = 'glass-content'; glassContent.style.cssText = ` position: relative; z-index: 4; padding: 12px 20px; color: white; display: flex; align-items: center; gap: 12px; min-height: 44px; `; // Drag handle const dragHandle = document.createElement('div'); dragHandle.style.cssText = ` width: 4px; height: 20px; background: rgba(255, 255, 255, 0.6); border-radius: 2px; cursor: move; flex-shrink: 0; border: 1px solid rgba(0, 0, 0, 0.2); `; // Status indicator const statusDot = document.createElement('div'); statusDot.id = 'unity-status-dot'; statusDot.style.cssText = ` width: 8px; height: 8px; border-radius: 50%; background: #34c759; transition: all 0.3s ease; flex-shrink: 0; box-shadow: 0 0 10px rgba(52, 199, 89, 0.5); `; // Main text content const textContent = document.createElement('div'); textContent.style.cssText = ` flex: 1; display: flex; flex-direction: column; gap: 2px; `; const mainText = document.createElement('span'); mainText.id = 'unity-main-text'; mainText.textContent = 'Auto Redeem'; mainText.style.cssText = ` color: white; font-size: 14px; font-weight: 600; white-space: nowrap; transition: all 0.3s ease; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); `; const subText = document.createElement('span'); subText.id = 'unity-sub-text'; subText.textContent = 'Ready'; subText.style.cssText = ` color: rgba(255, 255, 255, 0.9); font-size: 11px; font-weight: 400; white-space: nowrap; transition: all 0.3s ease; overflow: hidden; text-overflow: ellipsis; max-width: 200px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); `; textContent.appendChild(mainText); textContent.appendChild(subText); // Assemble island glassContent.appendChild(dragHandle); glassContent.appendChild(statusDot); glassContent.appendChild(textContent); island.appendChild(glassFilter); island.appendChild(glassOverlay); island.appendChild(glassSpecular); island.appendChild(glassContent); // Create expanded panel const expandedPanel = document.createElement('div'); expandedPanel.id = 'unity-expanded-panel'; expandedPanel.style.cssText = ` position: absolute; top: 60px; right: 0; width: 350px; max-height: 500px; border-radius: 20px; overflow: hidden; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 8px 24px rgba(0, 0, 0, 0.4); opacity: 0; pointer-events: none; transform: scale(0.9) translateY(-10px); transition: all 0.4s cubic-bezier(0.25, 0.1, 0.25, 1); `; // Expanded panel glass layers const expandedGlassFilter = document.createElement('div'); expandedGlassFilter.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 1; backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); filter: url(#glass-distortion) saturate(120%) brightness(1.15); `; const expandedGlassOverlay = document.createElement('div'); expandedGlassOverlay.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 2; background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.2); `; const expandedGlassSpecular = document.createElement('div'); expandedGlassSpecular.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 3; box-shadow: inset 1px 1px 1px rgba(255, 255, 255, 0.4); `; const expandedGlassContent = document.createElement('div'); expandedGlassContent.style.cssText = ` position: relative; z-index: 4; padding: 20px; color: white; height: 100%; `; expandedPanel.appendChild(expandedGlassFilter); expandedPanel.appendChild(expandedGlassOverlay); expandedPanel.appendChild(expandedGlassSpecular); expandedPanel.appendChild(expandedGlassContent); // Create expanded content createExpandedContent(expandedGlassContent); // Assemble final container container.appendChild(island); container.appendChild(expandedPanel); // Add event listeners setupEventListeners(container, island, dragHandle); document.body.appendChild(container); return container; } function createExpandedContent(container) { // Main content container with scroll const mainContent = document.createElement('div'); mainContent.id = 'unity-main-content'; mainContent.style.cssText = ` transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); opacity: 1; transform: translateX(0); `; // Progress section const progressSection = document.createElement('div'); progressSection.style.cssText = ` margin-bottom: 20px; `; const progressTitle = document.createElement('h4'); progressTitle.style.cssText = ` margin: 0 0 10px 0; font-size: 16px; font-weight: 600; color: white; `; progressTitle.textContent = 'Current Progress'; const progressBar = document.createElement('div'); progressBar.id = 'unity-progress-bar'; progressBar.style.cssText = ` width: 100%; height: 6px; background: rgba(255, 255, 255, 0.1); border-radius: 3px; overflow: hidden; margin-bottom: 8px; `; const progressFill = document.createElement('div'); progressFill.id = 'unity-progress-fill'; progressFill.style.cssText = ` height: 100%; background: linear-gradient(90deg, #34c759, #30d158); border-radius: 3px; width: 0%; transition: width 0.3s ease; `; const progressText = document.createElement('div'); progressText.id = 'unity-progress-text'; progressText.style.cssText = ` font-size: 12px; color: rgba(255, 255, 255, 0.9); text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); `; progressText.textContent = 'Ready to start'; progressBar.appendChild(progressFill); progressSection.appendChild(progressTitle); progressSection.appendChild(progressBar); progressSection.appendChild(progressText); // Buttons section const buttonsSection = document.createElement('div'); buttonsSection.style.cssText = ` display: flex; gap: 10px; margin-bottom: 20px; `; // Main action button const actionButton = document.createElement('button'); actionButton.id = 'unity-action-btn'; actionButton.style.cssText = ` flex: 1; padding: 12px; border-radius: 12px; background: linear-gradient(135deg, #34c759, #30d158); border: none; color: white; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 20px rgba(52, 199, 89, 0.3); `; actionButton.textContent = 'Start Auto Redeem'; // Show grabbed assets button const showAssetsButton = document.createElement('button'); showAssetsButton.id = 'unity-show-assets-btn'; showAssetsButton.style.cssText = ` padding: 12px; border-radius: 12px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; white-space: nowrap; `; showAssetsButton.textContent = `Assets (${grabbedAssets.length})`; buttonsSection.appendChild(actionButton); buttonsSection.appendChild(showAssetsButton); // Social buttons const socialContainer = document.createElement('div'); socialContainer.style.cssText = ` display: flex; gap: 8px; justify-content: center; margin-bottom: 15px; `; const socialButtons = [ { icon: ``, url: 'http://buymeacoffee.com/creos', title: 'Buy me a coffee' }, { icon: ` `, url: 'https://github.com/creosb', title: 'GitHub' }, { icon: ` `, url: 'https://x.com/CreosB', title: 'X (Twitter)' }, { icon: ` `, url: 'https://www.youtube.com/@CreosB', title: 'YouTube' } ]; socialButtons.forEach(btn => { const button = document.createElement('button'); button.style.cssText = ` width: 40px; height: 40px; border-radius: 12px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.15); color: white; font-size: 16px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); `; button.innerHTML = btn.icon; button.title = btn.title; button.addEventListener('click', () => window.open(btn.url, '_blank')); button.addEventListener('mouseenter', () => { button.style.background = 'rgba(255, 255, 255, 0.2)'; button.style.transform = 'scale(1.1)'; }); button.addEventListener('mouseleave', () => { button.style.background = 'rgba(255, 255, 255, 0.1)'; button.style.transform = 'scale(1)'; }); socialContainer.appendChild(button); }); // Tip text const tipText = document.createElement('p'); tipText.style.cssText = ` color: rgba(255, 255, 255, 0.8); font-size: 11px; margin: 0; line-height: 1.4; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); `; tipText.textContent = 'Depends on your filters on this page, it automatically adjusts what needs to be grabbed'; // Assemble main content mainContent.appendChild(progressSection); mainContent.appendChild(buttonsSection); mainContent.appendChild(socialContainer); mainContent.appendChild(tipText); // Assets view container (initially hidden) const assetsView = document.createElement('div'); assetsView.id = 'unity-assets-view'; assetsView.style.cssText = ` position: absolute; top: 0; left: 0; right: 0; bottom: 0; padding: 20px; background: inherit; opacity: 0; transform: translateX(100%); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: none; display: flex; flex-direction: column; `; // Assets view header const assetsHeader = document.createElement('div'); assetsHeader.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); `; const assetsTitle = document.createElement('h4'); assetsTitle.style.cssText = ` margin: 0; font-size: 16px; font-weight: 600; color: white; `; assetsTitle.textContent = 'Grabbed Assets'; const backButton = document.createElement('button'); backButton.id = 'unity-back-btn'; backButton.style.cssText = ` background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; font-size: 18px; cursor: pointer; padding: 8px 12px; border-radius: 8px; transition: all 0.3s ease; display: flex; align-items: center; gap: 6px; `; backButton.innerHTML = '← Back'; assetsHeader.appendChild(assetsTitle); assetsHeader.appendChild(backButton); // Search input (glass style) const searchContainer = document.createElement('div'); searchContainer.style.cssText = ` position: relative; margin-bottom: 15px; `; const searchInput = document.createElement('input'); searchInput.id = 'unity-assets-search'; searchInput.type = 'text'; searchInput.placeholder = 'Search assets...'; searchInput.style.cssText = ` width: 100%; padding: 12px 40px 12px 15px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 12px; color: white; font-size: 14px; transition: all 0.3s ease; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); `; const searchIcon = document.createElement('div'); searchIcon.style.cssText = ` position: absolute; right: 15px; top: 50%; transform: translateY(-50%); color: rgba(255, 255, 255, 0.6); font-size: 16px; pointer-events: none; `; searchIcon.textContent = '🔍'; const clearButton = document.createElement('button'); clearButton.id = 'unity-search-clear'; clearButton.style.cssText = ` position: absolute; right: 40px; top: 50%; transform: translateY(-50%); background: none; border: none; color: rgba(255, 255, 255, 0.6); font-size: 18px; cursor: pointer; padding: 2px; border-radius: 50%; opacity: 0; transition: all 0.3s ease; `; clearButton.innerHTML = '×'; searchContainer.appendChild(searchInput); searchContainer.appendChild(searchIcon); searchContainer.appendChild(clearButton); // Assets list const assetsList = document.createElement('div'); assetsList.id = 'unity-assets-list'; assetsList.style.cssText = ` flex: 1; overflow-y: auto; max-height: 300px; padding-right: 8px; `; // Assemble assets view assetsView.appendChild(assetsHeader); assetsView.appendChild(searchContainer); assetsView.appendChild(assetsList); // Add both views to container container.appendChild(mainContent); container.appendChild(assetsView); // Setup search functionality setupSearchFunctionality(searchInput, clearButton, assetsList); // Setup view switching setupViewSwitching(showAssetsButton, backButton, mainContent, assetsView); } function updateAssetsList() { const assetsList = document.getElementById('unity-assets-list'); if (!assetsList) return; assetsList.innerHTML = ''; if (grabbedAssets.length === 0) { const emptyMessage = document.createElement('div'); emptyMessage.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.6); font-size: 14px; padding: 40px 20px; display: flex; flex-direction: column; align-items: center; gap: 10px; `; emptyMessage.innerHTML = `
📦
No assets grabbed yet
Start the auto redeemer to grab free assets
`; assetsList.appendChild(emptyMessage); return; } grabbedAssets.forEach((asset, index) => { const assetItem = document.createElement('div'); assetItem.className = 'asset-item'; assetItem.style.cssText = ` padding: 12px; margin-bottom: 8px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 10px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); animation: fadeInUp 0.3s ease forwards; animation-delay: ${index * 0.05}s; `; const assetHeader = document.createElement('div'); assetHeader.style.cssText = ` display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 6px; `; const assetName = document.createElement('div'); assetName.style.cssText = ` font-weight: 600; font-size: 14px; color: white; line-height: 1.3; flex: 1; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); `; assetName.textContent = asset.name; const assetIndex = document.createElement('div'); assetIndex.style.cssText = ` font-size: 11px; color: rgba(255, 255, 255, 0.5); background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 6px; margin-left: 8px; `; assetIndex.textContent = `#${index + 1}`; const assetInfo = document.createElement('div'); assetInfo.style.cssText = ` font-size: 12px; color: rgba(255, 255, 255, 0.6); display: flex; justify-content: space-between; align-items: center; `; const assetId = document.createElement('span'); assetId.style.cssText = ` font-family: monospace; background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 4px; font-size: 11px; `; assetId.textContent = asset.id; const assetTime = document.createElement('span'); assetTime.style.cssText = ` font-size: 11px; opacity: 0.7; `; assetTime.textContent = asset.timestamp; assetInfo.appendChild(assetId); assetInfo.appendChild(assetTime); assetHeader.appendChild(assetName); assetHeader.appendChild(assetIndex); assetItem.appendChild(assetHeader); assetItem.appendChild(assetInfo); // Hover effects assetItem.addEventListener('mouseenter', () => { assetItem.style.background = 'rgba(255, 255, 255, 0.1)'; assetItem.style.borderColor = 'rgba(255, 255, 255, 0.2)'; assetItem.style.transform = 'translateY(-2px)'; assetItem.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.2)'; }); assetItem.addEventListener('mouseleave', () => { assetItem.style.background = 'rgba(255, 255, 255, 0.05)'; assetItem.style.borderColor = 'rgba(255, 255, 255, 0.1)'; assetItem.style.transform = 'translateY(0)'; assetItem.style.boxShadow = 'none'; }); assetsList.appendChild(assetItem); }); // Add CSS animation if (!document.getElementById('unity-assets-animation')) { const style = document.createElement('style'); style.id = 'unity-assets-animation'; style.textContent = ` @keyframes fadeInUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } `; document.head.appendChild(style); } } function filterAssetsList(searchTerm) { const assetItems = document.querySelectorAll('.asset-item'); let visibleCount = 0; assetItems.forEach((item, index) => { const assetName = item.querySelector('div div').textContent.toLowerCase(); const shouldShow = assetName.includes(searchTerm); if (shouldShow) { item.style.display = 'block'; item.style.animationDelay = `${visibleCount * 0.03}s`; visibleCount++; } else { item.style.display = 'none'; } }); // Show "no results" message if no items match const assetsList = document.getElementById('unity-assets-list'); if (assetsList && searchTerm && visibleCount === 0 && grabbedAssets.length > 0) { const noResults = document.createElement('div'); noResults.className = 'no-results-message'; noResults.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.6); font-size: 14px; padding: 40px 20px; display: flex; flex-direction: column; align-items: center; gap: 10px; `; noResults.innerHTML = `
🔍
No assets found
Try a different search term
`; // Remove existing no-results message const existingNoResults = assetsList.querySelector('.no-results-message'); if (existingNoResults) { existingNoResults.remove(); } assetsList.appendChild(noResults); } else { // Remove no-results message when showing results const existingNoResults = assetsList.querySelector('.no-results-message'); if (existingNoResults) { existingNoResults.remove(); } } } function setupEventListeners(container, island, dragHandle) { // Drag functionality let isDragging = false; let dragOffset = { x: 0, y: 0 }; dragHandle.addEventListener('mousedown', (e) => { isDragging = true; const rect = container.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; dragHandle.style.cursor = 'grabbing'; // Disable transitions during drag for better performance container.style.transition = 'none'; island.style.transition = 'none'; e.preventDefault(); e.stopPropagation(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const newX = e.clientX - dragOffset.x; const newY = e.clientY - dragOffset.y; // Keep within viewport bounds const maxX = window.innerWidth - container.offsetWidth; const maxY = window.innerHeight - container.offsetHeight; const clampedX = Math.max(0, Math.min(newX, maxX)); const clampedY = Math.max(0, Math.min(newY, maxY)); container.style.left = clampedX + 'px'; container.style.top = clampedY + 'px'; container.style.right = 'auto'; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; dragHandle.style.cursor = 'move'; // Re-enable transitions after drag container.style.transition = 'all 0.4s cubic-bezier(0.25, 0.1, 0.25, 1)'; island.style.transition = 'all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1)'; } }); // Click to expand (only on non-drag handle areas) island.addEventListener('click', (e) => { if (e.target !== dragHandle && !isDragging) { toggleExpanded(); } }); // Set up button event listeners after a short delay to ensure elements exist setTimeout(() => { // Action button const actionButton = document.getElementById('unity-action-btn'); if (actionButton) { actionButton.addEventListener('click', (e) => { e.stopPropagation(); console.log('[DEBUG] Action button clicked, isRunning:', isRunning); if (isRunning) { stopRedemption(); } else { redeemAll(); } }); actionButton.addEventListener('mouseenter', () => { actionButton.style.transform = 'scale(1.05)'; }); actionButton.addEventListener('mouseleave', () => { actionButton.style.transform = 'scale(1)'; }); } else { console.log('[DEBUG] Action button not found'); } // Note: Show assets button event listener is now handled in setupViewSwitching function }, 100); // Glass effect on mouse move (optimized) let animationFrame; container.addEventListener('mousemove', (e) => { if (animationFrame) cancelAnimationFrame(animationFrame); animationFrame = requestAnimationFrame(() => { const rect = container.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const specular = container.querySelector('.glass-specular'); if (specular) { specular.style.background = `radial-gradient( circle at ${x}px ${y}px, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0.15) 30%, rgba(255,255,255,0) 60% )`; } }); }); container.addEventListener('mouseleave', () => { if (animationFrame) cancelAnimationFrame(animationFrame); const specular = container.querySelector('.glass-specular'); if (specular) { specular.style.background = 'none'; } }); // Hover effects island.addEventListener('mouseenter', () => { if (!isExpanded && !isDragging) { island.style.transform = 'scale(1.02)'; } }); island.addEventListener('mouseleave', () => { if (!isExpanded && !isDragging) { island.style.transform = 'scale(1)'; } }); } function toggleExpanded() { isExpanded = !isExpanded; const island = document.getElementById('unity-island'); const expandedPanel = document.getElementById('unity-expanded-panel'); if (isExpanded) { expandedPanel.style.opacity = '1'; expandedPanel.style.transform = 'scale(1) translateY(0)'; expandedPanel.style.pointerEvents = 'auto'; island.style.borderBottomLeftRadius = '12px'; island.style.borderBottomRightRadius = '12px'; } else { expandedPanel.style.opacity = '0'; expandedPanel.style.transform = 'scale(0.9) translateY(-10px)'; expandedPanel.style.pointerEvents = 'none'; island.style.borderBottomLeftRadius = '20px'; island.style.borderBottomRightRadius = '20px'; } } function updateDynamicIsland() { const statusDot = document.getElementById('unity-status-dot'); const mainText = document.getElementById('unity-main-text'); const subText = document.getElementById('unity-sub-text'); const actionButton = document.getElementById('unity-action-btn'); const showAssetsButton = document.getElementById('unity-show-assets-btn'); const progressFill = document.getElementById('unity-progress-fill'); const progressText = document.getElementById('unity-progress-text'); if (!statusDot || !mainText || !subText) return; if (isRunning) { statusDot.style.background = '#ff3b30'; statusDot.style.boxShadow = '0 0 10px rgba(255, 59, 48, 0.5)'; if (currentStats.total > 0) { const progress = (currentStats.current / currentStats.total) * 100; mainText.textContent = `${currentStats.current}/${currentStats.total}`; subText.textContent = currentStats.currentAsset || 'Processing...'; if (progressFill) { progressFill.style.width = progress + '%'; } if (progressText) { progressText.textContent = `${currentStats.current} of ${currentStats.total} assets`; } } else { mainText.textContent = 'Running...'; subText.textContent = 'Searching for assets...'; if (progressText) { progressText.textContent = 'Initializing...'; } } if (actionButton) { actionButton.textContent = 'Stop Process'; actionButton.style.background = 'linear-gradient(135deg, #ff3b30, #ff453a)'; actionButton.style.boxShadow = '0 4px 20px rgba(255, 59, 48, 0.3)'; } } else { statusDot.style.background = '#34c759'; statusDot.style.boxShadow = '0 0 10px rgba(52, 199, 89, 0.5)'; mainText.textContent = 'Auto Redeem'; subText.textContent = `Ready • ${grabbedAssets.length} assets grabbed`; if (actionButton) { actionButton.textContent = 'Start Auto Redeem'; actionButton.style.background = 'linear-gradient(135deg, #34c759, #30d158)'; actionButton.style.boxShadow = '0 4px 20px rgba(52, 199, 89, 0.3)'; } if (progressFill) { progressFill.style.width = '0%'; } if (progressText) { progressText.textContent = 'Ready to start'; } } if (showAssetsButton) { showAssetsButton.textContent = `Assets (${grabbedAssets.length})`; } } // Initialize function init() { // Wait for page to load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); return; } // Check if we're on the right page (has product listings) const checkInterval = setInterval(() => { if (getAllProductDetails().length > 0) { clearInterval(checkInterval); createDynamicIsland(); console.log('[INFO] Unity Asset Store Auto Redeemer loaded with Dynamic Island UI.'); } }, 1000); // Stop checking after 30 seconds setTimeout(() => { clearInterval(checkInterval); }, 30000); } // Close expanded panel when clicking outside document.addEventListener('click', (e) => { if (isExpanded && !e.target.closest('#unity-dynamic-island')) { toggleExpanded(); } }); function setupSearchFunctionality(searchInput, clearButton, assetsList) { // Search input functionality searchInput.addEventListener('input', (e) => { const searchTerm = e.target.value.toLowerCase(); filterAssetsList(searchTerm); // Show/hide clear button if (searchTerm.length > 0) { clearButton.style.opacity = '1'; } else { clearButton.style.opacity = '0'; } }); // Focus effects searchInput.addEventListener('focus', () => { searchInput.style.background = 'rgba(255, 255, 255, 0.15)'; searchInput.style.borderColor = 'rgba(255, 255, 255, 0.3)'; searchInput.style.transform = 'translateY(-1px)'; searchInput.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.2)'; }); searchInput.addEventListener('blur', () => { searchInput.style.background = 'rgba(255, 255, 255, 0.1)'; searchInput.style.borderColor = 'rgba(255, 255, 255, 0.2)'; searchInput.style.transform = 'translateY(0)'; searchInput.style.boxShadow = 'none'; }); // Clear button functionality clearButton.addEventListener('click', () => { searchInput.value = ''; clearButton.style.opacity = '0'; filterAssetsList(''); searchInput.focus(); }); // Clear button hover effects clearButton.addEventListener('mouseenter', () => { clearButton.style.background = 'rgba(255, 255, 255, 0.1)'; clearButton.style.color = 'white'; }); clearButton.addEventListener('mouseleave', () => { clearButton.style.background = 'none'; clearButton.style.color = 'rgba(255, 255, 255, 0.6)'; }); } function setupViewSwitching(showAssetsButton, backButton, mainContent, assetsView) { let isInAssetsView = false; // Show assets view showAssetsButton.addEventListener('click', (e) => { e.stopPropagation(); console.log('[DEBUG] Show assets button clicked'); if (!isInAssetsView) { // Update assets list before showing updateAssetsList(); // Animate to assets view mainContent.style.opacity = '0'; mainContent.style.transform = 'translateX(-100%)'; setTimeout(() => { assetsView.style.opacity = '1'; assetsView.style.transform = 'translateX(0)'; assetsView.style.pointerEvents = 'auto'; // Focus search input const searchInput = document.getElementById('unity-assets-search'); if (searchInput) { setTimeout(() => searchInput.focus(), 200); } }, 200); isInAssetsView = true; } }); // Back to main view backButton.addEventListener('click', (e) => { e.stopPropagation(); if (isInAssetsView) { // Animate back to main view assetsView.style.opacity = '0'; assetsView.style.transform = 'translateX(100%)'; assetsView.style.pointerEvents = 'none'; setTimeout(() => { mainContent.style.opacity = '1'; mainContent.style.transform = 'translateX(0)'; }, 200); isInAssetsView = false; // Clear search const searchInput = document.getElementById('unity-assets-search'); if (searchInput) { searchInput.value = ''; filterAssetsList(''); } } }); // Back button hover effects backButton.addEventListener('mouseenter', () => { backButton.style.background = 'rgba(255, 255, 255, 0.15)'; backButton.style.transform = 'translateX(-2px)'; }); backButton.addEventListener('mouseleave', () => { backButton.style.background = 'rgba(255, 255, 255, 0.1)'; backButton.style.transform = 'translateX(0)'; }); } init(); })();