Created
June 27, 2025 22:46
-
-
Save ivanlonel/292e43f6dda6417f1bd242f31f21be9e to your computer and use it in GitHub Desktop.
Pokemon Zone Card Collection CSV Downloader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Pokemon Zone Card Collection CSV Downloader | |
// @namespace http://tampermonkey.net/ | |
// @version 1.0 | |
// @description Download Pokemon card collection data as CSV from Pokemon Zone | |
// @author Ivan Donisete Lonel | |
// @match https://www.pokemon-zone.com/players/*/cards/ | |
// @grant none | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
// Wait for the page to load completely | |
function waitForElement(selector, timeout = 10000) { | |
return new Promise((resolve, reject) => { | |
const startTime = Date.now(); | |
function check() { | |
const element = document.querySelector(selector); | |
if (element) { | |
resolve(element); | |
} else if (Date.now() - startTime > timeout) { | |
reject(new Error(`Element ${selector} not found within ${timeout}ms`)); | |
} else { | |
setTimeout(check, 100); | |
} | |
} | |
check(); | |
}); | |
} | |
// Function to create and download CSV | |
function downloadCSV(data, filename) { | |
const csvContent = data.map(row => | |
row.map(field => `"${field.toString().replace(/"/g, '""')}"`).join(',') | |
).join('\n'); | |
const link = document.createElement('a'); | |
const url = URL.createObjectURL(new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })); | |
link.setAttribute('href', url); | |
link.setAttribute('download', filename); | |
link.style.visibility = 'hidden'; | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
} | |
// Function to extract card data | |
function extractCardData(cardElement) { | |
try { | |
// Get the link element for Set and ID | |
const linkElement = cardElement.querySelector('div[class*="player-expansion-collection-card__preview"] div[class*="player-expansion-collection-card__card"] div[class*="game-card-image"] a'); | |
let set = ''; | |
let id = ''; | |
if (linkElement && linkElement.href) { | |
const href = linkElement.getAttribute('href'); | |
const pathParts = href.split('/').filter(part => part.length > 0); | |
if (pathParts.length >= 4 && pathParts[0] === 'cards') { | |
// Extract set (second part, capitalized) | |
set = pathParts[1].charAt(0).toUpperCase() + pathParts[1].slice(1); | |
// Extract ID (third part) | |
id = pathParts[2]; | |
} | |
} | |
// Get the name | |
const nameElement = cardElement.querySelector('div[class*="player-expansion-collection-card__footer"] div[class*="player-expansion-collection-card__name"] div[class*="player-expansion-collection-card__name-text"]'); | |
// Get the amount | |
const amountElement = cardElement.querySelector('div[class*="player-expansion-collection-card__preview"] div[class*="player-expansion-collection-card__count"]'); | |
return { | |
set: set, | |
id: id, | |
name: nameElement ? nameElement.textContent.trim() : '', | |
amount: amountElement ? amountElement.textContent.trim() : '0' | |
}; | |
} catch (error) { | |
console.error('Error extracting card data:', error); | |
return { | |
set: '', | |
id: '', | |
name: '', | |
amount: '0' | |
}; | |
} | |
} | |
// Main function to handle the download process | |
async function handleDownload() { | |
const downloadButton = document.getElementById('pokemon-csv-download'); | |
downloadButton.disabled = true; | |
downloadButton.textContent = 'Loading...'; | |
try { | |
const rootDiv = document.querySelector('div[class*="data-infinite-scroller"]'); | |
if (!rootDiv) { | |
throw new Error('Root div not found'); | |
} | |
// Keep clicking the "Load More" button until it's no longer visible | |
let loadMoreButton; | |
let clickCount = 0; | |
const maxClicks = 1000; // Safety limit | |
while (clickCount < maxClicks) { | |
loadMoreButton = rootDiv.querySelector('div[class*="data-infinite-scroller__more"] button'); | |
if (!loadMoreButton || loadMoreButton.style.display === 'none' || !loadMoreButton.offsetParent) { | |
console.log('Load more button is no longer visible or available'); | |
break; | |
} | |
console.log(`Clicking load more button (${clickCount + 1})`); | |
loadMoreButton.click(); | |
clickCount++; | |
// Wait for new content to load | |
await new Promise(resolve => setTimeout(resolve, 1000)); | |
// Update button text to show progress | |
downloadButton.textContent = `Loading... (${clickCount} clicks)`; | |
} | |
if (clickCount >= maxClicks) { | |
console.warn('Reached maximum click limit'); | |
} | |
downloadButton.textContent = 'Extracting data...'; | |
// Wait a bit more for final content to load | |
await new Promise(resolve => setTimeout(resolve, 2000)); | |
// Extract all card data - use XPath-equivalent selector to match exactly 1327 elements | |
const cardElements = Array.from(rootDiv.querySelectorAll('div')).filter(el => { | |
// Check if this div is inside a collection-card-grid | |
const isInCardGrid = el.closest('div[class*="collection-card-grid"]'); | |
// Check if this div has the exact player-expansion-collection-card class (not a subclass) | |
const hasCardClass = Array.from(el.classList).some(cls => | |
cls === 'player-expansion-collection-card' || | |
(cls.includes('player-expansion-collection-card') && !cls.includes('__')) | |
); | |
return isInCardGrid && hasCardClass; | |
}); | |
console.log(`Found ${cardElements.length} card elements`); | |
const csvData = [['Set', 'ID', 'Name', 'Amount']]; // Header row | |
cardElements.forEach((cardElement, index) => { | |
const cardData = extractCardData(cardElement); | |
csvData.push([cardData.set, cardData.id, cardData.name, cardData.amount]); | |
// Update progress | |
if (index % 100 === 0) { | |
downloadButton.textContent = `Processing... (${index + 1}/${cardElements.length})`; | |
} | |
}); | |
// Generate filename with current date and download the CSV | |
const now = new Date(); | |
const dateStr = now.toISOString().split('T')[0]; | |
downloadCSV(csvData, `pokemon-cards-${dateStr}.csv`); | |
downloadButton.textContent = `Downloaded ${csvData.length - 1} cards`; | |
console.log(`CSV downloaded with ${csvData.length - 1} cards`); | |
} catch (error) { | |
console.error('Error during download process:', error); | |
downloadButton.textContent = 'Error occurred'; | |
alert('An error occurred while downloading the data. Check the console for details.'); | |
} finally { | |
// Re-enable button after a delay | |
setTimeout(() => { | |
downloadButton.disabled = false; | |
downloadButton.textContent = 'Download CSV'; | |
}, 3000); | |
} | |
} | |
// Initialize the script | |
async function init() { | |
try { | |
// Wait for the root div to be available | |
const rootDiv = await waitForElement('div[class*="data-infinite-scroller"]'); | |
// Create the download button | |
const downloadButton = document.createElement('button'); | |
downloadButton.id = 'pokemon-csv-download'; | |
downloadButton.textContent = 'Download CSV'; | |
downloadButton.style.cssText = ` | |
background-color: #4CAF50; | |
color: white; | |
padding: 10px 20px; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
font-size: 14px; | |
margin: 10px 0; | |
font-weight: bold; | |
`; | |
// Add hover effect | |
downloadButton.addEventListener('mouseenter', () => { | |
downloadButton.style.backgroundColor = '#45a049'; | |
}); | |
downloadButton.addEventListener('mouseleave', () => { | |
downloadButton.style.backgroundColor = '#4CAF50'; | |
}); | |
// Add click event | |
downloadButton.addEventListener('click', handleDownload); | |
// Insert the button before the first child of the root div | |
if (rootDiv.firstChild) { | |
rootDiv.insertBefore(downloadButton, rootDiv.firstChild); | |
} else { | |
rootDiv.appendChild(downloadButton); | |
} | |
console.log('Pokemon Zone CSV Downloader initialized'); | |
} catch (error) { | |
console.error('Failed to initialize Pokemon Zone CSV Downloader:', error); | |
} | |
} | |
// Start the script when the page is loaded | |
if (document.readyState === 'loading') { | |
document.addEventListener('DOMContentLoaded', init); | |
} else { | |
init(); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment