Skip to content

Instantly share code, notes, and snippets.

@ivanlonel
Created June 27, 2025 22:46
Show Gist options
  • Save ivanlonel/292e43f6dda6417f1bd242f31f21be9e to your computer and use it in GitHub Desktop.
Save ivanlonel/292e43f6dda6417f1bd242f31f21be9e to your computer and use it in GitHub Desktop.
Pokemon Zone Card Collection CSV Downloader
// ==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