// Pokemon Stock Monitor - Content Script // Runs on PokemonCenter pages to extract product data and intercept APIs (function() { console.log("[Pokemon Monitor] Content script loaded on:", window.location.href); // Inject the API interceptor into the page context function injectApiInterceptor() { const script = document.createElement('script'); script.src = chrome.runtime.getURL('api-interceptor.js'); script.onload = () => script.remove(); (document.head || document.documentElement).appendChild(script); } // Listen for messages from the injected interceptor window.addEventListener('message', (event) => { if (event.source !== window) return; if (event.data?.source !== 'pokemon-api-interceptor') return; // Forward API data to background script chrome.runtime.sendMessage({ type: 'apiData', data: event.data.data }).catch(() => {}); }); // Inject the interceptor injectApiInterceptor(); // Listen for messages from background script chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "extractProducts") { console.log("[Pokemon Monitor] Extracting products..."); const products = extractProducts(); console.log("[Pokemon Monitor] Found", products.length, "products"); sendResponse(products); } return true; }); function extractProducts() { const products = []; // Group all product links by URL const urlToLinks = {}; document.querySelectorAll('a[href*="/product/"]').forEach(link => { const url = link.href; if (!urlToLinks[url]) { urlToLinks[url] = []; } urlToLinks[url].push(link); }); console.log("[Pokemon Monitor] Found", Object.keys(urlToLinks).length, "unique product URLs"); // Process each unique product URL for (const [url, links] of Object.entries(urlToLinks)) { // Find the best name from all links to this product let bestName = ""; let isSoldOut = false; for (const link of links) { const clone = link.cloneNode(true); clone.querySelectorAll('svg, style').forEach(el => el.remove()); const text = clone.textContent.trim(); // Check if any link says "SOLD OUT" if (text.toUpperCase().includes("SOLD OUT")) { isSoldOut = true; } // Pick the longest non-"SOLD OUT" text as the name if (text.length > bestName.length && !text.toUpperCase().startsWith("SOLD") && text.length > 10) { bestName = text; } } // Skip if we couldn't find a good name if (!bestName || bestName.length < 10) continue; // Clean up name - remove price if embedded bestName = bestName.replace(/\$[\d,.]+/g, "").trim(); // Remove "Add to Cart" etc bestName = bestName.replace(/Add to Cart/gi, "").trim(); // Remove review star CSS/SVG junk and plain review counts bestName = bestName.replace(/\s*(review-|\d*\s*reviews?).*$/i, "").trim(); // Clean whitespace bestName = bestName.replace(/\s+/g, " ").trim(); // Find price from any of the links' containers let price = null; for (const link of links) { let parent = link.parentElement; for (let i = 0; i < 6 && parent && !price; i++) { const priceMatch = parent.textContent.match(/\$[\d,]+\.?\d*/); if (priceMatch) { price = priceMatch[0]; break; } parent = parent.parentElement; } if (price) break; } // Find image from any of the links let imageUrl = null; for (const link of links) { const img = link.querySelector("img") || link.closest("[class*='product']")?.querySelector("img"); if (img) { imageUrl = img.src || img.dataset.src; if (imageUrl && !imageUrl.startsWith("data:")) break; } } // Extract product ID from URL const idMatch = url.match(/\/product\/([^\/]+)/); const productId = idMatch ? idMatch[1] : url; products.push({ name: bestName, url: url, price: price, inStock: !isSoldOut, imageUrl: imageUrl, productId: productId, site: "pokemoncenter" }); } console.log("[Pokemon Monitor] Extracted products:", products.map(p => ({name: p.name.slice(0,40), inStock: p.inStock}))); return products; } })();